diff --git a/docs/build.gradle b/docs/build.gradle index f66cf249..433293a8 100644 --- a/docs/build.gradle +++ b/docs/build.gradle @@ -16,6 +16,10 @@ liveReload { docRoot asciidoctor.sourceDir.canonicalPath } +repositories { + maven { url 'http://dist.gemstone.com/maven/release' } +} + asciidoctorj { } @@ -25,6 +29,7 @@ tasks.findByPath("artifactoryPublish")?.enabled = false dependencies { testCompile project(':spring-session'), "org.springframework.data:spring-data-redis:$springDataRedisVersion", + "org.springframework.data:spring-data-gemfire:$springDataGemFireVersion", "org.springframework:spring-websocket:${springVersion}", "org.springframework:spring-messaging:${springVersion}", "org.springframework.security:spring-security-web:${springSecurityVersion}", @@ -61,4 +66,4 @@ asciidoctor { 'idseparator':'-', 'docinfo1':'true', 'revnumber' : project.version -} \ No newline at end of file +} diff --git a/docs/src/docs/asciidoc/guides/httpsession-gemfire-clientserver-xml.adoc b/docs/src/docs/asciidoc/guides/httpsession-gemfire-clientserver-xml.adoc new file mode 100644 index 00000000..08f68baf --- /dev/null +++ b/docs/src/docs/asciidoc/guides/httpsession-gemfire-clientserver-xml.adoc @@ -0,0 +1,272 @@ += Spring Session - HttpSession with GemFire Client/Server using XML (Quick Start) +John Blum +:toc: + +This guide describes how to configure Spring Session to transparently leverage Pivotal GemFire to back a web application's +`HttpSession` using XML Configuration. + +NOTE: The completed guide can be found in the <>. + +== Updating Dependencies +Before using Spring Session, you must ensure that the required dependencies are included. +If you are using Maven, include the following `dependencies` in your _pom.xml_: + +.pom.xml +[source,xml] +[subs="verbatim,attributes"] +---- + + + + + org.springframework.session + spring-session-data-gemfire + {spring-session-version} + pom + + + org.springframework + spring-web + {spring-version} + + +---- + +ifeval::["{version-snapshot}" == "true"] +Since we are using a SNAPSHOT version, we need to add the Spring Snapshot Maven Repository. +If you are using Maven, include the following `repository` declaration in your _pom.xml_: + +.pom.xml +[source,xml] +---- + + + + + spring-snapshot + https://repo.spring.io/libs-snapshot + + +---- +endif::[] + +ifeval::["{version-milestone}" == "true"] +Since we are using a Milestone version, we need to add the Spring Milestone Maven Repository. +If you are using Maven, include the following `repository` declaration in your _pom.xml_: + +.pom.xml +[source,xml] +---- + + + + + spring-milestone + https://repo.spring.io/libs-milestone + + +---- +endif::[] + +// tag::config[] +[[httpsession-spring-xml-configuration]] +== Spring XML Configuration + +After adding the required dependencies and repository declarations, we can create our Spring configuration. +The Spring configuration is responsible for creating a Servlet Filter that replaces the `HttpSession` +with an implementation backed by Spring Session and GemFire. + +Add the following Spring Configuration: + +[source,xml] +---- +include::{samples-dir}httpsession-gemfire-clientserver-xml/src/main/webapp/WEB-INF/spring/session-client.xml[tags=beans] +---- + +<1> First, a `Properties` bean is created to reference GemFire configuration common to both the client and server, +stored in the `META-INF/spring/application.properties` file. +<2> The `application.properties` are used along with the `PropertySourcesPlaceholderConfigurer` bean to replace +placeholders in the Spring XML configuration meta-data with property values. +<3> Spring annotation configuration support is enabled with `` element so that any +Spring beans declared in the XML config that are annotated with either Spring or Standard Java annotations supported +by Spring will be configured appropriately. +<4> `GemFireHttpSessionConfiguration` is registered to enable Spring Session functionality. +<5> Then, a Spring `BeanPostProcessor` is registered to determine whether a GemFire Server at the designated host/port +is running, blocking client startup until the server is available. +<6> Next, we include a `Properties` bean to configure certain aspects of the GemFire client cache using +http://gemfire.docs.pivotal.io/docs-gemfire/reference/topics/gemfire_properties.html[GemFire's System properties]. +In this case, we are just setting GemFire's `log-level` from a sample application-specific System property, defaulting +to `warning` if unspecified. +<7> Finally, we create the GemFire client cache and configure a Pool of client connections to talk to the GemFire Server +in our Client/Server topology. In our configuration, we use sensible settings for timeouts, number of connections +and so on. Also, our `Pool` has been configured to connect directly to a server. + +TIP: In typical GemFire deployments, where the cluster includes potentially hundreds of GemFire data nodes (servers), +it is more common for clients to connect to one or more GemFire Locators running in the cluster. A Locator passes meta-data +to clients about the servers available, load and which servers have the client's data of interest, which is particularly +important for single-hop, direct data access. See more details about the http://gemfire.docs.pivotal.io/docs-gemfire/latest/topologies_and_comm/cs_configuration/chapter_overview.html[Client/Server Topology in GemFire's User Guide]. + +NOTE: For more information on configuring _Spring Data GemFire_, refer to the http://docs.spring.io/spring-data-gemfire/docs/current/reference/html/[reference guide]. + +=== Server Configuration + +Now, we have only covered one side of the equation. We also need a GemFire Server for our client to talk to and pass +session state information up to the server to manage. + +In this sample, we will use the following GemFire Server Java Configuration: + +[source,xml] +---- +include::{samples-dir}httpsession-gemfire-clientserver-xml/src/main/resources/META-INF/spring/session-server.xml[tags=beans] +---- + +<1> First, we enable Spring annotation config support with the `` element so that any +Spring beans declared in the XML config that are annotated with either Spring or Standard Java annotations supported +by Spring will be configured appropriately. +<2> A `PropertySourcesPlaceholderConfigurer` is registered to replace placeholders in our Spring XML configuration +meta-data with property values from `META-INF/spring/application.properties`. +<3> We enable the same Spring Session functionality that we used on the client by registering an instance of `GemFireHttpSessionConfiguration`, +except that we set the session expiration timeout to **30 seconds**. We will explain later what this means. +<4> Next, we configure the GemFire Server using GemFire System properties very much like our P2P samples. +With the `mcast-port` set to 0 and no `locators` property specified, our server will be standalone. We also allow a +JMX client (e.g. _Gfsh_) to connect to our server with the use of the GemFire-specific JMX System properties. +<5> Then, we create an instance of the GemFire peer cache using our GemFire System properties. +<6> And finally, we also setup a GemFire `CacheServer` instance running on *localhost*, listening on port **11235**, +to accept our client connection. + +The GemFire Server configuration gets bootstrapped with the following: + +[source,java] +---- +include::{samples-dir}httpsession-gemfire-clientserver-xml/src/main/java/sample/Application.java[tags=class] +---- + +TIP: Instead of a simple Java class with a main method, you could also use _Spring Boot_. + +<1> The `@Configuration` annotation designates this Java class as a source for Spring configuration meta-data using +Spring's annotation configuration support. +<2> Primarily, the configuration comes from the `META-INF/spring/session-server.xml` file, which is also the reason +why _Spring Boot_ was not used in this sample, since using XML seemingly defeats the purpose and benefits +of using Spring Boot. However, this sample is about demonstrating how to use Spring XML to configure +the GemFire client and server. + +== XML Servlet Container Initialization + +Our <> created a Spring bean named `springSessionRepositoryFilter` +that implements `Filter`. The `springSessionRepositoryFilter` bean is responsible for replacing the `HttpSession` with +a custom implementation that is backed by Spring Session and GemFire. + +In order for our `Filter` to do its magic, we need to instruct Spring to load our `session-client.xml` configuration file. +We do this with the following configuration: + +.src/main/webapp/WEB-INF/web.xml +[source,xml,indent=0] +---- +include::{samples-dir}httpsession-gemfire-clientserver-xml/src/main/webapp/WEB-INF/web.xml[tags=context-param] +include::{samples-dir}httpsession-gemfire-clientserver-xml/src/main/webapp/WEB-INF/web.xml[tags=listeners] +---- + +The http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#context-create[ContextLoaderListener] +reads the `contextConfigLocation` context parameter value and picks up our _session-client.xml_ configuration file. + +Finally, we need to ensure that our Servlet Container (i.e. Tomcat) uses our `springSessionRepositoryFilter` +for every request. + +The following snippet performs this last step for us: + +.src/main/webapp/WEB-INF/web.xml +[source,xml,indent=0] +---- +include::{samples-dir}httpsession-gemfire-clientserver-xml/src/main/webapp/WEB-INF/web.xml[tags=springSessionRepositoryFilter] +---- + +The http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/filter/DelegatingFilterProxy.html[DelegatingFilterProxy] +will look up a bean by the name of `springSessionRepositoryFilter` and cast it to a `Filter`. For every request that `DelegatingFilterProxy` +is invoked, the `springSessionRepositoryFilter` will be invoked. +// end::config[] + +[[httpsession-gemfire-clientserver-xml-sample-app]] +== HttpSession with GemFire (Client/Server) using XML Sample Application + + +=== Running the httpsession-gemfire-clientserver-xml Sample Application + +You can run the sample by obtaining the {download-url}[source code] and invoking the following commands. + +First, you need to run the server using: + +---- +$ ./gradlew :samples:httpsession-gemfire-clientserver-xml:run [-Dsample.httpsession.gemfire.log-level=info] +---- + +Now, in a separate terminal, you can run the client using: + +---- +$ ./gradlew :samples:httpsession-gemfire-clientserver-xml:tomcatRun [-Dsample.httpsession.gemfire.log-level=info] +---- + +You should now be able to access the application at http://localhost:8080/. In this sample, the web application +is the client cache and the server is standalone. + +=== Exploring the httpsession-gemfire-clientserver-xml Sample Application + +Try using the application. Fill out the form with the following information: + +* **Attribute Name:** _username_ +* **Attribute Value:** _john_ + +Now click the **Set Attribute** button. You should now see the values displayed in the table. + +=== How does it work? + +We interact with the standard `HttpSession` in the `SessionServlet` shown below: + +.src/main/java/sample/SessionServlet.java +[source,java] +---- +include::{samples-dir}httpsession-gemfire-clientserver/src/main/java/sample/SessionServlet.java[tags=class] +---- + +Instead of using Tomcat's `HttpSession`, we are actually persisting the values in GemFire. +Spring Session creates a cookie named SESSION in your browser that contains the id of your session. +Go ahead and view the cookies (click for help with https://developer.chrome.com/devtools/docs/resources#cookies[Chrome] +or https://getfirebug.com/wiki/index.php/Cookies_Panel#Cookies_List[Firefox]). + +NOTE: The following instructions assume you have a local GemFire installation. For more information on installation, +see http://gemfire.docs.pivotal.io/docs-gemfire/latest/getting_started/installation/install_intro.html[Installing Pivotal GemFire]. + +If you like, you can easily remove the session using `gfsh`. For example, on a Linux-based system type the following +at the command-line: + + $ gfsh + +Then, enter the following commands in _Gfsh_ ensuring to replace `70002719-3c54-4c20-82c3-e7faa6b718f3` with the value +of your SESSION cookie, or the session ID returned by the GemFire OQL query (which should match): + +.... +gfsh>connect --jmx-manager=localhost[1099] + +gfsh>query --query='SELECT * FROM /ClusteredSpringSessions.keySet' + +Result : true +startCount : 0 +endCount : 20 +Rows : 1 + +Result +------------------------------------ +70002719-3c54-4c20-82c3-e7faa6b718f3 + +NEXT_STEP_NAME : END + +gfsh>remove --region=/ClusteredSpringSessions --key="70002719-3c54-4c20-82c3-e7faa6b718f3" +.... + +NOTE: The _GemFire User Guide_ has more detailed instructions on using http://gemfire.docs.pivotal.io/docs-gemfire/latest/tools_modules/gfsh/chapter_overview.html[gfsh]. + +Now visit the application at http://localhost:8080/ again and observe that the attribute we added is no longer displayed. + +Alternatively, you can wait *30 seconds* for the session to timeout (i.e. expire) and refresh the page. Again, the +attribute we added should no longer be displayed in the table. However, keep in mind, that by refreshing the page, +you will inadvertently create a new (empty) session. If you run the query again, you will also see two session IDs, +the new and the old, since GemFire keeps a "tombstone" of the old session around. diff --git a/docs/src/docs/asciidoc/guides/httpsession-gemfire-clientserver.adoc b/docs/src/docs/asciidoc/guides/httpsession-gemfire-clientserver.adoc new file mode 100644 index 00000000..3aa110de --- /dev/null +++ b/docs/src/docs/asciidoc/guides/httpsession-gemfire-clientserver.adoc @@ -0,0 +1,274 @@ += Spring Session - HttpSession with GemFire Client/Server (Quick Start) +John Blum +:toc: + +This guide describes how to configure Spring Session to transparently leverage Pivotal GemFire to back a web application's +`HttpSession` using Java Configuration. + +NOTE: The completed guide can be found in the <>. + +== Updating Dependencies +Before using Spring Session, you must ensure that the required dependencies are included. +If you are using Maven, include the following `dependencies` in your _pom.xml_: + +.pom.xml +[source,xml] +[subs="verbatim,attributes"] +---- + + + + + org.springframework.session + spring-session-data-gemfire + {spring-session-version} + pom + + + org.springframework + spring-web + {spring-version} + + +---- + +ifeval::["{version-snapshot}" == "true"] +Since we are using a SNAPSHOT version, we need to add the Spring Snapshot Maven Repository. +If you are using Maven, include the following `repository` declaration in your _pom.xml_: + +.pom.xml +[source,xml] +---- + + + + + spring-snapshot + https://repo.spring.io/libs-snapshot + + +---- +endif::[] + +ifeval::["{version-milestone}" == "true"] +Since we are using a Milestone version, we need to add the Spring Milestone Maven Repository. +If you are using Maven, include the following `repository` declaration in your _pom.xml_: + +.pom.xml +[source,xml] +---- + + + + + spring-milestone + https://repo.spring.io/libs-milestone + + +---- +endif::[] + +// tag::config[] +[[httpsession-spring-java-configuration]] +== Spring Java Configuration + +After adding the required dependencies and repository declarations, we can create our Spring configuration. +The Spring configuration is responsible for creating a Servlet Filter that replaces the `HttpSession` +with an implementation backed by Spring Session and GemFire. + +Add the following Spring Configuration: + +[source,java] +---- +include::{samples-dir}httpsession-gemfire-clientserver/src/main/java/sample/ClientConfig.java[tags=class] +---- + +<1> The `@EnableGemFireHttpSession` annotation creates a Spring bean named `springSessionRepositoryFilter` that +implements `Filter`. The filter is what replaces the `HttpSession` with an implementation backed by Spring Session. +In this instance, Spring Session is backed by GemFire. +<2> Next, we register a `Properties` bean that allows us to configure certain aspects of the GemFire client cache +using http://gemfire.docs.pivotal.io/docs-gemfire/reference/topics/gemfire_properties.html[GemFire's System properties]. +<3> Then, we configure a `Pool` of client connections to talk to the GemFire Server in our Client/Server topology. In our +configuration, we have used sensible settings for timeouts, number of connections and so on. Also, the `Pool` has been +configured to connect directly to a server. Learn more about various `Pool` configuration settings from the +http://gemfire.docs.pivotal.io/docs-gemfire/latest/javadocs/japi/com/gemstone/gemfire/cache/client/PoolFactory.html[PoolFactory API]. +<4> After configuring a `Pool`, we create an instance of the GemFire client cache using the GemFire `Properties` +and `Pool` to communicate with the server and perform cache data access operations. +<5> Finally, we include a Spring `BeanPostProcessor` to block the client until our GemFire Server is up and running, +listening for and accepting client connections. + +The `gemfireCacheServerReadyBeanPostProcessor` is necessary in order to coordinate the client and server in +an automated fashion during testing, but unnecessary in situations where the GemFire cluster is already presently +running, such as in production. This `BeanPostProcessor` implements 2 approaches to ensure our server has adequate +time to startup. + +The first approach uses a timed wait, checking at periodic intervals to determine whether a client `Socket` connection +can be made to the server's `CacheServer` endpoint. + +The second approach uses a GemFire http://gemfire.docs.pivotal.io/docs-gemfire/latest/javadocs/japi/com/gemstone/gemfire/management/membership/ClientMembershipListener.html[ClientMembershipListener] +that will be notified when the client has successfully connected to the server. Once a connection has been established, +the listener releases the latch that the `BeanPostProcessor` will wait on (up to the specified timeout) in the +`postProcessAfterInitialization` callback to block the client. Either one of these approaches are sufficient +by themselves, but both are demonstrated here to illustrate how this might work and to give you ideas, or other options +in practice. + +TIP: In typical GemFire deployments, where the cluster includes potentially hundreds of GemFire data nodes (servers), +it is more common for clients to connect to one or more GemFire Locators running in the cluster. A Locator passes meta-data +to clients about the servers available, load and which servers have the client's data of interest, which is particularly +important for single-hop, direct data access. See more details about the http://gemfire.docs.pivotal.io/docs-gemfire/latest/topologies_and_comm/cs_configuration/chapter_overview.html[Client/Server Topology in GemFire's User Guide]. + +NOTE: For more information on configuring _Spring Data GemFire_, refer to the http://docs.spring.io/spring-data-gemfire/docs/current/reference/html/[reference guide]. + +The `@EnableGemFireHttpSession` annotation enables a developer to configure certain aspects of both Spring Session +and GemFire out-of-the-box using the following attributes: + +* `maxInactiveIntervalInSeconds` - controls HttpSession idle-timeout expiration (defaults to **30 minutes**). +* `regionName` - specifies the name of the GemFire Region used to store `HttpSession` state (defaults is "_ClusteredSpringSessions_"). +* `clientRegionShort` - specifies GemFire http://gemfire.docs.pivotal.io/docs-gemfire/latest/developing/management_all_region_types/chapter_overview.html[data management policies] +with a GemFire http://gemfire.docs.pivotal.io/docs-gemfire/latest/javadocs/japi/com/gemstone/gemfire/cache/client/ClientRegionShortcut.html[ClientRegionShortcut] +(default is `PROXY`). + +NOTE: It is important to note that the GemFire client Region name must match a server Region by the same name if +the client Region is a `PROXY` or `CACHING_PROXY`. Names are not required to match if the client Region used to +store Spring Sessions is `LOCAL`, however, keep in mind that your session state will not be propagated to the server +and you lose all benefits of using GemFire to store and manage distributed, replicated session state information +in a cluster. + +NOTE: `serverRegionShort` is ignored in a client/server cache configuration and only applies when a peer-to-peer (P2P) topology, +and more specifically, a GemFire peer cache is used. + +=== Server Configuration + +Now, we have only covered one side of the equation. We also need a GemFire Server for our client to talk to and pass +session state up to the server to manage. + +In this sample, we will use the following GemFire Server Java Configuration: + +[source,java] +---- +include::{samples-dir}httpsession-gemfire-clientserver/src/main/java/sample/ServerConfig.java[tags=class] +---- + +<1> On the server, we also configure Spring Session using the `@EnableGemFireHttpSession` annotation. For one, this +ensures that the Region names on both the client and server match (in this sample, we use the default "_ClusteredSpringSessions_"). +We have also set the session timeout to **30 seconds**. Later, we will see how this timeout is used. +<2> Next, we configure the GemFire Server using GemFire System properties very much like our P2P samples. +With the `mcast-port` set to 0 and no `locators` property specified, our server will be standalone. We also allow a +JMX client (e.g. _Gfsh_) to connect to our server with the use of the GemFire-specific JMX System properties. +<3> Then, we create an instance of the GemFire peer cache using our GemFire System properties. +<4> We also setup a GemFire `CacheServer` instance running on **localhost**, listening on port **12480**, +to accept our client connection. +<5> Finally, we declare a `main` method as an entry point for launching and running our GemFire Server +from the command-line. + +== Java Servlet Container Initialization + +Our <> created a Spring bean named `springSessionRepositoryFilter` +that implements `Filter`. The `springSessionRepositoryFilter` bean is responsible for replacing the `HttpSession` +with a custom implementation backed by Spring Session and GemFire. + +In order for our `Filter` to do its magic, Spring needs to load our `ClientConfig` class. We also need to ensure our +Servlet Container (i.e. Tomcat) uses our `springSessionRepositoryFilter` for every request. Fortunately, Spring Session +provides a utility class named `AbstractHttpSessionApplicationInitializer` to make both of these steps extremely easy. + +You can find an example below: + +.src/main/java/sample/Initializer.java +[source,java] +---- +include::{samples-dir}httpsession-gemfire-clientserver/src/main/java/sample/Initializer.java[tags=class] +---- + +NOTE: The name of our class (`Initializer`) does not matter. What is important is that we extend `AbstractHttpSessionApplicationInitializer`. + +<1> The first step is to extend `AbstractHttpSessionApplicationInitializer`. +This ensures that a Spring bean named `springSessionRepositoryFilter` is registered with our Servlet Container +and used for every request. +<2> `AbstractHttpSessionApplicationInitializer` also provides a mechanism to easily allow Spring to load our `ClientConfig`. +// end::config[] + +[[httpsession-gemfire-clientserver-java-sample-app]] +== HttpSession with GemFire (Client/Server) Sample Application + + +=== Running the httpsession-gemfire-clientserver Sample Application + +You can run the sample by obtaining the {download-url}[source code] and invoking the following commands. + +First, you need to run the server using: + +---- +$ ./gradlew :samples:httpsession-gemfire-clientserver:run [-Dsample.httpsession.gemfire.log-level=info] +---- + +Then, in a separate terminal, you run the client using: + +---- +$ ./gradlew :samples:httpsession-gemfire-clientserver:tomcatRun [-Dsample.httpsession.gemfire.log-level=info] +---- + +You should now be able to access the application at http://localhost:8080/. In this sample, the web application +is the client cache and the server is standalone. + +=== Exploring the httpsession-gemfire-clientserver Sample Application + +Try using the application. Fill out the form with the following information: + +* **Attribute Name:** _username_ +* **Attribute Value:** _john_ + +Now click the **Set Attribute** button. You should now see the values displayed in the table. + +=== How does it work? + +We interact with the standard `HttpSession` in the `SessionServlet` shown below: + +.src/main/java/sample/SessionServlet.java +[source,java] +---- +include::{samples-dir}httpsession-gemfire-clientserver/src/main/java/sample/SessionServlet.java[tags=class] +---- + +Instead of using Tomcat's `HttpSession`, we are actually persisting the values in GemFire. +Spring Session creates a cookie named SESSION in your browser that contains the id of your session. +Go ahead and view the cookies (click for help with https://developer.chrome.com/devtools/docs/resources#cookies[Chrome] +or https://getfirebug.com/wiki/index.php/Cookies_Panel#Cookies_List[Firefox]). + +NOTE: The following instructions assume you have a local GemFire installation. For more information on installation, +see http://gemfire.docs.pivotal.io/docs-gemfire/latest/getting_started/installation/install_intro.html[Installing Pivotal GemFire]. + +If you like, you can easily remove the session using `gfsh`. For example, on a Linux-based system type the following +at the command-line: + + $ gfsh + +Then, enter the following commands in _Gfsh_ ensuring to replace `70002719-3c54-4c20-82c3-e7faa6b718f3` with the value +of your SESSION cookie, or the session ID returned by the GemFire OQL query (which should match): + +.... +gfsh>connect --jmx-manager=localhost[1099] + +gfsh>query --query='SELECT * FROM /ClusteredSpringSessions.keySet' + +Result : true +startCount : 0 +endCount : 20 +Rows : 1 + +Result +------------------------------------ +70002719-3c54-4c20-82c3-e7faa6b718f3 + +NEXT_STEP_NAME : END + +gfsh>remove --region=/ClusteredSpringSessions --key="70002719-3c54-4c20-82c3-e7faa6b718f3" +.... + +NOTE: The _GemFire User Guide_ has more detailed instructions on using http://gemfire.docs.pivotal.io/docs-gemfire/latest/tools_modules/gfsh/chapter_overview.html[gfsh]. + +Now visit the application at http://localhost:8080/ again and observe that the attribute we added is no longer displayed. + +Alternatively, you can wait **30 seconds** for the session to expire and timeout, and then refresh the page. The attribute +we added should no longer be displayed in the table. However, keep in mind, that by refreshing the page, you will inadvertently +create a new (empty) session. If you run the query again, you will also see two session IDs, the new and the old, +since GemFire keeps a "tombstone" of the old session around. diff --git a/docs/src/docs/asciidoc/guides/httpsession-gemfire-p2p-xml.adoc b/docs/src/docs/asciidoc/guides/httpsession-gemfire-p2p-xml.adoc new file mode 100644 index 00000000..7b22274d --- /dev/null +++ b/docs/src/docs/asciidoc/guides/httpsession-gemfire-p2p-xml.adoc @@ -0,0 +1,210 @@ += Spring Session - HttpSession with GemFire P2P using XML (Quick Start) +John Blum, Rob Winch +:toc: + +This guide describes how to configure Pivotal GemFire as a provider in Spring Session to transparently back +a web application's `HttpSession` using XML Configuration. + +NOTE: The completed guide can be found in the <>. + +== Updating Dependencies +Before using Spring Session, you must ensure that the required dependencies are included. +If you are using Maven, include the following `dependencies` in your _pom.xml_: + +.pom.xml +[source,xml] +[subs="verbatim,attributes"] +---- + + + + + org.springframework.session + spring-session-data-gemfire + {spring-session-version} + pom + + + org.springframework + spring-web + {spring-version} + + +---- + +ifeval::["{version-snapshot}" == "true"] +Since we are using a SNAPSHOT version, we need to add the Spring Snapshot Maven Repository. +If you are using Maven, include the following `repository` declaration in your _pom.xml_: + +.pom.xml +[source,xml] +---- + + + + + spring-snapshot + https://repo.spring.io/libs-snapshot + + +---- +endif::[] + +ifeval::["{version-milestone}" == "true"] +Since we are using a Milestone version, we need to add the Spring Milestone Maven Repository. +If you are using Maven, include the following `repository` declaration in your _pom.xml_: + +.pom.xml +[source,xml] +---- + + + + + spring-milestone + https://repo.spring.io/libs-milestone + + +---- +endif::[] + +// tag::config[] +[[httpsession-spring-xml-configuration]] +== Spring XML Configuration + +After adding the required dependencies and repository declarations, we can create our Spring configuration. +The Spring configuration is responsible for creating a Servlet Filter that replaces the `HttpSession` +with an implementation backed by Spring Session and GemFire. + +Add the following Spring Configuration: + +.src/main/webapp/WEB-INF/spring/session.xml +[source,xml,indent=0] +---- +include::{samples-dir}httpsession-gemfire-p2p-xml/src/main/webapp/WEB-INF/spring/session.xml[tags=beans] +---- + +<1> We use the combination of `` and `GemFireHttpSessionConfiguration` because Spring Session +does not yet provide XML Namespace support (see https://github.com/spring-projects/spring-session/issues/104[gh-104]). +This creates a Spring bean with the name of `springSessionRepositoryFilter` that implements `Filter`. The filter is what +replaces the `HttpSession` with an implementation backed by Spring Session. +In this instance, Spring Session is backed by GemFire. +<2> Then, we configure a GemFire peer cache using standard GemFire System properties. We give the GemFire data node +a name using the `name` property and set `mcast-port` to 0. With the absence of a `locators` property, this data node +will be a standalone server. GemFire's `log-level` is set using an application-specific System property +(`sample.httpsession.gemfire.log-level`) that a user can specify on the command-line when running this application +using either Maven or Gradle (default is "_warning_"). +<3> Finally, we create an instance of the GemFire peer cache that embeds GemFire in the same JVM process as the running +Spring Session sample application. + +TIP: Additionally, we have configured this data node (server) as a GemFire Manager as well using GemFire-specific +JMX System properties that enable JMX client (e.g. _Gfsh_) to connect to this running data node. + +NOTE: For more information on configuring _Spring Data GemFire_, refer to the http://docs.spring.io/spring-data-gemfire/docs/current/reference/html/[reference guide]. + +== XML Servlet Container Initialization + +Our <> created a Spring bean named `springSessionRepositoryFilter` +that implements `Filter`. The `springSessionRepositoryFilter` bean is responsible for replacing the `HttpSession` with +a custom implementation that is backed by Spring Session and GemFire. + +In order for our `Filter` to do its magic, we need to instruct Spring to load our `session.xml` configuration file. +We do this with the following configuration: + +.src/main/webapp/WEB-INF/web.xml +[source,xml,indent=0] +---- +include::{samples-dir}httpsession-gemfire-p2p-xml/src/main/webapp/WEB-INF/web.xml[tags=context-param] +include::{samples-dir}httpsession-gemfire-p2p-xml/src/main/webapp/WEB-INF/web.xml[tags=listeners] +---- + +The http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#context-create[ContextLoaderListener] +reads the `contextConfigLocation` context parameter value and picks up our _session.xml_ configuration file. + +Finally, we need to ensure that our Servlet Container (i.e. Tomcat) uses our `springSessionRepositoryFilter` +for every request. + +The following snippet performs this last step for us: + +.src/main/webapp/WEB-INF/web.xml +[source,xml,indent=0] +---- +include::{samples-dir}httpsession-gemfire-p2p-xml/src/main/webapp/WEB-INF/web.xml[tags=springSessionRepositoryFilter] +---- + +The http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/filter/DelegatingFilterProxy.html[DelegatingFilterProxy] +will look up a bean by the name of `springSessionRepositoryFilter` and cast it to a `Filter`. For every request that `DelegatingFilterProxy` +is invoked, the `springSessionRepositoryFilter` will be invoked. +// end::config[] + +[[httpsession-gemfire-p2p-xml-sample-app]] +== HttpSession with GemFire (P2P) using XML Sample Application + + +=== Running the httpsession-gemfire-p2p-xml Sample Application + +You can run the sample by obtaining the {download-url}[source code] and invoking the following command: + +---- +$ ./gradlew :samples:httpsession-gemfire-p2p-xml:tomcatRun [-Dsample.httpsession.gemfire.log-level=info] +---- + +You should now be able to access the application at http://localhost:8080/ + +=== Exploring the httpsession-gemfire-p2p-xml Sample Application + +Try using the application. Fill out the form with the following information: + +* **Attribute Name:** _username_ +* **Attribute Value:** _john_ + +Now click the **Set Attribute** button. You should now see the values displayed in the table. + +=== How does it work? + +We interact with the standard `HttpSession` in the `SessionServlet` shown below: + +.src/main/java/sample/SessionServlet.java +[source,java] +---- +include::{samples-dir}httpsession-gemfire-p2p-xml/src/main/java/sample/SessionServlet.java[tags=class] +---- + +Instead of using Tomcat's `HttpSession`, we are actually persisting the values in GemFire. +Spring Session creates a cookie named SESSION in your browser that contains the id of your session. +Go ahead and view the cookies (click for help with https://developer.chrome.com/devtools/docs/resources#cookies[Chrome] +or https://getfirebug.com/wiki/index.php/Cookies_Panel#Cookies_List[Firefox]). + +NOTE: The following instructions assume you have a local GemFire installation. For more information on installation, +see http://gemfire.docs.pivotal.io/docs-gemfire/latest/getting_started/installation/install_intro.html[Installing Pivotal GemFire]. + +If you like, you can easily remove the session using `gfsh`. For example, on a Linux-based system type the following +at the command-line: + + $ gfsh + +Then, enter the following into _Gfsh_ ensuring to replace `70002719-3c54-4c20-82c3-e7faa6b718f3` with the value +of your SESSION cookie, or the session ID returned by the GemFire OQL query (which should match): + +.... +gfsh>connect --jmx-manager=localhost[1099] + +gfsh>query --query='SELECT * FROM /ClusteredSpringSessions.keySet' + +Result : true +startCount : 0 +endCount : 20 +Rows : 1 + +Result +------------------------------------ +70002719-3c54-4c20-82c3-e7faa6b718f3 + +NEXT_STEP_NAME : END + +gfsh>remove --region=/ClusteredSpringSessions --key="70002719-3c54-4c20-82c3-e7faa6b718f3" +.... + +NOTE: The _GemFire User Guide_ has more detailed instructions on using http://gemfire.docs.pivotal.io/docs-gemfire/latest/tools_modules/gfsh/chapter_overview.html[gfsh]. + +Now visit the application at http://localhost:8080/ and observe that the attribute we added is no longer displayed. diff --git a/docs/src/docs/asciidoc/guides/httpsession-gemfire-p2p.adoc b/docs/src/docs/asciidoc/guides/httpsession-gemfire-p2p.adoc new file mode 100644 index 00000000..c452383f --- /dev/null +++ b/docs/src/docs/asciidoc/guides/httpsession-gemfire-p2p.adoc @@ -0,0 +1,209 @@ += Spring Session - HttpSession with GemFire P2P (Quick Start) +John Blum, Rob Winch +:toc: + +This guide describes how to configure Pivotal GemFire as a provider in Spring Session to transparently back +a web application's `HttpSession` using Java Configuration. + +NOTE: The completed guide can be found in the <>. + +== Updating Dependencies +Before using Spring Session, you must ensure that the required dependencies are included. +If you are using Maven, include the following `dependencies` in your _pom.xml_: + +.pom.xml +[source,xml] +[subs="verbatim,attributes"] +---- + + + + + org.springframework.session + spring-session-data-gemfire + {spring-session-version} + pom + + + org.springframework + spring-web + {spring-version} + + +---- + +ifeval::["{version-snapshot}" == "true"] +Since we are using a SNAPSHOT version, we need to add the Spring Snapshot Maven Repository. +If you are using Maven, include the following `repository` declaration in your _pom.xml_: + +.pom.xml +[source,xml] +---- + + + + + spring-snapshot + https://repo.spring.io/libs-snapshot + + +---- +endif::[] + +ifeval::["{version-milestone}" == "true"] +Since we are using a Milestone version, we need to add the Spring Milestone Maven Repository. +If you are using Maven, include the following `repository` declaration in your _pom.xml_: + +.pom.xml +[source,xml] +---- + + + + + spring-milestone + https://repo.spring.io/libs-milestone + + +---- +endif::[] + +// tag::config[] +[[httpsession-spring-java-configuration]] +== Spring Java Configuration + +After adding the required dependencies and repository declarations, we can create our Spring configuration. +The Spring configuration is responsible for creating a Servlet Filter that replaces the `HttpSession` +with an implementation backed by Spring Session and GemFire. + +Add the following Spring Configuration: + +[source,java] +---- +include::{samples-dir}httpsession-gemfire-p2p/src/main/java/sample/Config.java[tags=class] +---- + +<1> The `@EnableGemFireHttpSession` annotation creates a Spring bean named `springSessionRepositoryFilter` that +implements `Filter`. The filter is what replaces the `HttpSession` with an implementation backed by Spring Session. +In this instance, Spring Session is backed by GemFire. +<2> Then, we configure a GemFire peer cache using standard GemFire System properties. We give the GemFire data node +a name using the `name` property and set `mcast-port` to 0. With the absence of a `locators` property, this data node +will be a standalone server. GemFire's `log-level` is set using an application-specific System property (`sample.httpsession.gemfire.log-level`) +that a user can specify on the command-line when running this sample application using either Maven or Gradle (default is "_warning_"). +<3> Finally, we create an instance of the GemFire peer cache that embeds GemFire in the same JVM process as the running +Spring Session sample application. + +TIP: Additionally, we have configured this data node (server) as a GemFire Manager as well using GemFire-specific +JMX System properties that enable JMX client (e.g. _Gfsh_) to connect to this running data node. + +NOTE: For more information on configuring _Spring Data GemFire_, refer to the http://docs.spring.io/spring-data-gemfire/docs/current/reference/html/[reference guide]. + +The `@EnableGemFireHttpSession` annotation enables a developer to configure certain aspects of Spring Session +and GemFire out-of-the-box using the following attributes: + +* `maxInactiveIntervalInSeconds` - controls HttpSession idle-timeout expiration (defaults to **30 minutes**). +* `regionName` - specifies the name of the GemFire Region used to store `HttpSession` state (defaults is "_ClusteredSpringSessions_"). +* `serverRegionShort` - specifies GemFire http://gemfire.docs.pivotal.io/docs-gemfire/latest/developing/management_all_region_types/chapter_overview.html[data management policies] +with a GemFire http://gemfire.docs.pivotal.io/docs-gemfire/latest/javadocs/japi/com/gemstone/gemfire/cache/RegionShortcut.html[RegionShortcut] +(default is `PARTITION`). + +NOTE: `clientRegionShort` is ignored in a peer cache configuration and only applies when a client-server topology, +and more specifically, a GemFire client cache is used. + +== Java Servlet Container Initialization + +Our <> created a Spring bean named `springSessionRepositoryFilter` +that implements `Filter`. The `springSessionRepositoryFilter` bean is responsible for replacing the `HttpSession` +with a custom implementation backed by Spring Session and GemFire. + +In order for our `Filter` to do its magic, Spring needs to load our `Config` class. We also need to ensure our +Servlet Container (i.e. Tomcat) uses our `springSessionRepositoryFilter` for every request. Fortunately, Spring Session +provides a utility class named `AbstractHttpSessionApplicationInitializer` to make both of these steps extremely easy. + +You can find an example below: + +.src/main/java/sample/Initializer.java +[source,java] +---- +include::{samples-dir}httpsession-gemfire-p2p/src/main/java/sample/Initializer.java[tags=class] +---- + +NOTE: The name of our class (`Initializer`) does not matter. What is important is that we extend `AbstractHttpSessionApplicationInitializer`. + +<1> The first step is to extend `AbstractHttpSessionApplicationInitializer`. +This ensures that a Spring bean named `springSessionRepositoryFilter` is registered with our Servlet Container +and used for every request. +<2> `AbstractHttpSessionApplicationInitializer` also provides a mechanism to easily allow Spring to load our `Config`. +// end::config[] + +[[httpsession-gemfire-p2p-java-sample-app]] +== HttpSession with GemFire (P2P) Sample Application + + +=== Running the httpsession-gemfire-p2p Sample Application + +You can run the sample by obtaining the {download-url}[source code] and invoking the following command: + +---- +$ ./gradlew :samples:httpsession-gemfire-p2p:tomcatRun [-Dsample.httpsession.gemfire.log-level=info] +---- + +You should now be able to access the application at http://localhost:8080/ + +=== Exploring the httpsession-gemfire-p2p Sample Application + +Try using the application. Fill out the form with the following information: + +* **Attribute Name:** _username_ +* **Attribute Value:** _john_ + +Now click the **Set Attribute** button. You should now see the values displayed in the table. + +=== How does it work? + +We interact with the standard `HttpSession` in the `SessionServlet` shown below: + +.src/main/java/sample/SessionServlet.java +[source,java] +---- +include::{samples-dir}httpsession-gemfire-p2p/src/main/java/sample/SessionServlet.java[tags=class] +---- + +Instead of using Tomcat's `HttpSession`, we are actually persisting the values in GemFire. +Spring Session creates a cookie named SESSION in your browser that contains the id of your session. +Go ahead and view the cookies (click for help with https://developer.chrome.com/devtools/docs/resources#cookies[Chrome] +or https://getfirebug.com/wiki/index.php/Cookies_Panel#Cookies_List[Firefox]). + +NOTE: The following instructions assume you have a local GemFire installation. For more information on installation, +see http://gemfire.docs.pivotal.io/docs-gemfire/latest/getting_started/installation/install_intro.html[Installing Pivotal GemFire]. + +If you like, you can easily remove the session using `gfsh`. For example, on a Linux-based system type the following +at the command-line: + + $ gfsh + +Then, enter the following into _Gfsh_ ensuring to replace `70002719-3c54-4c20-82c3-e7faa6b718f3` with the value +of your SESSION cookie, or the session ID returned by the GemFire OQL query (which should match): + +.... +gfsh>connect --jmx-manager=localhost[1099] + +gfsh>query --query='SELECT * FROM /ClusteredSpringSessions.keySet' + +Result : true +startCount : 0 +endCount : 20 +Rows : 1 + +Result +------------------------------------ +70002719-3c54-4c20-82c3-e7faa6b718f3 + +NEXT_STEP_NAME : END + +gfsh>remove --region=/ClusteredSpringSessions --key="70002719-3c54-4c20-82c3-e7faa6b718f3" +.... + +NOTE: The _GemFire User Guide_ has more detailed instructions on using http://gemfire.docs.pivotal.io/docs-gemfire/latest/tools_modules/gfsh/chapter_overview.html[gfsh]. + +Now visit the application at http://localhost:8080/ and observe that the attribute we added is no longer displayed. diff --git a/docs/src/docs/asciidoc/index.adoc b/docs/src/docs/asciidoc/index.adoc index b8c1143d..ab3d29c8 100644 --- a/docs/src/docs/asciidoc/index.adoc +++ b/docs/src/docs/asciidoc/index.adoc @@ -52,6 +52,22 @@ If you are looking to get started with Spring Session, the best place to start i | Demonstrates how to use Spring Session to replace the `HttpSession` with a Redis store using XML based configuration. | link:guides/httpsession-xml.html[HttpSession XML Guide] +| {gh-samples-url}httpsession-gemfire-clientserver[HttpSession with GemFire (Client/Server)] +| Demonstrates how to use Spring Session to replace the `HttpSession` with GemFire using a Client/Server topology. +| link:guides/httpsession-gemfire-clientserver.html[HttpSession GemFire Client/Server Guide] + +| {gh-samples-url}httpsession-gemfire-clientserver-xml[HttpSession with GemFire (Client/Server) using XML] +| Demonstrates how to use Spring Session to replace the `HttpSession` with GemFire using a Client/Server topology configured with XML. +| link:guides/httpsession-gemfire-clientserver-xml.html[HttpSession GemFire Client/Server XML Guide] + +| {gh-samples-url}httpsession-gemfire-p2p[HttpSession with GemFire (P2P)] +| Demonstrates how to use Spring Session to replace the `HttpSession` with GemFire using a P2P topology. +| link:guides/httpsession-gemfire-p2p.html[HttpSession GemFire P2P Guide] + +| {gh-samples-url}httpsession-gemfire-p2p-xml[HttpSession with GemFire (P2P) using XML] +| Demonstrates how to use Spring Session to replace the `HttpSession` with GemFire using a P2P topology configured with XML. +| link:guides/httpsession-gemfire-p2p-xml.html[HttpSession GemFire P2P XML Guide] + | {gh-samples-url}custom-cookie[Custom Cookie] | Demonstrates how to use Spring Session and customize the cookie. | link:guides/custom-cookie.html[Custom Cookie Guide] @@ -136,6 +152,110 @@ You can read the basic steps for integration below, but you are encouraged to fo include::guides/httpsession-xml.adoc[tags=config,leveloffset=+3] +[[httpsession-gemfire]] +=== HttpSession with Pivotal GemFire + +When https://pivotal.io/big-data/pivotal-gemfire[Pivotal GemFire] is used with Spring Session, a web application's +`HttpSession` can be replaced with a **clustered** implementation managed by GemFire and conveniently accessed +with Spring Session's API. + +The two most common topologies to manage Spring Sessions using GemFire include: + +* <> +* <> + +Additionally, GemFire supports site-to-site replication using http://gemfire.docs.pivotal.io/docs-gemfire/topologies_and_comm/multi_site_configuration/chapter_overview.html[WAN functionality]. +The ability to configure and use GemFire's WAN support is independent of Spring Session, and is beyond the scope +of this document. More details on GemFire WAN functionality can be found http://docs.spring.io/spring-data-gemfire/docs/current/reference/html/#bootstrap:gateway[here]. + +[[httpsession-gemfire-clientserver]] +==== GemFire Client-Server + +The http://gemfire.docs.pivotal.io/docs-gemfire/latest/topologies_and_comm/cs_configuration/chapter_overview.html[Client-Server] +topology will probably be the more common configuration preference for users when using GemFire as a provider in +Spring Session since a GemFire server will have significantly different and unique JVM heap requirements when compared +to the application. Using a client-server topology enables an application to manage (e.g. replicate) application state +independently from other application processes. + +In a client-server topology, an application using Spring Session will open a client cache connection to a (remote) +GemFire server cluster to manage and provide consistent access to all `HttpSession` state. + +You can configure a Client-Server topology with either: + +* <> +* <> + +[[httpsession-gemfire-clientserver-java]] +===== GemFire Client-Server Java-based Configuration + +This section describes how to use GemFire's Client-Server topology to back an `HttpSession` with Java-based configuration. + +NOTE: The <> provides a working sample on how to integrate +Spring Session and GemFire to replace the HttpSession using Java configuration. You can read the basic steps for +integration below, but you are encouraged to follow along with the detailed HttpSession with GemFire (Client-Server) +Guide when integrating with your own application. + +include::guides/httpsession-gemfire-clientserver.adoc[tags=config,leveloffset=+3] + +[[http-session-gemfire-clientserver-xml]] +===== GemFire Client-Server XML-based Configuration + +This section describes how to use GemFire's Client-Server topology to back an `HttpSession` with XML-based configuration. + +NOTE: The <> provides a working sample on how to +integrate Spring Session and GemFire to replace the `HttpSession` using XML configuration. You can read the basic steps +for integration below, but you are encouraged to follow along with the detailed HttpSession with GemFire (Client-Server) +using XML Guide when integrating with your own application. + +include::guides/httpsession-gemfire-clientserver-xml.adoc[tags=config,leveloffset=+3] + +[[httpsession-gemfire-p2p]] +==== GemFire Peer-To-Peer (P2P) + +Perhaps less common would be to configure the Spring Session application as a peer member in the GemFire cluster using +the http://gemfire.docs.pivotal.io/docs-gemfire/latest/topologies_and_comm/p2p_configuration/chapter_overview.html[Peer-To-Peer (P2P)] topology. +In this configuration, a Spring Session application would be an actual data node (server) in the GemFire cluster, +and **not** a cache client as before. + +One advantage to this approach is the proximity of the application to the application's state (i.e. it's data). However, +there are other effective means of accomplishing similar data dependent computations, such as using GemFire's +http://gemfire.docs.pivotal.io/docs-gemfire/latest/developing/function_exec/chapter_overview.html[Function Execution]. +Any of GemFire's other http://gemfire.docs.pivotal.io/docs-gemfire/latest/getting_started/product_intro.html[features] +can be used when GemFire is serving as a provider in Spring Session. + +P2P is very useful for both testing purposes as well as smaller, more focused and self-contained applications, +such as those found in a microservices architecture, and will most certainly improve on your application's latency, +throughput and consistency needs. + +You can configure a Peer-To-Peer (P2P) topology with either: + +* <> +* <> + +[[httpsession-gemfire-p2p-java]] +===== GemFire Peer-To-Peer (P2P) Java-based Configuration + +This section describes how to use GemFire's Peer-To-Peer (P2P) topology to back an `HttpSession` using Java-based configuration. + +NOTE: The <> provides a working sample on how to integrate +Spring Session and GemFire to replace the `HttpSession` using Java configuration. You can read the basic steps +for integration below, but you are encouraged to follow along with the detailed HttpSession with GemFire (P2P) Guide +when integrating with your own application. + +include::guides/httpsession-gemfire-p2p.adoc[tags=config,leveloffset=+3] + +[[httpsession-gemfire-p2p-xml]] +===== GemFire Peer-To-Peer (P2P) XML-based Configuration + +This section describes how to use GemFire's Peer-To-Peer (P2P) topology to back an `HttpSession` using XML-based configuration. + +NOTE: The <> provides a working sample on how to integrate +Spring Session and GemFire to replace the `HttpSession` using XML configuration. You can read the basic steps for +integration below, but you are encouraged to follow along with the detailed HttpSession with GemFire (P2P) using XML +Guide when integrating with your own application. + +include::guides/httpsession-gemfire-p2p-xml.adoc[tags=config,leveloffset=+3] + [[httpsession-how]] === How HttpSession Integration Works diff --git a/gradle.properties b/gradle.properties index d747ed4a..b3f00f56 100644 --- a/gradle.properties +++ b/gradle.properties @@ -12,6 +12,7 @@ jspApiVersion=2.0 servletApiVersion=3.0.1 spockVersion=0.7-groovy-2.0 commonsPoolVersion=2.2 +springDataGemFireVersion=1.7.2.RELEASE springDataRedisVersion=1.4.2.RELEASE hazelcastVersion=3.5.1 assertjVersion=2.3.0 \ No newline at end of file diff --git a/samples/httpsession-gemfire-clientserver-xml/build.gradle b/samples/httpsession-gemfire-clientserver-xml/build.gradle new file mode 100644 index 00000000..eeb388c6 --- /dev/null +++ b/samples/httpsession-gemfire-clientserver-xml/build.gradle @@ -0,0 +1,65 @@ +apply from: JAVA_GRADLE +apply from: TOMCAT_7_GRADLE +apply plugin: "application" + +tasks.findByPath("artifactoryPublish")?.enabled = false + +sonarRunner { + skipProject = true +} + +dependencies { + compile project(':spring-session-data-gemfire'), + "org.springframework:spring-web:$springVersion", + jstlDependencies + + providedCompile "javax.servlet:javax.servlet-api:$servletApiVersion" + + testCompile "junit:junit:$junitVersion" + + integrationTestCompile gebDependencies + + integrationTestRuntime "org.springframework.shell:spring-shell:1.0.0.RELEASE" +} + +mainClassName = 'sample.Application' + +def port +def process + +task availablePort() << { + def serverSocket = new ServerSocket(0) + port = serverSocket.localPort + serverSocket.close() +} + +task runGemFireServer(dependsOn: availablePort) << { + println 'STARTING GEMFIRE SERVER...' + + String classpath = sourceSets.main.runtimeClasspath.collect { it }.join(File.pathSeparator) + + String[] commandLine = ['java', '-server', '-ea', + "-Dspring.session.data.gemfire.port=$port", + "-Dsample.httpsession.gemfire.log-level=" + + System.getProperty('sample.httpsession.gemfire.log-level', 'warning'), + '-classpath', classpath, 'sample.Application' ] + + //println commandLine + + process = commandLine.execute() + process.in.close() + process.out.close() + process.err.close() +} + +integrationTest.doLast { + println 'STOPPING GEMFIRE SERVER...' + process?.destroyForcibly() +} + +integrationTomcatRun { + dependsOn runGemFireServer + doFirst { + System.setProperty("spring.session.data.gemfire.port", "$port"); + } +} diff --git a/samples/httpsession-gemfire-clientserver-xml/src/integration-test/groovy/sample/AttributeTests.groovy b/samples/httpsession-gemfire-clientserver-xml/src/integration-test/groovy/sample/AttributeTests.groovy new file mode 100644 index 00000000..a5f85c86 --- /dev/null +++ b/samples/httpsession-gemfire-clientserver-xml/src/integration-test/groovy/sample/AttributeTests.groovy @@ -0,0 +1,45 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * 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. + */ +package sample + +import geb.spock.* +import sample.pages.HomePage; +import spock.lang.Stepwise +import pages.* + +/** + * Tests the CAS sample application using service tickets. + * + * @author Rob Winch + */ +@Stepwise +class AttributeTests extends GebReportingSpec { + def 'first visit no attributes'() { + when: + to HomePage + then: + attributes.empty + } + + def 'create attribute'() { + when: + createAttribute('a','b') + then: + attributes.size() == 1 + attributes[0].name == 'a' + attributes[0].value == 'b' + } +} \ No newline at end of file diff --git a/samples/httpsession-gemfire-clientserver-xml/src/integration-test/groovy/sample/pages/HomePage.groovy b/samples/httpsession-gemfire-clientserver-xml/src/integration-test/groovy/sample/pages/HomePage.groovy new file mode 100644 index 00000000..e087b5c4 --- /dev/null +++ b/samples/httpsession-gemfire-clientserver-xml/src/integration-test/groovy/sample/pages/HomePage.groovy @@ -0,0 +1,45 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * 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. + */ +package sample.pages + +import geb.* + +/** + * The home page + * + * @author Rob Winch + */ +class HomePage extends Page { + static url = '' + static at = { assert driver.title == 'Session Attributes'; true} + static content = { + form { $('form') } + submit { $('input[type=submit]') } + createAttribute(required:false) { name, value -> + form.attributeName = name + form.attributeValue = value + submit.click(HomePage) + } + attributes { moduleList AttributeRow, $("table tr").tail() } + } +} +class AttributeRow extends Module { + static content = { + cell { $("td", it) } + name { cell(0).text() } + value { cell(1).text() } + } +} diff --git a/samples/httpsession-gemfire-clientserver-xml/src/main/java/sample/Application.java b/samples/httpsession-gemfire-clientserver-xml/src/main/java/sample/Application.java new file mode 100644 index 00000000..25fc98e8 --- /dev/null +++ b/samples/httpsession-gemfire-clientserver-xml/src/main/java/sample/Application.java @@ -0,0 +1,31 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * 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. + */ +package sample; + +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.ImportResource; + +// tag::class[] +@Configuration // <1> +@ImportResource("META-INF/spring/session-server.xml") // <2> +public class Application { + + public static void main(final String[] args) { + new AnnotationConfigApplicationContext(Application.class).registerShutdownHook(); + } +} +// tag::end[] diff --git a/samples/httpsession-gemfire-clientserver-xml/src/main/java/sample/GemFireCacheServerReadyBeanPostProcessor.java b/samples/httpsession-gemfire-clientserver-xml/src/main/java/sample/GemFireCacheServerReadyBeanPostProcessor.java new file mode 100644 index 00000000..79d2dc8e --- /dev/null +++ b/samples/httpsession-gemfire-clientserver-xml/src/main/java/sample/GemFireCacheServerReadyBeanPostProcessor.java @@ -0,0 +1,151 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * 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. + */ +package sample; + +import java.io.IOException; +import java.net.Socket; +import java.util.Properties; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import javax.annotation.Resource; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.data.gemfire.client.PoolFactoryBean; +import org.springframework.session.data.gemfire.support.GemFireUtils; +import org.springframework.util.Assert; + +import com.gemstone.gemfire.cache.client.Pool; +import com.gemstone.gemfire.management.membership.ClientMembership; +import com.gemstone.gemfire.management.membership.ClientMembershipEvent; +import com.gemstone.gemfire.management.membership.ClientMembershipListenerAdapter; + +public class GemFireCacheServerReadyBeanPostProcessor implements BeanPostProcessor { + + static final long DEFAULT_WAIT_DURATION = TimeUnit.SECONDS.toMillis(20); + static final long DEFAULT_WAIT_INTERVAL = 500l; + + static final CountDownLatch latch = new CountDownLatch(1); + + static final String DEFAULT_SERVER_HOST = "localhost"; + + @Value("${spring.session.data.gemfire.port:${application.gemfire.client-server.port}}") + int port; + +// tag::class[] + static { + ClientMembership.registerClientMembershipListener(new ClientMembershipListenerAdapter() { + public void memberJoined(final ClientMembershipEvent event) { + if (!event.isClient()) { + latch.countDown(); + } + } + }); + } + + @SuppressWarnings("all") + @Resource(name = "applicationProperties") + private Properties applicationProperties; + + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + if (bean instanceof PoolFactoryBean || bean instanceof Pool) { + String host = getServerHost(DEFAULT_SERVER_HOST); + Assert.isTrue(waitForCacheServerToStart(host, port), String.format( + "GemFire Server failed to start [host: '%1$s', port: %2$d]%n", host, port)); + } + + return bean; + } + + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + if (bean instanceof PoolFactoryBean || bean instanceof Pool) { + try { + latch.await(DEFAULT_WAIT_DURATION, TimeUnit.MILLISECONDS); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + + return bean; + } +// tag::end[] + + interface Condition { + boolean evaluate(); + } + + String getServerHost(String defaultServerHost) { + return applicationProperties.getProperty("application.gemfire.client-server.host", defaultServerHost); + } + + boolean waitForCacheServerToStart(String host, int port) { + return waitForCacheServerToStart(host, port, DEFAULT_WAIT_DURATION); + } + + /* (non-Javadoc) */ + boolean waitForCacheServerToStart(final String host, final int port, long duration) { + return waitOnCondition(new Condition() { + AtomicBoolean connected = new AtomicBoolean(false); + + public boolean evaluate() { + Socket socket = null; + + try { + // NOTE: this code is not intended to be an atomic, compound action (a possible race condition); + // opening another connection (at the expense of using system resources) after connectivity + // has already been established is not detrimental in this use case + if (!connected.get()) { + socket = new Socket(host, port); + connected.set(true); + } + } + catch (IOException ignore) { + } + finally { + GemFireUtils.close(socket); + } + + return connected.get(); + } + }, duration); + } + + @SuppressWarnings("unused") + boolean waitOnCondition(Condition condition) { + return waitOnCondition(condition, DEFAULT_WAIT_DURATION); + } + + @SuppressWarnings("all") + boolean waitOnCondition(Condition condition, long duration) { + final long timeout = (System.currentTimeMillis() + duration); + + try { + while (!condition.evaluate() && System.currentTimeMillis() < timeout) { + synchronized (condition) { + TimeUnit.MILLISECONDS.timedWait(condition, DEFAULT_WAIT_INTERVAL); + } + } + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + + return condition.evaluate(); + } +} diff --git a/samples/httpsession-gemfire-clientserver-xml/src/main/java/sample/SessionServlet.java b/samples/httpsession-gemfire-clientserver-xml/src/main/java/sample/SessionServlet.java new file mode 100644 index 00000000..cac9108c --- /dev/null +++ b/samples/httpsession-gemfire-clientserver-xml/src/main/java/sample/SessionServlet.java @@ -0,0 +1,38 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * 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. + */ +package sample; + +import java.io.IOException; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +// tag::class[] +public class SessionServlet extends HttpServlet { + + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + String attributeName = request.getParameter("attributeName"); + String attributeValue = request.getParameter("attributeValue"); + request.getSession().setAttribute(attributeName, attributeValue); + response.sendRedirect(request.getContextPath() + "/"); + } + + private static final long serialVersionUID = 2878267318695777395L; +} +// tag::end[] diff --git a/samples/httpsession-gemfire-clientserver-xml/src/main/resources/META-INF/spring/application.properties b/samples/httpsession-gemfire-clientserver-xml/src/main/resources/META-INF/spring/application.properties new file mode 100644 index 00000000..1e7e05a6 --- /dev/null +++ b/samples/httpsession-gemfire-clientserver-xml/src/main/resources/META-INF/spring/application.properties @@ -0,0 +1,3 @@ +application.gemfire.client-server.host=localhost +application.gemfire.client-server.port=11235 +application.gemfire.client-server.max-connections=50 diff --git a/samples/httpsession-gemfire-clientserver-xml/src/main/resources/META-INF/spring/session-server.xml b/samples/httpsession-gemfire-clientserver-xml/src/main/resources/META-INF/spring/session-server.xml new file mode 100644 index 00000000..a160945b --- /dev/null +++ b/samples/httpsession-gemfire-clientserver-xml/src/main/resources/META-INF/spring/session-server.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + GemFireClientServerHttpSessionXmlSample + 0 + ${sample.httpsession.gemfire.log-level:warning} + true + true + + + + + + + + + + diff --git a/samples/httpsession-gemfire-clientserver-xml/src/main/webapp/META-INF/MANIFEST.MF b/samples/httpsession-gemfire-clientserver-xml/src/main/webapp/META-INF/MANIFEST.MF new file mode 100644 index 00000000..e69de29b diff --git a/samples/httpsession-gemfire-clientserver-xml/src/main/webapp/WEB-INF/spring/session-client.xml b/samples/httpsession-gemfire-clientserver-xml/src/main/webapp/WEB-INF/spring/session-client.xml new file mode 100644 index 00000000..1621d292 --- /dev/null +++ b/samples/httpsession-gemfire-clientserver-xml/src/main/webapp/WEB-INF/spring/session-client.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + ${sample.httpsession.gemfire.log-level:warning} + + + + + + + + + + + diff --git a/samples/httpsession-gemfire-clientserver-xml/src/main/webapp/WEB-INF/web.xml b/samples/httpsession-gemfire-clientserver-xml/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 00000000..f7022c59 --- /dev/null +++ b/samples/httpsession-gemfire-clientserver-xml/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,55 @@ + + + + + + contextConfigLocation + + /WEB-INF/spring/session-client.xml + + + + + + + springSessionRepositoryFilter + org.springframework.web.filter.DelegatingFilterProxy + + + springSessionRepositoryFilter + /* + + + + + + + + org.springframework.web.context.ContextLoaderListener + + + + + + session + sample.SessionServlet + + + + session + /session + + + + index.jsp + + diff --git a/samples/httpsession-gemfire-clientserver-xml/src/main/webapp/assets/bootstrap.min.css b/samples/httpsession-gemfire-clientserver-xml/src/main/webapp/assets/bootstrap.min.css new file mode 100644 index 00000000..a9f35cee --- /dev/null +++ b/samples/httpsession-gemfire-clientserver-xml/src/main/webapp/assets/bootstrap.min.css @@ -0,0 +1,5 @@ +/*! + * Bootstrap v3.2.0 (http://getbootstrap.com) + * Copyright 2011-2014 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + *//*! normalize.css v3.0.1 | MIT License | git.io/normalize */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background:0 0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}@media print{*{color:#000!important;text-shadow:none!important;background:transparent!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100%!important}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}select{background:#fff!important}.navbar{display:none}.table td,.table th{background-color:#fff!important}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table-bordered th,.table-bordered td{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\2a"}.glyphicon-plus:before{content:"\2b"}.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:before,:after{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}input,button,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#428bca;text-decoration:none}a:hover,a:focus{color:#2a6496;text-decoration:underline}a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.img-responsive,.thumbnail>img,.thumbnail a>img,.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;width:100% \9;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;width:100% \9;max-width:100%;height:auto;padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small,.h1 small,.h2 small,.h3 small,.h4 small,.h5 small,.h6 small,h1 .small,h2 .small,h3 .small,h4 .small,h5 .small,h6 .small,.h1 .small,.h2 .small,.h3 .small,.h4 .small,.h5 .small,.h6 .small{font-weight:400;line-height:1;color:#777}h1,.h1,h2,.h2,h3,.h3{margin-top:20px;margin-bottom:10px}h1 small,.h1 small,h2 small,.h2 small,h3 small,.h3 small,h1 .small,.h1 .small,h2 .small,.h2 .small,h3 .small,.h3 .small{font-size:65%}h4,.h4,h5,.h5,h6,.h6{margin-top:10px;margin-bottom:10px}h4 small,.h4 small,h5 small,.h5 small,h6 small,.h6 small,h4 .small,.h4 .small,h5 .small,.h5 .small,h6 .small,.h6 .small{font-size:75%}h1,.h1{font-size:36px}h2,.h2{font-size:30px}h3,.h3{font-size:24px}h4,.h4{font-size:18px}h5,.h5{font-size:14px}h6,.h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}small,.small{font-size:85%}cite{font-style:normal}mark,.mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#428bca}a.text-primary:hover{color:#3071a9}.text-success{color:#3c763d}a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#428bca}a.bg-primary:hover{background-color:#3071a9}.bg-success{background-color:#dff0d8}a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ul,ol{margin-top:0;margin-bottom:10px}ul ul,ol ul,ul ol,ol ol{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;margin-left:-5px;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dt,dd{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote p:last-child,blockquote ul:last-child,blockquote ol:last-child{margin-bottom:0}blockquote footer,blockquote small,blockquote .small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote footer:before,blockquote small:before,blockquote .small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0}.blockquote-reverse footer:before,blockquote.pull-right footer:before,.blockquote-reverse small:before,blockquote.pull-right small:before,.blockquote-reverse .small:before,blockquote.pull-right .small:before{content:''}.blockquote-reverse footer:after,blockquote.pull-right footer:after,.blockquote-reverse small:after,blockquote.pull-right small:after,.blockquote-reverse .small:after,blockquote.pull-right .small:after{content:'\00A0 \2014'}blockquote:before,blockquote:after{content:""}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.col-xs-1,.col-sm-1,.col-md-1,.col-lg-1,.col-xs-2,.col-sm-2,.col-md-2,.col-lg-2,.col-xs-3,.col-sm-3,.col-md-3,.col-lg-3,.col-xs-4,.col-sm-4,.col-md-4,.col-lg-4,.col-xs-5,.col-sm-5,.col-md-5,.col-lg-5,.col-xs-6,.col-sm-6,.col-md-6,.col-lg-6,.col-xs-7,.col-sm-7,.col-md-7,.col-lg-7,.col-xs-8,.col-sm-8,.col-md-8,.col-lg-8,.col-xs-9,.col-sm-9,.col-md-9,.col-lg-9,.col-xs-10,.col-sm-10,.col-md-10,.col-lg-10,.col-xs-11,.col-sm-11,.col-md-11,.col-lg-11,.col-xs-12,.col-sm-12,.col-md-12,.col-lg-12{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-10,.col-xs-11,.col-xs-12{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11,.col-lg-12{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>thead>tr>th,.table>tbody>tr>th,.table>tfoot>tr>th,.table>thead>tr>td,.table>tbody>tr>td,.table>tfoot>tr>td{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>th,.table>caption+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>td,.table>thead:first-child>tr:first-child>td{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>thead>tr>th,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>tbody>tr>td,.table-condensed>tfoot>tr>td{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>tbody>tr>td,.table-bordered>tfoot>tr>td{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td{border-bottom-width:2px}.table-striped>tbody>tr:nth-child(odd)>td,.table-striped>tbody>tr:nth-child(odd)>th{background-color:#f9f9f9}.table-hover>tbody>tr:hover>td,.table-hover>tbody>tr:hover>th{background-color:#f5f5f5}table col[class*=col-]{position:static;display:table-column;float:none}table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none}.table>thead>tr>td.active,.table>tbody>tr>td.active,.table>tfoot>tr>td.active,.table>thead>tr>th.active,.table>tbody>tr>th.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>tbody>tr.active>td,.table>tfoot>tr.active>td,.table>thead>tr.active>th,.table>tbody>tr.active>th,.table>tfoot>tr.active>th{background-color:#f5f5f5}.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover,.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr.active:hover>th{background-color:#e8e8e8}.table>thead>tr>td.success,.table>tbody>tr>td.success,.table>tfoot>tr>td.success,.table>thead>tr>th.success,.table>tbody>tr>th.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>tbody>tr.success>td,.table>tfoot>tr.success>td,.table>thead>tr.success>th,.table>tbody>tr.success>th,.table>tfoot>tr.success>th{background-color:#dff0d8}.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover,.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr.success:hover>th{background-color:#d0e9c6}.table>thead>tr>td.info,.table>tbody>tr>td.info,.table>tfoot>tr>td.info,.table>thead>tr>th.info,.table>tbody>tr>th.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>tbody>tr.info>td,.table>tfoot>tr.info>td,.table>thead>tr.info>th,.table>tbody>tr.info>th,.table>tfoot>tr.info>th{background-color:#d9edf7}.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover,.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr.info:hover>th{background-color:#c4e3f3}.table>thead>tr>td.warning,.table>tbody>tr>td.warning,.table>tfoot>tr>td.warning,.table>thead>tr>th.warning,.table>tbody>tr>th.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>tbody>tr.warning>td,.table>tfoot>tr.warning>td,.table>thead>tr.warning>th,.table>tbody>tr.warning>th,.table>tfoot>tr.warning>th{background-color:#fcf8e3}.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover,.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr.warning:hover>th{background-color:#faf2cc}.table>thead>tr>td.danger,.table>tbody>tr>td.danger,.table>tfoot>tr>td.danger,.table>thead>tr>th.danger,.table>tbody>tr>th.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>tbody>tr.danger>td,.table>tfoot>tr.danger>td,.table>thead>tr.danger>th,.table>tbody>tr.danger>th,.table>tfoot>tr.danger>th{background-color:#f2dede}.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover,.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr.danger:hover>th{background-color:#ebcccc}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>thead>tr>th,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tfoot>tr>td{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>thead>tr>th:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.table-responsive>.table-bordered>thead>tr>th:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>th,.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>td{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=radio],input[type=checkbox]{margin:4px 0 0;margin-top:1px \9;line-height:normal}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=radio]:focus,input[type=checkbox]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#777;opacity:1}.form-control:-ms-input-placeholder{color:#777}.form-control::-webkit-input-placeholder{color:#777}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{cursor:not-allowed;background-color:#eee;opacity:1}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}input[type=date],input[type=time],input[type=datetime-local],input[type=month]{line-height:34px;line-height:1.42857143 \0}input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}.form-group{margin-bottom:15px}.radio,.checkbox{position:relative;display:block;min-height:20px;margin-top:10px;margin-bottom:10px}.radio label,.checkbox label{padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.radio input[type=radio],.radio-inline input[type=radio],.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox]{position:absolute;margin-top:4px \9;margin-left:-20px}.radio+.radio,.checkbox+.checkbox{margin-top:-5px}.radio-inline,.checkbox-inline{display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.radio-inline+.radio-inline,.checkbox-inline+.checkbox-inline{margin-top:0;margin-left:10px}input[type=radio][disabled],input[type=checkbox][disabled],input[type=radio].disabled,input[type=checkbox].disabled,fieldset[disabled] input[type=radio],fieldset[disabled] input[type=checkbox]{cursor:not-allowed}.radio-inline.disabled,.checkbox-inline.disabled,fieldset[disabled] .radio-inline,fieldset[disabled] .checkbox-inline{cursor:not-allowed}.radio.disabled label,.checkbox.disabled label,fieldset[disabled] .radio label,fieldset[disabled] .checkbox label{cursor:not-allowed}.form-control-static{padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm,.form-horizontal .form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}textarea.input-sm,select[multiple].input-sm{height:auto}.input-lg,.form-horizontal .form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}select.input-lg{height:46px;line-height:46px}textarea.input-lg,select[multiple].input-lg{height:auto}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:25px;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center}.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .help-block,.has-success .control-label,.has-success .radio,.has-success .checkbox,.has-success .radio-inline,.has-success .checkbox-inline{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .help-block,.has-warning .control-label,.has-warning .radio,.has-warning .checkbox,.has-warning .radio-inline,.has-warning .checkbox-inline{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .help-block,.has-error .control-label,.has-error .radio,.has-error .checkbox,.has-error .radio-inline,.has-error .checkbox-inline{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn,.form-inline .input-group .form-control{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .radio,.form-inline .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .radio label,.form-inline .checkbox label{padding-left:0}.form-inline .radio input[type=radio],.form-inline .checkbox input[type=checkbox]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .radio,.form-horizontal .checkbox,.form-horizontal .radio-inline,.form-horizontal .checkbox-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .radio,.form-horizontal .checkbox{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{top:0;right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:14.3px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn:focus,.btn:active:focus,.btn.active:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn:hover,.btn:focus{color:#333;text-decoration:none}.btn:active,.btn.active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{pointer-events:none;cursor:not-allowed;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default:hover,.btn-default:focus,.btn-default:active,.btn-default.active,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default:active,.btn-default.active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled,.btn-default[disabled],fieldset[disabled] .btn-default,.btn-default.disabled:hover,.btn-default[disabled]:hover,fieldset[disabled] .btn-default:hover,.btn-default.disabled:focus,.btn-default[disabled]:focus,fieldset[disabled] .btn-default:focus,.btn-default.disabled:active,.btn-default[disabled]:active,fieldset[disabled] .btn-default:active,.btn-default.disabled.active,.btn-default[disabled].active,fieldset[disabled] .btn-default.active{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#428bca;border-color:#357ebd}.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#3071a9;border-color:#285e8e}.btn-primary:active,.btn-primary.active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled,.btn-primary[disabled],fieldset[disabled] .btn-primary,.btn-primary.disabled:hover,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary:hover,.btn-primary.disabled:focus,.btn-primary[disabled]:focus,fieldset[disabled] .btn-primary:focus,.btn-primary.disabled:active,.btn-primary[disabled]:active,fieldset[disabled] .btn-primary:active,.btn-primary.disabled.active,.btn-primary[disabled].active,fieldset[disabled] .btn-primary.active{background-color:#428bca;border-color:#357ebd}.btn-primary .badge{color:#428bca;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success:active,.btn-success.active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled,.btn-success[disabled],fieldset[disabled] .btn-success,.btn-success.disabled:hover,.btn-success[disabled]:hover,fieldset[disabled] .btn-success:hover,.btn-success.disabled:focus,.btn-success[disabled]:focus,fieldset[disabled] .btn-success:focus,.btn-success.disabled:active,.btn-success[disabled]:active,fieldset[disabled] .btn-success:active,.btn-success.disabled.active,.btn-success[disabled].active,fieldset[disabled] .btn-success.active{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info:active,.btn-info.active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled,.btn-info[disabled],fieldset[disabled] .btn-info,.btn-info.disabled:hover,.btn-info[disabled]:hover,fieldset[disabled] .btn-info:hover,.btn-info.disabled:focus,.btn-info[disabled]:focus,fieldset[disabled] .btn-info:focus,.btn-info.disabled:active,.btn-info[disabled]:active,fieldset[disabled] .btn-info:active,.btn-info.disabled.active,.btn-info[disabled].active,fieldset[disabled] .btn-info.active{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning:hover,.btn-warning:focus,.btn-warning:active,.btn-warning.active,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning:active,.btn-warning.active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled,.btn-warning[disabled],fieldset[disabled] .btn-warning,.btn-warning.disabled:hover,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning:hover,.btn-warning.disabled:focus,.btn-warning[disabled]:focus,fieldset[disabled] .btn-warning:focus,.btn-warning.disabled:active,.btn-warning[disabled]:active,fieldset[disabled] .btn-warning:active,.btn-warning.disabled.active,.btn-warning[disabled].active,fieldset[disabled] .btn-warning.active{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger:hover,.btn-danger:focus,.btn-danger:active,.btn-danger.active,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger:active,.btn-danger.active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled,.btn-danger[disabled],fieldset[disabled] .btn-danger,.btn-danger.disabled:hover,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger:hover,.btn-danger.disabled:focus,.btn-danger[disabled]:focus,fieldset[disabled] .btn-danger:focus,.btn-danger.disabled:active,.btn-danger[disabled]:active,fieldset[disabled] .btn-danger:active,.btn-danger.disabled.active,.btn-danger[disabled].active,fieldset[disabled] .btn-danger.active{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#428bca;cursor:pointer;border-radius:0}.btn-link,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:hover,.btn-link:focus,.btn-link:active{border-color:transparent}.btn-link:hover,.btn-link:focus{color:#2a6496;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,fieldset[disabled] .btn-link:hover,.btn-link[disabled]:focus,fieldset[disabled] .btn-link:focus{color:#777;text-decoration:none}.btn-lg,.btn-group-lg>.btn{padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}.btn-sm,.btn-group-sm>.btn{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-xs,.btn-group-xs>.btn{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=submit].btn-block,input[type=reset].btn-block,input[type=button].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition:height .35s ease;-o-transition:height .35s ease;transition:height .35s ease}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px solid;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#fff;text-decoration:none;background-color:#428bca;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#777}.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px solid}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;float:left}.btn-group>.btn:hover,.btn-group-vertical>.btn:hover,.btn-group>.btn:focus,.btn-group-vertical>.btn:focus,.btn-group>.btn:active,.btn-group-vertical>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn.active{z-index:2}.btn-group>.btn:focus,.btn-group-vertical>.btn:focus{outline:0}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child>.btn:last-child,.btn-group>.btn-group:first-child>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn>input[type=radio],[data-toggle=buttons]>.btn>input[type=checkbox]{position:absolute;z-index:-1;filter:alpha(opacity=0);opacity:0}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn,select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn,select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn{height:auto}.input-group-addon,.input-group-btn,.input-group .form-control{display:table-cell}.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child),.input-group .form-control:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=radio],.input-group-addon input[type=checkbox]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group-btn:last-child>.btn-group:not(:last-child)>.btn{border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:first-child>.btn-group:not(:first-child)>.btn{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:hover,.input-group-btn>.btn:focus,.input-group-btn>.btn:active{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:hover,.nav>li.disabled>a:focus{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{background-color:#eee;border-color:#428bca}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:hover,.nav-pills>li.active>a:focus{color:#fff;background-color:#428bca}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;-webkit-overflow-scrolling:touch;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:340px}@media (max-width:480px) and (orientation:landscape){.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:200px}}.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030;-webkit-transform:translate3d(0,0,0);-o-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}@media (min-width:768px){.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:hover,.navbar-brand:focus{text-decoration:none}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu>li>a,.navbar-nav .open .dropdown-menu .dropdown-header{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:hover,.navbar-nav .open .dropdown-menu>li>a:focus{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}.navbar-nav.navbar-right:last-child{margin-right:-15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1)}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn,.navbar-form .input-group .form-control{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .radio,.navbar-form .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .radio label,.navbar-form .checkbox label{padding-left:0}.navbar-form .radio input[type=radio],.navbar-form .checkbox input[type=checkbox]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-form.navbar-right:last-child{margin-right:-15px}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}.navbar-text.navbar-right:last-child{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:hover,.navbar-default .navbar-brand:focus{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:hover,.navbar-default .navbar-nav>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:hover,.navbar-default .navbar-nav>.disabled>a:focus{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:hover,.navbar-default .navbar-toggle:focus{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:hover,.navbar-default .navbar-nav>.open>a:focus{color:#555;background-color:#e7e7e7}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:hover,.navbar-default .btn-link:focus{color:#333}.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:hover,.navbar-default .btn-link[disabled]:focus,fieldset[disabled] .navbar-default .btn-link:focus{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#777}.navbar-inverse .navbar-brand:hover,.navbar-inverse .navbar-brand:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#777}.navbar-inverse .navbar-nav>li>a{color:#777}.navbar-inverse .navbar-nav>li>a:hover,.navbar-inverse .navbar-nav>li>a:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:hover,.navbar-inverse .navbar-nav>.active>a:focus{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:hover,.navbar-inverse .navbar-nav>.disabled>a:focus{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:hover,.navbar-inverse .navbar-toggle:focus{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:hover,.navbar-inverse .navbar-nav>.open>a:focus{color:#fff;background-color:#080808}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#777}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#777}.navbar-inverse .btn-link:hover,.navbar-inverse .btn-link:focus{color:#fff}.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:hover,.navbar-inverse .btn-link[disabled]:focus,fieldset[disabled] .navbar-inverse .btn-link:focus{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#428bca;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:hover,.pagination>li>span:hover,.pagination>li>a:focus,.pagination>li>span:focus{color:#2a6496;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>span,.pagination>.active>a:hover,.pagination>.active>span:hover,.pagination>.active>a:focus,.pagination>.active>span:focus{z-index:2;color:#fff;cursor:default;background-color:#428bca;border-color:#428bca}.pagination>.disabled>span,.pagination>.disabled>span:hover,.pagination>.disabled>span:focus,.pagination>.disabled>a,.pagination>.disabled>a:hover,.pagination>.disabled>a:focus{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:hover,a.label:focus{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:hover,.label-default[href]:focus{background-color:#5e5e5e}.label-primary{background-color:#428bca}.label-primary[href]:hover,.label-primary[href]:focus{background-color:#3071a9}.label-success{background-color:#5cb85c}.label-success[href]:hover,.label-success[href]:focus{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:hover,.label-info[href]:focus{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:hover,.label-warning[href]:focus{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:hover,.label-danger[href]:focus{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-xs .badge{top:0;padding:1px 5px}a.badge:hover,a.badge:focus{color:#fff;text-decoration:none;cursor:pointer}a.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#428bca;background-color:#fff}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding:30px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron h1,.jumbotron .h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron{border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron{padding-right:60px;padding-left:60px}.jumbotron h1,.jumbotron .h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.thumbnail>img,.thumbnail a>img{margin-right:auto;margin-left:auto}a.thumbnail:hover,a.thumbnail:focus,a.thumbnail.active{border-color:#428bca}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#428bca;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-striped .progress-bar,.progress-bar-striped{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress.active .progress-bar,.progress-bar.active{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar[aria-valuenow="1"],.progress-bar[aria-valuenow="2"]{min-width:30px}.progress-bar[aria-valuenow="0"]{min-width:30px;color:#777;background-color:transparent;background-image:none;-webkit-box-shadow:none;box-shadow:none}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media,.media-body{overflow:hidden;zoom:1}.media,.media .media{margin-top:15px}.media:first-child{margin-top:0}.media-object{display:block}.media-heading{margin:0 0 5px}.media>.pull-left{margin-right:10px}.media>.pull-right{margin-left:10px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}a.list-group-item{color:#555}a.list-group-item .list-group-item-heading{color:#333}a.list-group-item:hover,a.list-group-item:focus{color:#555;text-decoration:none;background-color:#f5f5f5}.list-group-item.disabled,.list-group-item.disabled:hover,.list-group-item.disabled:focus{color:#777;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{z-index:2;color:#fff;background-color:#428bca;border-color:#428bca}.list-group-item.active .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>.small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:hover .list-group-item-text,.list-group-item.active:focus .list-group-item-text{color:#e1edf7}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:hover,a.list-group-item-success:focus{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:hover,a.list-group-item-success.active:focus{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:hover,a.list-group-item-info:focus{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:hover,a.list-group-item-info.active:focus{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:hover,a.list-group-item-warning:focus{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:hover,a.list-group-item-warning.active:focus{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:hover,a.list-group-item-danger:focus{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:hover,a.list-group-item-danger.active:focus{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group{margin-bottom:0}.panel>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.table,.panel>.table-responsive>.table,.panel>.panel-collapse>.table{margin-bottom:0}.panel>.table:first-child,.panel>.table-responsive:first-child>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table:last-child,.panel>.table-responsive:last-child>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child th,.panel>.table>tbody:first-child>tr:first-child td{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#428bca}.panel-primary>.panel-heading{color:#fff;background-color:#428bca;border-color:#428bca}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#428bca}.panel-primary>.panel-heading .badge{color:#428bca;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#428bca}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive iframe,.embed-responsive embed,.embed-responsive object{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.close:hover,.close:focus{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:translate3d(0,-25%,0);-o-transform:translate3d(0,-25%,0);transform:translate3d(0,-25%,0)}.modal.in .modal-dialog{-webkit-transform:translate3d(0,0,0);-o-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5)}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}.modal-header{min-height:16.43px;padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-size:12px;line-height:1.4;visibility:visible;filter:alpha(opacity=0);opacity:0}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{bottom:0;left:5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{right:5px;bottom:0;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;left:5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;right:5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;text-align:left;white-space:normal;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2)}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;font-weight:400;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{line-height:1}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:hover,.carousel-control:focus{color:#fff;text-decoration:none;filter:alpha(opacity=90);outline:0;opacity:.9}.carousel-control .icon-prev,.carousel-control .icon-next,.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right{position:absolute;top:50%;z-index:5;display:inline-block}.carousel-control .icon-prev,.carousel-control .glyphicon-chevron-left{left:50%;margin-left:-10px}.carousel-control .icon-next,.carousel-control .glyphicon-chevron-right{right:50%;margin-right:-10px}.carousel-control .icon-prev,.carousel-control .icon-next{width:20px;height:20px;margin-top:-10px;font-family:serif}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000 \9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-prev,.carousel-control .icon-next{width:30px;height:30px;margin-top:-15px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-15px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-15px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.clearfix:before,.clearfix:after,.dl-horizontal dd:before,.dl-horizontal dd:after,.container:before,.container:after,.container-fluid:before,.container-fluid:after,.row:before,.row:after,.form-horizontal .form-group:before,.form-horizontal .form-group:after,.btn-toolbar:before,.btn-toolbar:after,.btn-group-vertical>.btn-group:before,.btn-group-vertical>.btn-group:after,.nav:before,.nav:after,.navbar:before,.navbar:after,.navbar-header:before,.navbar-header:after,.navbar-collapse:before,.navbar-collapse:after,.pager:before,.pager:after,.panel-body:before,.panel-body:after,.modal-footer:before,.modal-footer:after{display:table;content:" "}.clearfix:after,.dl-horizontal dd:after,.container:after,.container-fluid:after,.row:after,.form-horizontal .form-group:after,.btn-toolbar:after,.btn-group-vertical>.btn-group:after,.nav:after,.navbar:after,.navbar-header:after,.navbar-collapse:after,.pager:after,.panel-body:after,.modal-footer:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important;visibility:hidden!important}.affix{position:fixed;-webkit-transform:translate3d(0,0,0);-o-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}@-ms-viewport{width:device-width}.visible-xs,.visible-sm,.visible-md,.visible-lg{display:none!important}.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table}tr.visible-xs{display:table-row!important}th.visible-xs,td.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table}tr.visible-sm{display:table-row!important}th.visible-sm,td.visible-sm{display:table-cell!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table}tr.visible-md{display:table-row!important}th.visible-md,td.visible-md{display:table-cell!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table}tr.visible-lg{display:table-row!important}th.visible-lg,td.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table}tr.visible-print{display:table-row!important}th.visible-print,td.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}} \ No newline at end of file diff --git a/samples/httpsession-gemfire-clientserver-xml/src/main/webapp/index.jsp b/samples/httpsession-gemfire-clientserver-xml/src/main/webapp/index.jsp new file mode 100644 index 00000000..b178e5e8 --- /dev/null +++ b/samples/httpsession-gemfire-clientserver-xml/src/main/webapp/index.jsp @@ -0,0 +1,48 @@ +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> + + + + Session Attributes + + + + +
+

Description

+

This application demonstrates how to use a GemFire instance to back your session. Notice that there is no JSESSIONID cookie. We are also able to customize the way of identifying what the requested session id is.

+ +

Try it

+ +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + +
Attribute NameAttribute Value
+
+ + diff --git a/samples/httpsession-gemfire-clientserver/build.gradle b/samples/httpsession-gemfire-clientserver/build.gradle new file mode 100644 index 00000000..7d968c7b --- /dev/null +++ b/samples/httpsession-gemfire-clientserver/build.gradle @@ -0,0 +1,66 @@ +apply from: JAVA_GRADLE +apply from: TOMCAT_7_GRADLE +apply plugin: "application" + +tasks.findByPath("artifactoryPublish")?.enabled = false + +sonarRunner { + skipProject = true +} + +dependencies { + compile project(':spring-session-data-gemfire'), + "org.springframework:spring-web:$springVersion", + jstlDependencies + + providedCompile "javax.servlet:javax.servlet-api:$servletApiVersion" + + testCompile "junit:junit:$junitVersion" + + integrationTestCompile gebDependencies + + integrationTestRuntime "org.springframework.shell:spring-shell:1.0.0.RELEASE" +} + + +def port +def process + +mainClassName = "sample.ServerConfig" + +task availablePort() << { + def serverSocket = new ServerSocket(0) + port = serverSocket.localPort + serverSocket.close() +} + +task runGemFireServer(dependsOn: availablePort) << { + println 'STARTING GEMFIRE SERVER...' + + String classpath = sourceSets.main.runtimeClasspath.collect { it }.join(File.pathSeparator) + + String[] commandLine = ['java', '-server', '-ea', + "-Dspring.session.data.gemfire.port=$port", + "-Dsample.httpsession.gemfire.log-level=" + + System.getProperty('sample.httpsession.gemfire.log-level', 'warning'), + '-classpath', classpath, 'sample.ServerConfig'] + + //println commandLine + + process = commandLine.execute() + process.in.close() + process.out.close() + process.err.close() +} + +integrationTest.doLast { + println 'STOPPING GEMFIRE SERVER...' + process?.destroyForcibly() +} + +integrationTomcatRun { + dependsOn runGemFireServer + doFirst { + System.setProperty("spring.session.data.gemfire.port", "$port"); + } +} diff --git a/samples/httpsession-gemfire-clientserver/src/integration-test/groovy/sample/AttributeTests.groovy b/samples/httpsession-gemfire-clientserver/src/integration-test/groovy/sample/AttributeTests.groovy new file mode 100644 index 00000000..a5f85c86 --- /dev/null +++ b/samples/httpsession-gemfire-clientserver/src/integration-test/groovy/sample/AttributeTests.groovy @@ -0,0 +1,45 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * 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. + */ +package sample + +import geb.spock.* +import sample.pages.HomePage; +import spock.lang.Stepwise +import pages.* + +/** + * Tests the CAS sample application using service tickets. + * + * @author Rob Winch + */ +@Stepwise +class AttributeTests extends GebReportingSpec { + def 'first visit no attributes'() { + when: + to HomePage + then: + attributes.empty + } + + def 'create attribute'() { + when: + createAttribute('a','b') + then: + attributes.size() == 1 + attributes[0].name == 'a' + attributes[0].value == 'b' + } +} \ No newline at end of file diff --git a/samples/httpsession-gemfire-clientserver/src/integration-test/groovy/sample/pages/HomePage.groovy b/samples/httpsession-gemfire-clientserver/src/integration-test/groovy/sample/pages/HomePage.groovy new file mode 100644 index 00000000..e087b5c4 --- /dev/null +++ b/samples/httpsession-gemfire-clientserver/src/integration-test/groovy/sample/pages/HomePage.groovy @@ -0,0 +1,45 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * 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. + */ +package sample.pages + +import geb.* + +/** + * The home page + * + * @author Rob Winch + */ +class HomePage extends Page { + static url = '' + static at = { assert driver.title == 'Session Attributes'; true} + static content = { + form { $('form') } + submit { $('input[type=submit]') } + createAttribute(required:false) { name, value -> + form.attributeName = name + form.attributeValue = value + submit.click(HomePage) + } + attributes { moduleList AttributeRow, $("table tr").tail() } + } +} +class AttributeRow extends Module { + static content = { + cell { $("td", it) } + name { cell(0).text() } + value { cell(1).text() } + } +} diff --git a/samples/httpsession-gemfire-clientserver/src/main/java/sample/ClientConfig.java b/samples/httpsession-gemfire-clientserver/src/main/java/sample/ClientConfig.java new file mode 100644 index 00000000..32d4d0ec --- /dev/null +++ b/samples/httpsession-gemfire-clientserver/src/main/java/sample/ClientConfig.java @@ -0,0 +1,204 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * 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. + */ +package sample; + +import java.io.IOException; +import java.net.Socket; +import java.util.Collections; +import java.util.Properties; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; +import org.springframework.data.gemfire.client.ClientCacheFactoryBean; +import org.springframework.data.gemfire.client.PoolFactoryBean; +import org.springframework.data.gemfire.config.GemfireConstants; +import org.springframework.data.gemfire.support.ConnectionEndpoint; +import org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession; +import org.springframework.session.data.gemfire.support.GemFireUtils; +import org.springframework.util.Assert; + +import com.gemstone.gemfire.cache.client.Pool; +import com.gemstone.gemfire.management.membership.ClientMembership; +import com.gemstone.gemfire.management.membership.ClientMembershipEvent; +import com.gemstone.gemfire.management.membership.ClientMembershipListenerAdapter; + +// tag::class[] +@EnableGemFireHttpSession(maxInactiveIntervalInSeconds = 30) // <1> +public class ClientConfig { + + static final long DEFAULT_WAIT_DURATION = TimeUnit.SECONDS.toMillis(20); + static final long DEFAULT_WAIT_INTERVAL = 500l; + + static final CountDownLatch latch = new CountDownLatch(1); + + static { + System.setProperty("gemfire.log-level", + System.getProperty("sample.httpsession.gemfire.log-level", "warning")); + + ClientMembership.registerClientMembershipListener( + new ClientMembershipListenerAdapter() { + public void memberJoined(ClientMembershipEvent event) { + if (!event.isClient()) { + latch.countDown(); + } + } + }); + } + + @Bean + PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() { + return new PropertySourcesPlaceholderConfigurer(); + } + + @Bean + Properties gemfireProperties() { // <2> + return new Properties(); + } + + @Bean(name = GemfireConstants.DEFAULT_GEMFIRE_POOL_NAME) + PoolFactoryBean gemfirePool( // <3> + @Value("${spring.session.data.gemfire.port:"+ServerConfig.SERVER_PORT+"}") int port) { + + PoolFactoryBean poolFactory = new PoolFactoryBean(); + + poolFactory.setName(GemfireConstants.DEFAULT_GEMFIRE_POOL_NAME); + poolFactory.setFreeConnectionTimeout(5000); // 5 seconds + poolFactory.setKeepAlive(false); + poolFactory.setMaxConnections(ServerConfig.MAX_CONNECTIONS); + poolFactory.setPingInterval(TimeUnit.SECONDS.toMillis(5)); + poolFactory.setReadTimeout(2000); // 2 seconds + poolFactory.setRetryAttempts(2); + poolFactory.setSubscriptionEnabled(true); + poolFactory.setThreadLocalConnections(false); + + poolFactory.setServerEndpoints(Collections.singletonList(new ConnectionEndpoint( + ServerConfig.SERVER_HOSTNAME, port))); + + return poolFactory; + } + + @Bean + ClientCacheFactoryBean gemfireCache(Pool gemfirePool) { // <4> + ClientCacheFactoryBean clientCacheFactory = new ClientCacheFactoryBean(); + + clientCacheFactory.setClose(true); + clientCacheFactory.setProperties(gemfireProperties()); + clientCacheFactory.setPool(gemfirePool); + clientCacheFactory.setUseBeanFactoryLocator(false); + + return clientCacheFactory; + } + + @Bean + BeanPostProcessor gemfireCacheServerReadyBeanPostProcessor( // <5> + @Value("${spring.session.data.gemfire.port:"+ServerConfig.SERVER_PORT+"}") final int port) { + + return new BeanPostProcessor() { + + public Object postProcessBeforeInitialization( + Object bean, String beanName) throws BeansException { + if (bean instanceof PoolFactoryBean || bean instanceof Pool) { + Assert.isTrue(waitForCacheServerToStart(ServerConfig.SERVER_HOSTNAME, port), + String.format("GemFire Server failed to start [hostname: %1$s, port: %2$d]", + ServerConfig.SERVER_HOSTNAME, port)); + } + + return bean; + } + + public Object postProcessAfterInitialization( + Object bean, String beanName) throws BeansException { + if (bean instanceof PoolFactoryBean || bean instanceof Pool) { + try { + latch.await(DEFAULT_WAIT_DURATION, + TimeUnit.MILLISECONDS); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + + return bean; + } + }; + } +// end::class[] + + interface Condition { + boolean evaluate(); + } + + boolean waitForCacheServerToStart(String host, int port) { + return waitForCacheServerToStart(host, port, DEFAULT_WAIT_DURATION); + } + + /* (non-Javadoc) */ + boolean waitForCacheServerToStart(final String host, final int port, long duration) { + return waitOnCondition(new Condition() { + AtomicBoolean connected = new AtomicBoolean(false); + + public boolean evaluate() { + Socket socket = null; + + try { + // NOTE: this code is not intended to be an atomic, compound action (a possible race condition); + // opening another connection (at the expense of using system resources) after connectivity + // has already been established is not detrimental in this use case + if (!connected.get()) { + socket = new Socket(host, port); + connected.set(true); + } + } + catch (IOException ignore) { + } + finally { + GemFireUtils.close(socket); + } + + return connected.get(); + } + }, duration); + } + + @SuppressWarnings("unused") + boolean waitOnCondition(Condition condition) { + return waitOnCondition(condition, DEFAULT_WAIT_DURATION); + } + + @SuppressWarnings("all") + boolean waitOnCondition(Condition condition, long duration) { + final long timeout = (System.currentTimeMillis() + duration); + + try { + while (!condition.evaluate() && System.currentTimeMillis() < timeout) { + synchronized (condition) { + TimeUnit.MILLISECONDS.timedWait(condition, DEFAULT_WAIT_INTERVAL); + } + } + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + + return condition.evaluate(); + } +} diff --git a/samples/httpsession-gemfire-clientserver/src/main/java/sample/Initializer.java b/samples/httpsession-gemfire-clientserver/src/main/java/sample/Initializer.java new file mode 100644 index 00000000..3690e0a3 --- /dev/null +++ b/samples/httpsession-gemfire-clientserver/src/main/java/sample/Initializer.java @@ -0,0 +1,28 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * 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. + */ +package sample; + +import org.springframework.session.web.context.AbstractHttpSessionApplicationInitializer; + +// tag::class[] +public class Initializer + extends AbstractHttpSessionApplicationInitializer { // <1> + + public Initializer() { + super(ClientConfig.class); // <2> + } +} +// end::class[] diff --git a/samples/httpsession-gemfire-clientserver/src/main/java/sample/ServerConfig.java b/samples/httpsession-gemfire-clientserver/src/main/java/sample/ServerConfig.java new file mode 100644 index 00000000..11a465b2 --- /dev/null +++ b/samples/httpsession-gemfire-clientserver/src/main/java/sample/ServerConfig.java @@ -0,0 +1,89 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * 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. + */ +package sample; + +import java.io.IOException; +import java.util.Properties; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; +import org.springframework.data.gemfire.CacheFactoryBean; +import org.springframework.data.gemfire.server.CacheServerFactoryBean; +import org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession; + +import com.gemstone.gemfire.cache.Cache; + +// tag::class[] +@EnableGemFireHttpSession(maxInactiveIntervalInSeconds = 30)// <1> +public class ServerConfig { + + static final int MAX_CONNECTIONS = 50; + static final int SERVER_PORT = 12480; + + static final String SERVER_HOSTNAME = "localhost"; + + @Bean + PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() { + return new PropertySourcesPlaceholderConfigurer(); + } + + @Bean + Properties gemfireProperties() { // <2> + Properties gemfireProperties = new Properties(); + + gemfireProperties.setProperty("name", "GemFireClientServerHttpSessionSample"); + gemfireProperties.setProperty("mcast-port", "0"); + gemfireProperties.setProperty("log-level", + System.getProperty("sample.httpsession.gemfire.log-level", "warning")); + gemfireProperties.setProperty("jmx-manager", "true"); + gemfireProperties.setProperty("jmx-manager-start", "true"); + + return gemfireProperties; + } + + @Bean + CacheFactoryBean gemfireCache() { // <3> + CacheFactoryBean gemfireCache = new CacheFactoryBean(); + + gemfireCache.setProperties(gemfireProperties()); + gemfireCache.setUseBeanFactoryLocator(false); + + return gemfireCache; + } + + @Bean + CacheServerFactoryBean gemfireCacheServer(Cache gemfireCache, // <4> + @Value("${spring.session.data.gemfire.port:"+SERVER_PORT+"}") int port) { + + CacheServerFactoryBean cacheServerFactory = new CacheServerFactoryBean(); + + cacheServerFactory.setAutoStartup(true); + cacheServerFactory.setBindAddress(SERVER_HOSTNAME); + cacheServerFactory.setCache(gemfireCache); + cacheServerFactory.setMaxConnections(MAX_CONNECTIONS); + cacheServerFactory.setPort(port); + + return cacheServerFactory; + } + + public static void main(final String[] args) throws IOException { // <5> + new AnnotationConfigApplicationContext(ServerConfig.class) + .registerShutdownHook(); + } +} +// end::class[] diff --git a/samples/httpsession-gemfire-clientserver/src/main/java/sample/SessionServlet.java b/samples/httpsession-gemfire-clientserver/src/main/java/sample/SessionServlet.java new file mode 100644 index 00000000..07b22e04 --- /dev/null +++ b/samples/httpsession-gemfire-clientserver/src/main/java/sample/SessionServlet.java @@ -0,0 +1,40 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * 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. + */ +package sample; + +import java.io.IOException; +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +// tag::class[] +@WebServlet("/session") +public class SessionServlet extends HttpServlet { + + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + String attributeName = request.getParameter("attributeName"); + String attributeValue = request.getParameter("attributeValue"); + request.getSession().setAttribute(attributeName, attributeValue); + response.sendRedirect(request.getContextPath() + "/"); + } + + private static final long serialVersionUID = 2878267318695777395L; +} +// tag::end[] diff --git a/samples/httpsession-gemfire-clientserver/src/main/webapp/META-INF/MANIFEST.MF b/samples/httpsession-gemfire-clientserver/src/main/webapp/META-INF/MANIFEST.MF new file mode 100644 index 00000000..e69de29b diff --git a/samples/httpsession-gemfire-clientserver/src/main/webapp/assets/bootstrap.min.css b/samples/httpsession-gemfire-clientserver/src/main/webapp/assets/bootstrap.min.css new file mode 100644 index 00000000..a9f35cee --- /dev/null +++ b/samples/httpsession-gemfire-clientserver/src/main/webapp/assets/bootstrap.min.css @@ -0,0 +1,5 @@ +/*! + * Bootstrap v3.2.0 (http://getbootstrap.com) + * Copyright 2011-2014 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + *//*! normalize.css v3.0.1 | MIT License | git.io/normalize */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background:0 0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}@media print{*{color:#000!important;text-shadow:none!important;background:transparent!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100%!important}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}select{background:#fff!important}.navbar{display:none}.table td,.table th{background-color:#fff!important}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table-bordered th,.table-bordered td{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\2a"}.glyphicon-plus:before{content:"\2b"}.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:before,:after{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}input,button,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#428bca;text-decoration:none}a:hover,a:focus{color:#2a6496;text-decoration:underline}a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.img-responsive,.thumbnail>img,.thumbnail a>img,.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;width:100% \9;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;width:100% \9;max-width:100%;height:auto;padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small,.h1 small,.h2 small,.h3 small,.h4 small,.h5 small,.h6 small,h1 .small,h2 .small,h3 .small,h4 .small,h5 .small,h6 .small,.h1 .small,.h2 .small,.h3 .small,.h4 .small,.h5 .small,.h6 .small{font-weight:400;line-height:1;color:#777}h1,.h1,h2,.h2,h3,.h3{margin-top:20px;margin-bottom:10px}h1 small,.h1 small,h2 small,.h2 small,h3 small,.h3 small,h1 .small,.h1 .small,h2 .small,.h2 .small,h3 .small,.h3 .small{font-size:65%}h4,.h4,h5,.h5,h6,.h6{margin-top:10px;margin-bottom:10px}h4 small,.h4 small,h5 small,.h5 small,h6 small,.h6 small,h4 .small,.h4 .small,h5 .small,.h5 .small,h6 .small,.h6 .small{font-size:75%}h1,.h1{font-size:36px}h2,.h2{font-size:30px}h3,.h3{font-size:24px}h4,.h4{font-size:18px}h5,.h5{font-size:14px}h6,.h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}small,.small{font-size:85%}cite{font-style:normal}mark,.mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#428bca}a.text-primary:hover{color:#3071a9}.text-success{color:#3c763d}a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#428bca}a.bg-primary:hover{background-color:#3071a9}.bg-success{background-color:#dff0d8}a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ul,ol{margin-top:0;margin-bottom:10px}ul ul,ol ul,ul ol,ol ol{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;margin-left:-5px;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dt,dd{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote p:last-child,blockquote ul:last-child,blockquote ol:last-child{margin-bottom:0}blockquote footer,blockquote small,blockquote .small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote footer:before,blockquote small:before,blockquote .small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0}.blockquote-reverse footer:before,blockquote.pull-right footer:before,.blockquote-reverse small:before,blockquote.pull-right small:before,.blockquote-reverse .small:before,blockquote.pull-right .small:before{content:''}.blockquote-reverse footer:after,blockquote.pull-right footer:after,.blockquote-reverse small:after,blockquote.pull-right small:after,.blockquote-reverse .small:after,blockquote.pull-right .small:after{content:'\00A0 \2014'}blockquote:before,blockquote:after{content:""}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.col-xs-1,.col-sm-1,.col-md-1,.col-lg-1,.col-xs-2,.col-sm-2,.col-md-2,.col-lg-2,.col-xs-3,.col-sm-3,.col-md-3,.col-lg-3,.col-xs-4,.col-sm-4,.col-md-4,.col-lg-4,.col-xs-5,.col-sm-5,.col-md-5,.col-lg-5,.col-xs-6,.col-sm-6,.col-md-6,.col-lg-6,.col-xs-7,.col-sm-7,.col-md-7,.col-lg-7,.col-xs-8,.col-sm-8,.col-md-8,.col-lg-8,.col-xs-9,.col-sm-9,.col-md-9,.col-lg-9,.col-xs-10,.col-sm-10,.col-md-10,.col-lg-10,.col-xs-11,.col-sm-11,.col-md-11,.col-lg-11,.col-xs-12,.col-sm-12,.col-md-12,.col-lg-12{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-10,.col-xs-11,.col-xs-12{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11,.col-lg-12{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>thead>tr>th,.table>tbody>tr>th,.table>tfoot>tr>th,.table>thead>tr>td,.table>tbody>tr>td,.table>tfoot>tr>td{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>th,.table>caption+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>td,.table>thead:first-child>tr:first-child>td{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>thead>tr>th,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>tbody>tr>td,.table-condensed>tfoot>tr>td{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>tbody>tr>td,.table-bordered>tfoot>tr>td{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td{border-bottom-width:2px}.table-striped>tbody>tr:nth-child(odd)>td,.table-striped>tbody>tr:nth-child(odd)>th{background-color:#f9f9f9}.table-hover>tbody>tr:hover>td,.table-hover>tbody>tr:hover>th{background-color:#f5f5f5}table col[class*=col-]{position:static;display:table-column;float:none}table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none}.table>thead>tr>td.active,.table>tbody>tr>td.active,.table>tfoot>tr>td.active,.table>thead>tr>th.active,.table>tbody>tr>th.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>tbody>tr.active>td,.table>tfoot>tr.active>td,.table>thead>tr.active>th,.table>tbody>tr.active>th,.table>tfoot>tr.active>th{background-color:#f5f5f5}.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover,.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr.active:hover>th{background-color:#e8e8e8}.table>thead>tr>td.success,.table>tbody>tr>td.success,.table>tfoot>tr>td.success,.table>thead>tr>th.success,.table>tbody>tr>th.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>tbody>tr.success>td,.table>tfoot>tr.success>td,.table>thead>tr.success>th,.table>tbody>tr.success>th,.table>tfoot>tr.success>th{background-color:#dff0d8}.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover,.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr.success:hover>th{background-color:#d0e9c6}.table>thead>tr>td.info,.table>tbody>tr>td.info,.table>tfoot>tr>td.info,.table>thead>tr>th.info,.table>tbody>tr>th.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>tbody>tr.info>td,.table>tfoot>tr.info>td,.table>thead>tr.info>th,.table>tbody>tr.info>th,.table>tfoot>tr.info>th{background-color:#d9edf7}.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover,.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr.info:hover>th{background-color:#c4e3f3}.table>thead>tr>td.warning,.table>tbody>tr>td.warning,.table>tfoot>tr>td.warning,.table>thead>tr>th.warning,.table>tbody>tr>th.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>tbody>tr.warning>td,.table>tfoot>tr.warning>td,.table>thead>tr.warning>th,.table>tbody>tr.warning>th,.table>tfoot>tr.warning>th{background-color:#fcf8e3}.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover,.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr.warning:hover>th{background-color:#faf2cc}.table>thead>tr>td.danger,.table>tbody>tr>td.danger,.table>tfoot>tr>td.danger,.table>thead>tr>th.danger,.table>tbody>tr>th.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>tbody>tr.danger>td,.table>tfoot>tr.danger>td,.table>thead>tr.danger>th,.table>tbody>tr.danger>th,.table>tfoot>tr.danger>th{background-color:#f2dede}.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover,.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr.danger:hover>th{background-color:#ebcccc}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>thead>tr>th,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tfoot>tr>td{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>thead>tr>th:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.table-responsive>.table-bordered>thead>tr>th:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>th,.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>td{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=radio],input[type=checkbox]{margin:4px 0 0;margin-top:1px \9;line-height:normal}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=radio]:focus,input[type=checkbox]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#777;opacity:1}.form-control:-ms-input-placeholder{color:#777}.form-control::-webkit-input-placeholder{color:#777}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{cursor:not-allowed;background-color:#eee;opacity:1}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}input[type=date],input[type=time],input[type=datetime-local],input[type=month]{line-height:34px;line-height:1.42857143 \0}input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}.form-group{margin-bottom:15px}.radio,.checkbox{position:relative;display:block;min-height:20px;margin-top:10px;margin-bottom:10px}.radio label,.checkbox label{padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.radio input[type=radio],.radio-inline input[type=radio],.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox]{position:absolute;margin-top:4px \9;margin-left:-20px}.radio+.radio,.checkbox+.checkbox{margin-top:-5px}.radio-inline,.checkbox-inline{display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.radio-inline+.radio-inline,.checkbox-inline+.checkbox-inline{margin-top:0;margin-left:10px}input[type=radio][disabled],input[type=checkbox][disabled],input[type=radio].disabled,input[type=checkbox].disabled,fieldset[disabled] input[type=radio],fieldset[disabled] input[type=checkbox]{cursor:not-allowed}.radio-inline.disabled,.checkbox-inline.disabled,fieldset[disabled] .radio-inline,fieldset[disabled] .checkbox-inline{cursor:not-allowed}.radio.disabled label,.checkbox.disabled label,fieldset[disabled] .radio label,fieldset[disabled] .checkbox label{cursor:not-allowed}.form-control-static{padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm,.form-horizontal .form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}textarea.input-sm,select[multiple].input-sm{height:auto}.input-lg,.form-horizontal .form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}select.input-lg{height:46px;line-height:46px}textarea.input-lg,select[multiple].input-lg{height:auto}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:25px;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center}.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .help-block,.has-success .control-label,.has-success .radio,.has-success .checkbox,.has-success .radio-inline,.has-success .checkbox-inline{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .help-block,.has-warning .control-label,.has-warning .radio,.has-warning .checkbox,.has-warning .radio-inline,.has-warning .checkbox-inline{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .help-block,.has-error .control-label,.has-error .radio,.has-error .checkbox,.has-error .radio-inline,.has-error .checkbox-inline{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn,.form-inline .input-group .form-control{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .radio,.form-inline .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .radio label,.form-inline .checkbox label{padding-left:0}.form-inline .radio input[type=radio],.form-inline .checkbox input[type=checkbox]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .radio,.form-horizontal .checkbox,.form-horizontal .radio-inline,.form-horizontal .checkbox-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .radio,.form-horizontal .checkbox{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{top:0;right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:14.3px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn:focus,.btn:active:focus,.btn.active:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn:hover,.btn:focus{color:#333;text-decoration:none}.btn:active,.btn.active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{pointer-events:none;cursor:not-allowed;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default:hover,.btn-default:focus,.btn-default:active,.btn-default.active,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default:active,.btn-default.active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled,.btn-default[disabled],fieldset[disabled] .btn-default,.btn-default.disabled:hover,.btn-default[disabled]:hover,fieldset[disabled] .btn-default:hover,.btn-default.disabled:focus,.btn-default[disabled]:focus,fieldset[disabled] .btn-default:focus,.btn-default.disabled:active,.btn-default[disabled]:active,fieldset[disabled] .btn-default:active,.btn-default.disabled.active,.btn-default[disabled].active,fieldset[disabled] .btn-default.active{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#428bca;border-color:#357ebd}.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#3071a9;border-color:#285e8e}.btn-primary:active,.btn-primary.active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled,.btn-primary[disabled],fieldset[disabled] .btn-primary,.btn-primary.disabled:hover,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary:hover,.btn-primary.disabled:focus,.btn-primary[disabled]:focus,fieldset[disabled] .btn-primary:focus,.btn-primary.disabled:active,.btn-primary[disabled]:active,fieldset[disabled] .btn-primary:active,.btn-primary.disabled.active,.btn-primary[disabled].active,fieldset[disabled] .btn-primary.active{background-color:#428bca;border-color:#357ebd}.btn-primary .badge{color:#428bca;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success:active,.btn-success.active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled,.btn-success[disabled],fieldset[disabled] .btn-success,.btn-success.disabled:hover,.btn-success[disabled]:hover,fieldset[disabled] .btn-success:hover,.btn-success.disabled:focus,.btn-success[disabled]:focus,fieldset[disabled] .btn-success:focus,.btn-success.disabled:active,.btn-success[disabled]:active,fieldset[disabled] .btn-success:active,.btn-success.disabled.active,.btn-success[disabled].active,fieldset[disabled] .btn-success.active{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info:active,.btn-info.active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled,.btn-info[disabled],fieldset[disabled] .btn-info,.btn-info.disabled:hover,.btn-info[disabled]:hover,fieldset[disabled] .btn-info:hover,.btn-info.disabled:focus,.btn-info[disabled]:focus,fieldset[disabled] .btn-info:focus,.btn-info.disabled:active,.btn-info[disabled]:active,fieldset[disabled] .btn-info:active,.btn-info.disabled.active,.btn-info[disabled].active,fieldset[disabled] .btn-info.active{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning:hover,.btn-warning:focus,.btn-warning:active,.btn-warning.active,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning:active,.btn-warning.active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled,.btn-warning[disabled],fieldset[disabled] .btn-warning,.btn-warning.disabled:hover,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning:hover,.btn-warning.disabled:focus,.btn-warning[disabled]:focus,fieldset[disabled] .btn-warning:focus,.btn-warning.disabled:active,.btn-warning[disabled]:active,fieldset[disabled] .btn-warning:active,.btn-warning.disabled.active,.btn-warning[disabled].active,fieldset[disabled] .btn-warning.active{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger:hover,.btn-danger:focus,.btn-danger:active,.btn-danger.active,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger:active,.btn-danger.active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled,.btn-danger[disabled],fieldset[disabled] .btn-danger,.btn-danger.disabled:hover,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger:hover,.btn-danger.disabled:focus,.btn-danger[disabled]:focus,fieldset[disabled] .btn-danger:focus,.btn-danger.disabled:active,.btn-danger[disabled]:active,fieldset[disabled] .btn-danger:active,.btn-danger.disabled.active,.btn-danger[disabled].active,fieldset[disabled] .btn-danger.active{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#428bca;cursor:pointer;border-radius:0}.btn-link,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:hover,.btn-link:focus,.btn-link:active{border-color:transparent}.btn-link:hover,.btn-link:focus{color:#2a6496;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,fieldset[disabled] .btn-link:hover,.btn-link[disabled]:focus,fieldset[disabled] .btn-link:focus{color:#777;text-decoration:none}.btn-lg,.btn-group-lg>.btn{padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}.btn-sm,.btn-group-sm>.btn{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-xs,.btn-group-xs>.btn{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=submit].btn-block,input[type=reset].btn-block,input[type=button].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition:height .35s ease;-o-transition:height .35s ease;transition:height .35s ease}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px solid;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#fff;text-decoration:none;background-color:#428bca;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#777}.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px solid}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;float:left}.btn-group>.btn:hover,.btn-group-vertical>.btn:hover,.btn-group>.btn:focus,.btn-group-vertical>.btn:focus,.btn-group>.btn:active,.btn-group-vertical>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn.active{z-index:2}.btn-group>.btn:focus,.btn-group-vertical>.btn:focus{outline:0}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child>.btn:last-child,.btn-group>.btn-group:first-child>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn>input[type=radio],[data-toggle=buttons]>.btn>input[type=checkbox]{position:absolute;z-index:-1;filter:alpha(opacity=0);opacity:0}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn,select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn,select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn{height:auto}.input-group-addon,.input-group-btn,.input-group .form-control{display:table-cell}.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child),.input-group .form-control:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=radio],.input-group-addon input[type=checkbox]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group-btn:last-child>.btn-group:not(:last-child)>.btn{border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:first-child>.btn-group:not(:first-child)>.btn{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:hover,.input-group-btn>.btn:focus,.input-group-btn>.btn:active{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:hover,.nav>li.disabled>a:focus{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{background-color:#eee;border-color:#428bca}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:hover,.nav-pills>li.active>a:focus{color:#fff;background-color:#428bca}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;-webkit-overflow-scrolling:touch;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:340px}@media (max-width:480px) and (orientation:landscape){.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:200px}}.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030;-webkit-transform:translate3d(0,0,0);-o-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}@media (min-width:768px){.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:hover,.navbar-brand:focus{text-decoration:none}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu>li>a,.navbar-nav .open .dropdown-menu .dropdown-header{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:hover,.navbar-nav .open .dropdown-menu>li>a:focus{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}.navbar-nav.navbar-right:last-child{margin-right:-15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1)}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn,.navbar-form .input-group .form-control{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .radio,.navbar-form .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .radio label,.navbar-form .checkbox label{padding-left:0}.navbar-form .radio input[type=radio],.navbar-form .checkbox input[type=checkbox]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-form.navbar-right:last-child{margin-right:-15px}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}.navbar-text.navbar-right:last-child{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:hover,.navbar-default .navbar-brand:focus{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:hover,.navbar-default .navbar-nav>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:hover,.navbar-default .navbar-nav>.disabled>a:focus{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:hover,.navbar-default .navbar-toggle:focus{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:hover,.navbar-default .navbar-nav>.open>a:focus{color:#555;background-color:#e7e7e7}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:hover,.navbar-default .btn-link:focus{color:#333}.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:hover,.navbar-default .btn-link[disabled]:focus,fieldset[disabled] .navbar-default .btn-link:focus{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#777}.navbar-inverse .navbar-brand:hover,.navbar-inverse .navbar-brand:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#777}.navbar-inverse .navbar-nav>li>a{color:#777}.navbar-inverse .navbar-nav>li>a:hover,.navbar-inverse .navbar-nav>li>a:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:hover,.navbar-inverse .navbar-nav>.active>a:focus{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:hover,.navbar-inverse .navbar-nav>.disabled>a:focus{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:hover,.navbar-inverse .navbar-toggle:focus{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:hover,.navbar-inverse .navbar-nav>.open>a:focus{color:#fff;background-color:#080808}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#777}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#777}.navbar-inverse .btn-link:hover,.navbar-inverse .btn-link:focus{color:#fff}.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:hover,.navbar-inverse .btn-link[disabled]:focus,fieldset[disabled] .navbar-inverse .btn-link:focus{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#428bca;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:hover,.pagination>li>span:hover,.pagination>li>a:focus,.pagination>li>span:focus{color:#2a6496;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>span,.pagination>.active>a:hover,.pagination>.active>span:hover,.pagination>.active>a:focus,.pagination>.active>span:focus{z-index:2;color:#fff;cursor:default;background-color:#428bca;border-color:#428bca}.pagination>.disabled>span,.pagination>.disabled>span:hover,.pagination>.disabled>span:focus,.pagination>.disabled>a,.pagination>.disabled>a:hover,.pagination>.disabled>a:focus{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:hover,a.label:focus{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:hover,.label-default[href]:focus{background-color:#5e5e5e}.label-primary{background-color:#428bca}.label-primary[href]:hover,.label-primary[href]:focus{background-color:#3071a9}.label-success{background-color:#5cb85c}.label-success[href]:hover,.label-success[href]:focus{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:hover,.label-info[href]:focus{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:hover,.label-warning[href]:focus{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:hover,.label-danger[href]:focus{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-xs .badge{top:0;padding:1px 5px}a.badge:hover,a.badge:focus{color:#fff;text-decoration:none;cursor:pointer}a.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#428bca;background-color:#fff}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding:30px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron h1,.jumbotron .h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron{border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron{padding-right:60px;padding-left:60px}.jumbotron h1,.jumbotron .h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.thumbnail>img,.thumbnail a>img{margin-right:auto;margin-left:auto}a.thumbnail:hover,a.thumbnail:focus,a.thumbnail.active{border-color:#428bca}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#428bca;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-striped .progress-bar,.progress-bar-striped{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress.active .progress-bar,.progress-bar.active{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar[aria-valuenow="1"],.progress-bar[aria-valuenow="2"]{min-width:30px}.progress-bar[aria-valuenow="0"]{min-width:30px;color:#777;background-color:transparent;background-image:none;-webkit-box-shadow:none;box-shadow:none}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media,.media-body{overflow:hidden;zoom:1}.media,.media .media{margin-top:15px}.media:first-child{margin-top:0}.media-object{display:block}.media-heading{margin:0 0 5px}.media>.pull-left{margin-right:10px}.media>.pull-right{margin-left:10px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}a.list-group-item{color:#555}a.list-group-item .list-group-item-heading{color:#333}a.list-group-item:hover,a.list-group-item:focus{color:#555;text-decoration:none;background-color:#f5f5f5}.list-group-item.disabled,.list-group-item.disabled:hover,.list-group-item.disabled:focus{color:#777;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{z-index:2;color:#fff;background-color:#428bca;border-color:#428bca}.list-group-item.active .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>.small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:hover .list-group-item-text,.list-group-item.active:focus .list-group-item-text{color:#e1edf7}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:hover,a.list-group-item-success:focus{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:hover,a.list-group-item-success.active:focus{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:hover,a.list-group-item-info:focus{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:hover,a.list-group-item-info.active:focus{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:hover,a.list-group-item-warning:focus{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:hover,a.list-group-item-warning.active:focus{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:hover,a.list-group-item-danger:focus{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:hover,a.list-group-item-danger.active:focus{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group{margin-bottom:0}.panel>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.table,.panel>.table-responsive>.table,.panel>.panel-collapse>.table{margin-bottom:0}.panel>.table:first-child,.panel>.table-responsive:first-child>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table:last-child,.panel>.table-responsive:last-child>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child th,.panel>.table>tbody:first-child>tr:first-child td{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#428bca}.panel-primary>.panel-heading{color:#fff;background-color:#428bca;border-color:#428bca}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#428bca}.panel-primary>.panel-heading .badge{color:#428bca;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#428bca}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive iframe,.embed-responsive embed,.embed-responsive object{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.close:hover,.close:focus{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:translate3d(0,-25%,0);-o-transform:translate3d(0,-25%,0);transform:translate3d(0,-25%,0)}.modal.in .modal-dialog{-webkit-transform:translate3d(0,0,0);-o-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5)}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}.modal-header{min-height:16.43px;padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-size:12px;line-height:1.4;visibility:visible;filter:alpha(opacity=0);opacity:0}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{bottom:0;left:5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{right:5px;bottom:0;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;left:5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;right:5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;text-align:left;white-space:normal;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2)}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;font-weight:400;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{line-height:1}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:hover,.carousel-control:focus{color:#fff;text-decoration:none;filter:alpha(opacity=90);outline:0;opacity:.9}.carousel-control .icon-prev,.carousel-control .icon-next,.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right{position:absolute;top:50%;z-index:5;display:inline-block}.carousel-control .icon-prev,.carousel-control .glyphicon-chevron-left{left:50%;margin-left:-10px}.carousel-control .icon-next,.carousel-control .glyphicon-chevron-right{right:50%;margin-right:-10px}.carousel-control .icon-prev,.carousel-control .icon-next{width:20px;height:20px;margin-top:-10px;font-family:serif}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000 \9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-prev,.carousel-control .icon-next{width:30px;height:30px;margin-top:-15px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-15px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-15px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.clearfix:before,.clearfix:after,.dl-horizontal dd:before,.dl-horizontal dd:after,.container:before,.container:after,.container-fluid:before,.container-fluid:after,.row:before,.row:after,.form-horizontal .form-group:before,.form-horizontal .form-group:after,.btn-toolbar:before,.btn-toolbar:after,.btn-group-vertical>.btn-group:before,.btn-group-vertical>.btn-group:after,.nav:before,.nav:after,.navbar:before,.navbar:after,.navbar-header:before,.navbar-header:after,.navbar-collapse:before,.navbar-collapse:after,.pager:before,.pager:after,.panel-body:before,.panel-body:after,.modal-footer:before,.modal-footer:after{display:table;content:" "}.clearfix:after,.dl-horizontal dd:after,.container:after,.container-fluid:after,.row:after,.form-horizontal .form-group:after,.btn-toolbar:after,.btn-group-vertical>.btn-group:after,.nav:after,.navbar:after,.navbar-header:after,.navbar-collapse:after,.pager:after,.panel-body:after,.modal-footer:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important;visibility:hidden!important}.affix{position:fixed;-webkit-transform:translate3d(0,0,0);-o-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}@-ms-viewport{width:device-width}.visible-xs,.visible-sm,.visible-md,.visible-lg{display:none!important}.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table}tr.visible-xs{display:table-row!important}th.visible-xs,td.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table}tr.visible-sm{display:table-row!important}th.visible-sm,td.visible-sm{display:table-cell!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table}tr.visible-md{display:table-row!important}th.visible-md,td.visible-md{display:table-cell!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table}tr.visible-lg{display:table-row!important}th.visible-lg,td.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table}tr.visible-print{display:table-row!important}th.visible-print,td.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}} \ No newline at end of file diff --git a/samples/httpsession-gemfire-clientserver/src/main/webapp/index.jsp b/samples/httpsession-gemfire-clientserver/src/main/webapp/index.jsp new file mode 100644 index 00000000..b178e5e8 --- /dev/null +++ b/samples/httpsession-gemfire-clientserver/src/main/webapp/index.jsp @@ -0,0 +1,48 @@ +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> + + + + Session Attributes + + + + +
+

Description

+

This application demonstrates how to use a GemFire instance to back your session. Notice that there is no JSESSIONID cookie. We are also able to customize the way of identifying what the requested session id is.

+ +

Try it

+ +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + +
Attribute NameAttribute Value
+
+ + diff --git a/samples/httpsession-gemfire-p2p-xml/build.gradle b/samples/httpsession-gemfire-p2p-xml/build.gradle new file mode 100644 index 00000000..19f6a559 --- /dev/null +++ b/samples/httpsession-gemfire-p2p-xml/build.gradle @@ -0,0 +1,22 @@ +apply from: JAVA_GRADLE +apply from: TOMCAT_7_GRADLE + +tasks.findByPath("artifactoryPublish")?.enabled = false + +sonarRunner { + skipProject = true +} + +dependencies { + compile project(':spring-session-data-gemfire'), + "org.springframework:spring-web:$springVersion", + jstlDependencies + + providedCompile "javax.servlet:javax.servlet-api:$servletApiVersion" + + testCompile "junit:junit:$junitVersion" + + integrationTestCompile gebDependencies + + integrationTestRuntime "org.springframework.shell:spring-shell:1.0.0.RELEASE" +} diff --git a/samples/httpsession-gemfire-p2p-xml/src/integration-test/groovy/sample/AttributeTests.groovy b/samples/httpsession-gemfire-p2p-xml/src/integration-test/groovy/sample/AttributeTests.groovy new file mode 100644 index 00000000..a5f85c86 --- /dev/null +++ b/samples/httpsession-gemfire-p2p-xml/src/integration-test/groovy/sample/AttributeTests.groovy @@ -0,0 +1,45 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * 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. + */ +package sample + +import geb.spock.* +import sample.pages.HomePage; +import spock.lang.Stepwise +import pages.* + +/** + * Tests the CAS sample application using service tickets. + * + * @author Rob Winch + */ +@Stepwise +class AttributeTests extends GebReportingSpec { + def 'first visit no attributes'() { + when: + to HomePage + then: + attributes.empty + } + + def 'create attribute'() { + when: + createAttribute('a','b') + then: + attributes.size() == 1 + attributes[0].name == 'a' + attributes[0].value == 'b' + } +} \ No newline at end of file diff --git a/samples/httpsession-gemfire-p2p-xml/src/integration-test/groovy/sample/pages/HomePage.groovy b/samples/httpsession-gemfire-p2p-xml/src/integration-test/groovy/sample/pages/HomePage.groovy new file mode 100644 index 00000000..e087b5c4 --- /dev/null +++ b/samples/httpsession-gemfire-p2p-xml/src/integration-test/groovy/sample/pages/HomePage.groovy @@ -0,0 +1,45 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * 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. + */ +package sample.pages + +import geb.* + +/** + * The home page + * + * @author Rob Winch + */ +class HomePage extends Page { + static url = '' + static at = { assert driver.title == 'Session Attributes'; true} + static content = { + form { $('form') } + submit { $('input[type=submit]') } + createAttribute(required:false) { name, value -> + form.attributeName = name + form.attributeValue = value + submit.click(HomePage) + } + attributes { moduleList AttributeRow, $("table tr").tail() } + } +} +class AttributeRow extends Module { + static content = { + cell { $("td", it) } + name { cell(0).text() } + value { cell(1).text() } + } +} diff --git a/samples/httpsession-gemfire-p2p-xml/src/main/java/sample/SessionServlet.java b/samples/httpsession-gemfire-p2p-xml/src/main/java/sample/SessionServlet.java new file mode 100644 index 00000000..478d53b0 --- /dev/null +++ b/samples/httpsession-gemfire-p2p-xml/src/main/java/sample/SessionServlet.java @@ -0,0 +1,38 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * 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. + */ +package sample; + +import java.io.IOException; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +// tag::class[] +public class SessionServlet extends HttpServlet { + + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + String attributeName = request.getParameter("attributeName"); + String attributeValue = request.getParameter("attributeValue"); + request.getSession().setAttribute(attributeName, attributeValue); + response.sendRedirect(request.getContextPath() + "/"); + } + + private static final long serialVersionUID = 2878267318695777395L; +} +// end::class[] diff --git a/samples/httpsession-gemfire-p2p-xml/src/main/webapp/META-INF/MANIFEST.MF b/samples/httpsession-gemfire-p2p-xml/src/main/webapp/META-INF/MANIFEST.MF new file mode 100644 index 00000000..e69de29b diff --git a/samples/httpsession-gemfire-p2p-xml/src/main/webapp/WEB-INF/spring/session.xml b/samples/httpsession-gemfire-p2p-xml/src/main/webapp/WEB-INF/spring/session.xml new file mode 100644 index 00000000..2e408ab0 --- /dev/null +++ b/samples/httpsession-gemfire-p2p-xml/src/main/webapp/WEB-INF/spring/session.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + GemFireP2PHttpSessionXmlSample + 0 + ${sample.httpsession.gemfire.log-level:warning} + true + true + + + + + + + diff --git a/samples/httpsession-gemfire-p2p-xml/src/main/webapp/WEB-INF/web.xml b/samples/httpsession-gemfire-p2p-xml/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 00000000..7e46aa5b --- /dev/null +++ b/samples/httpsession-gemfire-p2p-xml/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,55 @@ + + + + + + contextConfigLocation + + /WEB-INF/spring/*.xml + + + + + + + springSessionRepositoryFilter + org.springframework.web.filter.DelegatingFilterProxy + + + springSessionRepositoryFilter + /* + + + + + + + + org.springframework.web.context.ContextLoaderListener + + + + + + session + sample.SessionServlet + + + + session + /session + + + + index.jsp + + \ No newline at end of file diff --git a/samples/httpsession-gemfire-p2p-xml/src/main/webapp/assets/bootstrap.min.css b/samples/httpsession-gemfire-p2p-xml/src/main/webapp/assets/bootstrap.min.css new file mode 100644 index 00000000..a9f35cee --- /dev/null +++ b/samples/httpsession-gemfire-p2p-xml/src/main/webapp/assets/bootstrap.min.css @@ -0,0 +1,5 @@ +/*! + * Bootstrap v3.2.0 (http://getbootstrap.com) + * Copyright 2011-2014 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + *//*! normalize.css v3.0.1 | MIT License | git.io/normalize */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background:0 0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}@media print{*{color:#000!important;text-shadow:none!important;background:transparent!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100%!important}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}select{background:#fff!important}.navbar{display:none}.table td,.table th{background-color:#fff!important}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table-bordered th,.table-bordered td{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\2a"}.glyphicon-plus:before{content:"\2b"}.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:before,:after{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}input,button,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#428bca;text-decoration:none}a:hover,a:focus{color:#2a6496;text-decoration:underline}a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.img-responsive,.thumbnail>img,.thumbnail a>img,.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;width:100% \9;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;width:100% \9;max-width:100%;height:auto;padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small,.h1 small,.h2 small,.h3 small,.h4 small,.h5 small,.h6 small,h1 .small,h2 .small,h3 .small,h4 .small,h5 .small,h6 .small,.h1 .small,.h2 .small,.h3 .small,.h4 .small,.h5 .small,.h6 .small{font-weight:400;line-height:1;color:#777}h1,.h1,h2,.h2,h3,.h3{margin-top:20px;margin-bottom:10px}h1 small,.h1 small,h2 small,.h2 small,h3 small,.h3 small,h1 .small,.h1 .small,h2 .small,.h2 .small,h3 .small,.h3 .small{font-size:65%}h4,.h4,h5,.h5,h6,.h6{margin-top:10px;margin-bottom:10px}h4 small,.h4 small,h5 small,.h5 small,h6 small,.h6 small,h4 .small,.h4 .small,h5 .small,.h5 .small,h6 .small,.h6 .small{font-size:75%}h1,.h1{font-size:36px}h2,.h2{font-size:30px}h3,.h3{font-size:24px}h4,.h4{font-size:18px}h5,.h5{font-size:14px}h6,.h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}small,.small{font-size:85%}cite{font-style:normal}mark,.mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#428bca}a.text-primary:hover{color:#3071a9}.text-success{color:#3c763d}a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#428bca}a.bg-primary:hover{background-color:#3071a9}.bg-success{background-color:#dff0d8}a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ul,ol{margin-top:0;margin-bottom:10px}ul ul,ol ul,ul ol,ol ol{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;margin-left:-5px;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dt,dd{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote p:last-child,blockquote ul:last-child,blockquote ol:last-child{margin-bottom:0}blockquote footer,blockquote small,blockquote .small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote footer:before,blockquote small:before,blockquote .small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0}.blockquote-reverse footer:before,blockquote.pull-right footer:before,.blockquote-reverse small:before,blockquote.pull-right small:before,.blockquote-reverse .small:before,blockquote.pull-right .small:before{content:''}.blockquote-reverse footer:after,blockquote.pull-right footer:after,.blockquote-reverse small:after,blockquote.pull-right small:after,.blockquote-reverse .small:after,blockquote.pull-right .small:after{content:'\00A0 \2014'}blockquote:before,blockquote:after{content:""}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.col-xs-1,.col-sm-1,.col-md-1,.col-lg-1,.col-xs-2,.col-sm-2,.col-md-2,.col-lg-2,.col-xs-3,.col-sm-3,.col-md-3,.col-lg-3,.col-xs-4,.col-sm-4,.col-md-4,.col-lg-4,.col-xs-5,.col-sm-5,.col-md-5,.col-lg-5,.col-xs-6,.col-sm-6,.col-md-6,.col-lg-6,.col-xs-7,.col-sm-7,.col-md-7,.col-lg-7,.col-xs-8,.col-sm-8,.col-md-8,.col-lg-8,.col-xs-9,.col-sm-9,.col-md-9,.col-lg-9,.col-xs-10,.col-sm-10,.col-md-10,.col-lg-10,.col-xs-11,.col-sm-11,.col-md-11,.col-lg-11,.col-xs-12,.col-sm-12,.col-md-12,.col-lg-12{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-10,.col-xs-11,.col-xs-12{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11,.col-lg-12{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>thead>tr>th,.table>tbody>tr>th,.table>tfoot>tr>th,.table>thead>tr>td,.table>tbody>tr>td,.table>tfoot>tr>td{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>th,.table>caption+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>td,.table>thead:first-child>tr:first-child>td{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>thead>tr>th,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>tbody>tr>td,.table-condensed>tfoot>tr>td{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>tbody>tr>td,.table-bordered>tfoot>tr>td{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td{border-bottom-width:2px}.table-striped>tbody>tr:nth-child(odd)>td,.table-striped>tbody>tr:nth-child(odd)>th{background-color:#f9f9f9}.table-hover>tbody>tr:hover>td,.table-hover>tbody>tr:hover>th{background-color:#f5f5f5}table col[class*=col-]{position:static;display:table-column;float:none}table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none}.table>thead>tr>td.active,.table>tbody>tr>td.active,.table>tfoot>tr>td.active,.table>thead>tr>th.active,.table>tbody>tr>th.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>tbody>tr.active>td,.table>tfoot>tr.active>td,.table>thead>tr.active>th,.table>tbody>tr.active>th,.table>tfoot>tr.active>th{background-color:#f5f5f5}.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover,.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr.active:hover>th{background-color:#e8e8e8}.table>thead>tr>td.success,.table>tbody>tr>td.success,.table>tfoot>tr>td.success,.table>thead>tr>th.success,.table>tbody>tr>th.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>tbody>tr.success>td,.table>tfoot>tr.success>td,.table>thead>tr.success>th,.table>tbody>tr.success>th,.table>tfoot>tr.success>th{background-color:#dff0d8}.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover,.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr.success:hover>th{background-color:#d0e9c6}.table>thead>tr>td.info,.table>tbody>tr>td.info,.table>tfoot>tr>td.info,.table>thead>tr>th.info,.table>tbody>tr>th.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>tbody>tr.info>td,.table>tfoot>tr.info>td,.table>thead>tr.info>th,.table>tbody>tr.info>th,.table>tfoot>tr.info>th{background-color:#d9edf7}.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover,.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr.info:hover>th{background-color:#c4e3f3}.table>thead>tr>td.warning,.table>tbody>tr>td.warning,.table>tfoot>tr>td.warning,.table>thead>tr>th.warning,.table>tbody>tr>th.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>tbody>tr.warning>td,.table>tfoot>tr.warning>td,.table>thead>tr.warning>th,.table>tbody>tr.warning>th,.table>tfoot>tr.warning>th{background-color:#fcf8e3}.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover,.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr.warning:hover>th{background-color:#faf2cc}.table>thead>tr>td.danger,.table>tbody>tr>td.danger,.table>tfoot>tr>td.danger,.table>thead>tr>th.danger,.table>tbody>tr>th.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>tbody>tr.danger>td,.table>tfoot>tr.danger>td,.table>thead>tr.danger>th,.table>tbody>tr.danger>th,.table>tfoot>tr.danger>th{background-color:#f2dede}.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover,.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr.danger:hover>th{background-color:#ebcccc}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>thead>tr>th,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tfoot>tr>td{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>thead>tr>th:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.table-responsive>.table-bordered>thead>tr>th:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>th,.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>td{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=radio],input[type=checkbox]{margin:4px 0 0;margin-top:1px \9;line-height:normal}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=radio]:focus,input[type=checkbox]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#777;opacity:1}.form-control:-ms-input-placeholder{color:#777}.form-control::-webkit-input-placeholder{color:#777}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{cursor:not-allowed;background-color:#eee;opacity:1}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}input[type=date],input[type=time],input[type=datetime-local],input[type=month]{line-height:34px;line-height:1.42857143 \0}input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}.form-group{margin-bottom:15px}.radio,.checkbox{position:relative;display:block;min-height:20px;margin-top:10px;margin-bottom:10px}.radio label,.checkbox label{padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.radio input[type=radio],.radio-inline input[type=radio],.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox]{position:absolute;margin-top:4px \9;margin-left:-20px}.radio+.radio,.checkbox+.checkbox{margin-top:-5px}.radio-inline,.checkbox-inline{display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.radio-inline+.radio-inline,.checkbox-inline+.checkbox-inline{margin-top:0;margin-left:10px}input[type=radio][disabled],input[type=checkbox][disabled],input[type=radio].disabled,input[type=checkbox].disabled,fieldset[disabled] input[type=radio],fieldset[disabled] input[type=checkbox]{cursor:not-allowed}.radio-inline.disabled,.checkbox-inline.disabled,fieldset[disabled] .radio-inline,fieldset[disabled] .checkbox-inline{cursor:not-allowed}.radio.disabled label,.checkbox.disabled label,fieldset[disabled] .radio label,fieldset[disabled] .checkbox label{cursor:not-allowed}.form-control-static{padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm,.form-horizontal .form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}textarea.input-sm,select[multiple].input-sm{height:auto}.input-lg,.form-horizontal .form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}select.input-lg{height:46px;line-height:46px}textarea.input-lg,select[multiple].input-lg{height:auto}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:25px;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center}.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .help-block,.has-success .control-label,.has-success .radio,.has-success .checkbox,.has-success .radio-inline,.has-success .checkbox-inline{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .help-block,.has-warning .control-label,.has-warning .radio,.has-warning .checkbox,.has-warning .radio-inline,.has-warning .checkbox-inline{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .help-block,.has-error .control-label,.has-error .radio,.has-error .checkbox,.has-error .radio-inline,.has-error .checkbox-inline{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn,.form-inline .input-group .form-control{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .radio,.form-inline .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .radio label,.form-inline .checkbox label{padding-left:0}.form-inline .radio input[type=radio],.form-inline .checkbox input[type=checkbox]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .radio,.form-horizontal .checkbox,.form-horizontal .radio-inline,.form-horizontal .checkbox-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .radio,.form-horizontal .checkbox{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{top:0;right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:14.3px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn:focus,.btn:active:focus,.btn.active:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn:hover,.btn:focus{color:#333;text-decoration:none}.btn:active,.btn.active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{pointer-events:none;cursor:not-allowed;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default:hover,.btn-default:focus,.btn-default:active,.btn-default.active,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default:active,.btn-default.active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled,.btn-default[disabled],fieldset[disabled] .btn-default,.btn-default.disabled:hover,.btn-default[disabled]:hover,fieldset[disabled] .btn-default:hover,.btn-default.disabled:focus,.btn-default[disabled]:focus,fieldset[disabled] .btn-default:focus,.btn-default.disabled:active,.btn-default[disabled]:active,fieldset[disabled] .btn-default:active,.btn-default.disabled.active,.btn-default[disabled].active,fieldset[disabled] .btn-default.active{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#428bca;border-color:#357ebd}.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#3071a9;border-color:#285e8e}.btn-primary:active,.btn-primary.active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled,.btn-primary[disabled],fieldset[disabled] .btn-primary,.btn-primary.disabled:hover,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary:hover,.btn-primary.disabled:focus,.btn-primary[disabled]:focus,fieldset[disabled] .btn-primary:focus,.btn-primary.disabled:active,.btn-primary[disabled]:active,fieldset[disabled] .btn-primary:active,.btn-primary.disabled.active,.btn-primary[disabled].active,fieldset[disabled] .btn-primary.active{background-color:#428bca;border-color:#357ebd}.btn-primary .badge{color:#428bca;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success:active,.btn-success.active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled,.btn-success[disabled],fieldset[disabled] .btn-success,.btn-success.disabled:hover,.btn-success[disabled]:hover,fieldset[disabled] .btn-success:hover,.btn-success.disabled:focus,.btn-success[disabled]:focus,fieldset[disabled] .btn-success:focus,.btn-success.disabled:active,.btn-success[disabled]:active,fieldset[disabled] .btn-success:active,.btn-success.disabled.active,.btn-success[disabled].active,fieldset[disabled] .btn-success.active{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info:active,.btn-info.active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled,.btn-info[disabled],fieldset[disabled] .btn-info,.btn-info.disabled:hover,.btn-info[disabled]:hover,fieldset[disabled] .btn-info:hover,.btn-info.disabled:focus,.btn-info[disabled]:focus,fieldset[disabled] .btn-info:focus,.btn-info.disabled:active,.btn-info[disabled]:active,fieldset[disabled] .btn-info:active,.btn-info.disabled.active,.btn-info[disabled].active,fieldset[disabled] .btn-info.active{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning:hover,.btn-warning:focus,.btn-warning:active,.btn-warning.active,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning:active,.btn-warning.active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled,.btn-warning[disabled],fieldset[disabled] .btn-warning,.btn-warning.disabled:hover,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning:hover,.btn-warning.disabled:focus,.btn-warning[disabled]:focus,fieldset[disabled] .btn-warning:focus,.btn-warning.disabled:active,.btn-warning[disabled]:active,fieldset[disabled] .btn-warning:active,.btn-warning.disabled.active,.btn-warning[disabled].active,fieldset[disabled] .btn-warning.active{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger:hover,.btn-danger:focus,.btn-danger:active,.btn-danger.active,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger:active,.btn-danger.active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled,.btn-danger[disabled],fieldset[disabled] .btn-danger,.btn-danger.disabled:hover,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger:hover,.btn-danger.disabled:focus,.btn-danger[disabled]:focus,fieldset[disabled] .btn-danger:focus,.btn-danger.disabled:active,.btn-danger[disabled]:active,fieldset[disabled] .btn-danger:active,.btn-danger.disabled.active,.btn-danger[disabled].active,fieldset[disabled] .btn-danger.active{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#428bca;cursor:pointer;border-radius:0}.btn-link,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:hover,.btn-link:focus,.btn-link:active{border-color:transparent}.btn-link:hover,.btn-link:focus{color:#2a6496;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,fieldset[disabled] .btn-link:hover,.btn-link[disabled]:focus,fieldset[disabled] .btn-link:focus{color:#777;text-decoration:none}.btn-lg,.btn-group-lg>.btn{padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}.btn-sm,.btn-group-sm>.btn{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-xs,.btn-group-xs>.btn{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=submit].btn-block,input[type=reset].btn-block,input[type=button].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition:height .35s ease;-o-transition:height .35s ease;transition:height .35s ease}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px solid;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#fff;text-decoration:none;background-color:#428bca;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#777}.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px solid}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;float:left}.btn-group>.btn:hover,.btn-group-vertical>.btn:hover,.btn-group>.btn:focus,.btn-group-vertical>.btn:focus,.btn-group>.btn:active,.btn-group-vertical>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn.active{z-index:2}.btn-group>.btn:focus,.btn-group-vertical>.btn:focus{outline:0}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child>.btn:last-child,.btn-group>.btn-group:first-child>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn>input[type=radio],[data-toggle=buttons]>.btn>input[type=checkbox]{position:absolute;z-index:-1;filter:alpha(opacity=0);opacity:0}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn,select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn,select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn{height:auto}.input-group-addon,.input-group-btn,.input-group .form-control{display:table-cell}.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child),.input-group .form-control:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=radio],.input-group-addon input[type=checkbox]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group-btn:last-child>.btn-group:not(:last-child)>.btn{border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:first-child>.btn-group:not(:first-child)>.btn{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:hover,.input-group-btn>.btn:focus,.input-group-btn>.btn:active{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:hover,.nav>li.disabled>a:focus{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{background-color:#eee;border-color:#428bca}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:hover,.nav-pills>li.active>a:focus{color:#fff;background-color:#428bca}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;-webkit-overflow-scrolling:touch;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:340px}@media (max-width:480px) and (orientation:landscape){.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:200px}}.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030;-webkit-transform:translate3d(0,0,0);-o-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}@media (min-width:768px){.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:hover,.navbar-brand:focus{text-decoration:none}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu>li>a,.navbar-nav .open .dropdown-menu .dropdown-header{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:hover,.navbar-nav .open .dropdown-menu>li>a:focus{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}.navbar-nav.navbar-right:last-child{margin-right:-15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1)}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn,.navbar-form .input-group .form-control{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .radio,.navbar-form .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .radio label,.navbar-form .checkbox label{padding-left:0}.navbar-form .radio input[type=radio],.navbar-form .checkbox input[type=checkbox]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-form.navbar-right:last-child{margin-right:-15px}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}.navbar-text.navbar-right:last-child{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:hover,.navbar-default .navbar-brand:focus{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:hover,.navbar-default .navbar-nav>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:hover,.navbar-default .navbar-nav>.disabled>a:focus{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:hover,.navbar-default .navbar-toggle:focus{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:hover,.navbar-default .navbar-nav>.open>a:focus{color:#555;background-color:#e7e7e7}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:hover,.navbar-default .btn-link:focus{color:#333}.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:hover,.navbar-default .btn-link[disabled]:focus,fieldset[disabled] .navbar-default .btn-link:focus{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#777}.navbar-inverse .navbar-brand:hover,.navbar-inverse .navbar-brand:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#777}.navbar-inverse .navbar-nav>li>a{color:#777}.navbar-inverse .navbar-nav>li>a:hover,.navbar-inverse .navbar-nav>li>a:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:hover,.navbar-inverse .navbar-nav>.active>a:focus{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:hover,.navbar-inverse .navbar-nav>.disabled>a:focus{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:hover,.navbar-inverse .navbar-toggle:focus{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:hover,.navbar-inverse .navbar-nav>.open>a:focus{color:#fff;background-color:#080808}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#777}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#777}.navbar-inverse .btn-link:hover,.navbar-inverse .btn-link:focus{color:#fff}.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:hover,.navbar-inverse .btn-link[disabled]:focus,fieldset[disabled] .navbar-inverse .btn-link:focus{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#428bca;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:hover,.pagination>li>span:hover,.pagination>li>a:focus,.pagination>li>span:focus{color:#2a6496;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>span,.pagination>.active>a:hover,.pagination>.active>span:hover,.pagination>.active>a:focus,.pagination>.active>span:focus{z-index:2;color:#fff;cursor:default;background-color:#428bca;border-color:#428bca}.pagination>.disabled>span,.pagination>.disabled>span:hover,.pagination>.disabled>span:focus,.pagination>.disabled>a,.pagination>.disabled>a:hover,.pagination>.disabled>a:focus{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:hover,a.label:focus{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:hover,.label-default[href]:focus{background-color:#5e5e5e}.label-primary{background-color:#428bca}.label-primary[href]:hover,.label-primary[href]:focus{background-color:#3071a9}.label-success{background-color:#5cb85c}.label-success[href]:hover,.label-success[href]:focus{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:hover,.label-info[href]:focus{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:hover,.label-warning[href]:focus{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:hover,.label-danger[href]:focus{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-xs .badge{top:0;padding:1px 5px}a.badge:hover,a.badge:focus{color:#fff;text-decoration:none;cursor:pointer}a.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#428bca;background-color:#fff}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding:30px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron h1,.jumbotron .h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron{border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron{padding-right:60px;padding-left:60px}.jumbotron h1,.jumbotron .h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.thumbnail>img,.thumbnail a>img{margin-right:auto;margin-left:auto}a.thumbnail:hover,a.thumbnail:focus,a.thumbnail.active{border-color:#428bca}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#428bca;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-striped .progress-bar,.progress-bar-striped{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress.active .progress-bar,.progress-bar.active{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar[aria-valuenow="1"],.progress-bar[aria-valuenow="2"]{min-width:30px}.progress-bar[aria-valuenow="0"]{min-width:30px;color:#777;background-color:transparent;background-image:none;-webkit-box-shadow:none;box-shadow:none}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media,.media-body{overflow:hidden;zoom:1}.media,.media .media{margin-top:15px}.media:first-child{margin-top:0}.media-object{display:block}.media-heading{margin:0 0 5px}.media>.pull-left{margin-right:10px}.media>.pull-right{margin-left:10px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}a.list-group-item{color:#555}a.list-group-item .list-group-item-heading{color:#333}a.list-group-item:hover,a.list-group-item:focus{color:#555;text-decoration:none;background-color:#f5f5f5}.list-group-item.disabled,.list-group-item.disabled:hover,.list-group-item.disabled:focus{color:#777;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{z-index:2;color:#fff;background-color:#428bca;border-color:#428bca}.list-group-item.active .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>.small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:hover .list-group-item-text,.list-group-item.active:focus .list-group-item-text{color:#e1edf7}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:hover,a.list-group-item-success:focus{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:hover,a.list-group-item-success.active:focus{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:hover,a.list-group-item-info:focus{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:hover,a.list-group-item-info.active:focus{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:hover,a.list-group-item-warning:focus{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:hover,a.list-group-item-warning.active:focus{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:hover,a.list-group-item-danger:focus{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:hover,a.list-group-item-danger.active:focus{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group{margin-bottom:0}.panel>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.table,.panel>.table-responsive>.table,.panel>.panel-collapse>.table{margin-bottom:0}.panel>.table:first-child,.panel>.table-responsive:first-child>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table:last-child,.panel>.table-responsive:last-child>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child th,.panel>.table>tbody:first-child>tr:first-child td{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#428bca}.panel-primary>.panel-heading{color:#fff;background-color:#428bca;border-color:#428bca}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#428bca}.panel-primary>.panel-heading .badge{color:#428bca;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#428bca}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive iframe,.embed-responsive embed,.embed-responsive object{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.close:hover,.close:focus{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:translate3d(0,-25%,0);-o-transform:translate3d(0,-25%,0);transform:translate3d(0,-25%,0)}.modal.in .modal-dialog{-webkit-transform:translate3d(0,0,0);-o-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5)}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}.modal-header{min-height:16.43px;padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-size:12px;line-height:1.4;visibility:visible;filter:alpha(opacity=0);opacity:0}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{bottom:0;left:5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{right:5px;bottom:0;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;left:5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;right:5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;text-align:left;white-space:normal;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2)}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;font-weight:400;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{line-height:1}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:hover,.carousel-control:focus{color:#fff;text-decoration:none;filter:alpha(opacity=90);outline:0;opacity:.9}.carousel-control .icon-prev,.carousel-control .icon-next,.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right{position:absolute;top:50%;z-index:5;display:inline-block}.carousel-control .icon-prev,.carousel-control .glyphicon-chevron-left{left:50%;margin-left:-10px}.carousel-control .icon-next,.carousel-control .glyphicon-chevron-right{right:50%;margin-right:-10px}.carousel-control .icon-prev,.carousel-control .icon-next{width:20px;height:20px;margin-top:-10px;font-family:serif}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000 \9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-prev,.carousel-control .icon-next{width:30px;height:30px;margin-top:-15px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-15px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-15px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.clearfix:before,.clearfix:after,.dl-horizontal dd:before,.dl-horizontal dd:after,.container:before,.container:after,.container-fluid:before,.container-fluid:after,.row:before,.row:after,.form-horizontal .form-group:before,.form-horizontal .form-group:after,.btn-toolbar:before,.btn-toolbar:after,.btn-group-vertical>.btn-group:before,.btn-group-vertical>.btn-group:after,.nav:before,.nav:after,.navbar:before,.navbar:after,.navbar-header:before,.navbar-header:after,.navbar-collapse:before,.navbar-collapse:after,.pager:before,.pager:after,.panel-body:before,.panel-body:after,.modal-footer:before,.modal-footer:after{display:table;content:" "}.clearfix:after,.dl-horizontal dd:after,.container:after,.container-fluid:after,.row:after,.form-horizontal .form-group:after,.btn-toolbar:after,.btn-group-vertical>.btn-group:after,.nav:after,.navbar:after,.navbar-header:after,.navbar-collapse:after,.pager:after,.panel-body:after,.modal-footer:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important;visibility:hidden!important}.affix{position:fixed;-webkit-transform:translate3d(0,0,0);-o-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}@-ms-viewport{width:device-width}.visible-xs,.visible-sm,.visible-md,.visible-lg{display:none!important}.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table}tr.visible-xs{display:table-row!important}th.visible-xs,td.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table}tr.visible-sm{display:table-row!important}th.visible-sm,td.visible-sm{display:table-cell!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table}tr.visible-md{display:table-row!important}th.visible-md,td.visible-md{display:table-cell!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table}tr.visible-lg{display:table-row!important}th.visible-lg,td.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table}tr.visible-print{display:table-row!important}th.visible-print,td.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}} \ No newline at end of file diff --git a/samples/httpsession-gemfire-p2p-xml/src/main/webapp/index.jsp b/samples/httpsession-gemfire-p2p-xml/src/main/webapp/index.jsp new file mode 100644 index 00000000..748378df --- /dev/null +++ b/samples/httpsession-gemfire-p2p-xml/src/main/webapp/index.jsp @@ -0,0 +1,48 @@ +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> + + + + Session Attributes + + + + +
+

Description

+

This application demonstrates how to use a GemFire instance to back your session. Notice that there is no JSESSIONID cookie. We are also able to customize the way of identifying what the requested session id is.

+ +

Try it

+ +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + +
Attribute NameAttribute Value
+
+ + diff --git a/samples/httpsession-gemfire-p2p/build.gradle b/samples/httpsession-gemfire-p2p/build.gradle new file mode 100644 index 00000000..19f6a559 --- /dev/null +++ b/samples/httpsession-gemfire-p2p/build.gradle @@ -0,0 +1,22 @@ +apply from: JAVA_GRADLE +apply from: TOMCAT_7_GRADLE + +tasks.findByPath("artifactoryPublish")?.enabled = false + +sonarRunner { + skipProject = true +} + +dependencies { + compile project(':spring-session-data-gemfire'), + "org.springframework:spring-web:$springVersion", + jstlDependencies + + providedCompile "javax.servlet:javax.servlet-api:$servletApiVersion" + + testCompile "junit:junit:$junitVersion" + + integrationTestCompile gebDependencies + + integrationTestRuntime "org.springframework.shell:spring-shell:1.0.0.RELEASE" +} diff --git a/samples/httpsession-gemfire-p2p/src/integration-test/groovy/sample/AttributeTests.groovy b/samples/httpsession-gemfire-p2p/src/integration-test/groovy/sample/AttributeTests.groovy new file mode 100644 index 00000000..a5f85c86 --- /dev/null +++ b/samples/httpsession-gemfire-p2p/src/integration-test/groovy/sample/AttributeTests.groovy @@ -0,0 +1,45 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * 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. + */ +package sample + +import geb.spock.* +import sample.pages.HomePage; +import spock.lang.Stepwise +import pages.* + +/** + * Tests the CAS sample application using service tickets. + * + * @author Rob Winch + */ +@Stepwise +class AttributeTests extends GebReportingSpec { + def 'first visit no attributes'() { + when: + to HomePage + then: + attributes.empty + } + + def 'create attribute'() { + when: + createAttribute('a','b') + then: + attributes.size() == 1 + attributes[0].name == 'a' + attributes[0].value == 'b' + } +} \ No newline at end of file diff --git a/samples/httpsession-gemfire-p2p/src/integration-test/groovy/sample/pages/HomePage.groovy b/samples/httpsession-gemfire-p2p/src/integration-test/groovy/sample/pages/HomePage.groovy new file mode 100644 index 00000000..e087b5c4 --- /dev/null +++ b/samples/httpsession-gemfire-p2p/src/integration-test/groovy/sample/pages/HomePage.groovy @@ -0,0 +1,45 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * 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. + */ +package sample.pages + +import geb.* + +/** + * The home page + * + * @author Rob Winch + */ +class HomePage extends Page { + static url = '' + static at = { assert driver.title == 'Session Attributes'; true} + static content = { + form { $('form') } + submit { $('input[type=submit]') } + createAttribute(required:false) { name, value -> + form.attributeName = name + form.attributeValue = value + submit.click(HomePage) + } + attributes { moduleList AttributeRow, $("table tr").tail() } + } +} +class AttributeRow extends Module { + static content = { + cell { $("td", it) } + name { cell(0).text() } + value { cell(1).text() } + } +} diff --git a/samples/httpsession-gemfire-p2p/src/main/java/sample/Config.java b/samples/httpsession-gemfire-p2p/src/main/java/sample/Config.java new file mode 100644 index 00000000..66f53b62 --- /dev/null +++ b/samples/httpsession-gemfire-p2p/src/main/java/sample/Config.java @@ -0,0 +1,52 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * 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. + */ +package sample; + +import java.util.Properties; + +import org.springframework.context.annotation.Bean; +import org.springframework.data.gemfire.CacheFactoryBean; +import org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession; + +// tag::class[] +@EnableGemFireHttpSession // <1> +public class Config { + + @Bean + Properties gemfireProperties() { // <2> + Properties gemfireProperties = new Properties(); + + gemfireProperties.setProperty("name", "GemFireP2PHttpSessionSample"); + gemfireProperties.setProperty("mcast-port", "0"); + gemfireProperties.setProperty("log-level", + System.getProperty("sample.httpsession.gemfire.log-level", "warning")); + gemfireProperties.setProperty("jmx-manager", "true"); + gemfireProperties.setProperty("jmx-manager-start", "true"); + + return gemfireProperties; + } + + @Bean + CacheFactoryBean gemfireCache() { // <3> + CacheFactoryBean gemfireCache = new CacheFactoryBean(); + + gemfireCache.setProperties(gemfireProperties()); + gemfireCache.setUseBeanFactoryLocator(false); + + return gemfireCache; + } +} +// end::class[] diff --git a/samples/httpsession-gemfire-p2p/src/main/java/sample/Initializer.java b/samples/httpsession-gemfire-p2p/src/main/java/sample/Initializer.java new file mode 100644 index 00000000..7512f33c --- /dev/null +++ b/samples/httpsession-gemfire-p2p/src/main/java/sample/Initializer.java @@ -0,0 +1,28 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * 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. + */ +package sample; + +import org.springframework.session.web.context.AbstractHttpSessionApplicationInitializer; + +// tag::class[] +public class Initializer + extends AbstractHttpSessionApplicationInitializer { // <1> + + public Initializer() { + super(Config.class); // <2> + } +} +// end::class[] \ No newline at end of file diff --git a/samples/httpsession-gemfire-p2p/src/main/java/sample/SessionServlet.java b/samples/httpsession-gemfire-p2p/src/main/java/sample/SessionServlet.java new file mode 100644 index 00000000..07b22e04 --- /dev/null +++ b/samples/httpsession-gemfire-p2p/src/main/java/sample/SessionServlet.java @@ -0,0 +1,40 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * 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. + */ +package sample; + +import java.io.IOException; +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +// tag::class[] +@WebServlet("/session") +public class SessionServlet extends HttpServlet { + + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + String attributeName = request.getParameter("attributeName"); + String attributeValue = request.getParameter("attributeValue"); + request.getSession().setAttribute(attributeName, attributeValue); + response.sendRedirect(request.getContextPath() + "/"); + } + + private static final long serialVersionUID = 2878267318695777395L; +} +// tag::end[] diff --git a/samples/httpsession-gemfire-p2p/src/main/webapp/META-INF/MANIFEST.MF b/samples/httpsession-gemfire-p2p/src/main/webapp/META-INF/MANIFEST.MF new file mode 100644 index 00000000..e69de29b diff --git a/samples/httpsession-gemfire-p2p/src/main/webapp/assets/bootstrap.min.css b/samples/httpsession-gemfire-p2p/src/main/webapp/assets/bootstrap.min.css new file mode 100644 index 00000000..a9f35cee --- /dev/null +++ b/samples/httpsession-gemfire-p2p/src/main/webapp/assets/bootstrap.min.css @@ -0,0 +1,5 @@ +/*! + * Bootstrap v3.2.0 (http://getbootstrap.com) + * Copyright 2011-2014 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + *//*! normalize.css v3.0.1 | MIT License | git.io/normalize */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background:0 0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}@media print{*{color:#000!important;text-shadow:none!important;background:transparent!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100%!important}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}select{background:#fff!important}.navbar{display:none}.table td,.table th{background-color:#fff!important}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table-bordered th,.table-bordered td{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\2a"}.glyphicon-plus:before{content:"\2b"}.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:before,:after{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}input,button,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#428bca;text-decoration:none}a:hover,a:focus{color:#2a6496;text-decoration:underline}a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.img-responsive,.thumbnail>img,.thumbnail a>img,.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;width:100% \9;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;width:100% \9;max-width:100%;height:auto;padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small,.h1 small,.h2 small,.h3 small,.h4 small,.h5 small,.h6 small,h1 .small,h2 .small,h3 .small,h4 .small,h5 .small,h6 .small,.h1 .small,.h2 .small,.h3 .small,.h4 .small,.h5 .small,.h6 .small{font-weight:400;line-height:1;color:#777}h1,.h1,h2,.h2,h3,.h3{margin-top:20px;margin-bottom:10px}h1 small,.h1 small,h2 small,.h2 small,h3 small,.h3 small,h1 .small,.h1 .small,h2 .small,.h2 .small,h3 .small,.h3 .small{font-size:65%}h4,.h4,h5,.h5,h6,.h6{margin-top:10px;margin-bottom:10px}h4 small,.h4 small,h5 small,.h5 small,h6 small,.h6 small,h4 .small,.h4 .small,h5 .small,.h5 .small,h6 .small,.h6 .small{font-size:75%}h1,.h1{font-size:36px}h2,.h2{font-size:30px}h3,.h3{font-size:24px}h4,.h4{font-size:18px}h5,.h5{font-size:14px}h6,.h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}small,.small{font-size:85%}cite{font-style:normal}mark,.mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#428bca}a.text-primary:hover{color:#3071a9}.text-success{color:#3c763d}a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#428bca}a.bg-primary:hover{background-color:#3071a9}.bg-success{background-color:#dff0d8}a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ul,ol{margin-top:0;margin-bottom:10px}ul ul,ol ul,ul ol,ol ol{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;margin-left:-5px;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dt,dd{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote p:last-child,blockquote ul:last-child,blockquote ol:last-child{margin-bottom:0}blockquote footer,blockquote small,blockquote .small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote footer:before,blockquote small:before,blockquote .small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0}.blockquote-reverse footer:before,blockquote.pull-right footer:before,.blockquote-reverse small:before,blockquote.pull-right small:before,.blockquote-reverse .small:before,blockquote.pull-right .small:before{content:''}.blockquote-reverse footer:after,blockquote.pull-right footer:after,.blockquote-reverse small:after,blockquote.pull-right small:after,.blockquote-reverse .small:after,blockquote.pull-right .small:after{content:'\00A0 \2014'}blockquote:before,blockquote:after{content:""}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.col-xs-1,.col-sm-1,.col-md-1,.col-lg-1,.col-xs-2,.col-sm-2,.col-md-2,.col-lg-2,.col-xs-3,.col-sm-3,.col-md-3,.col-lg-3,.col-xs-4,.col-sm-4,.col-md-4,.col-lg-4,.col-xs-5,.col-sm-5,.col-md-5,.col-lg-5,.col-xs-6,.col-sm-6,.col-md-6,.col-lg-6,.col-xs-7,.col-sm-7,.col-md-7,.col-lg-7,.col-xs-8,.col-sm-8,.col-md-8,.col-lg-8,.col-xs-9,.col-sm-9,.col-md-9,.col-lg-9,.col-xs-10,.col-sm-10,.col-md-10,.col-lg-10,.col-xs-11,.col-sm-11,.col-md-11,.col-lg-11,.col-xs-12,.col-sm-12,.col-md-12,.col-lg-12{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-10,.col-xs-11,.col-xs-12{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11,.col-lg-12{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>thead>tr>th,.table>tbody>tr>th,.table>tfoot>tr>th,.table>thead>tr>td,.table>tbody>tr>td,.table>tfoot>tr>td{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>th,.table>caption+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>td,.table>thead:first-child>tr:first-child>td{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>thead>tr>th,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>tbody>tr>td,.table-condensed>tfoot>tr>td{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>tbody>tr>td,.table-bordered>tfoot>tr>td{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td{border-bottom-width:2px}.table-striped>tbody>tr:nth-child(odd)>td,.table-striped>tbody>tr:nth-child(odd)>th{background-color:#f9f9f9}.table-hover>tbody>tr:hover>td,.table-hover>tbody>tr:hover>th{background-color:#f5f5f5}table col[class*=col-]{position:static;display:table-column;float:none}table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none}.table>thead>tr>td.active,.table>tbody>tr>td.active,.table>tfoot>tr>td.active,.table>thead>tr>th.active,.table>tbody>tr>th.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>tbody>tr.active>td,.table>tfoot>tr.active>td,.table>thead>tr.active>th,.table>tbody>tr.active>th,.table>tfoot>tr.active>th{background-color:#f5f5f5}.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover,.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr.active:hover>th{background-color:#e8e8e8}.table>thead>tr>td.success,.table>tbody>tr>td.success,.table>tfoot>tr>td.success,.table>thead>tr>th.success,.table>tbody>tr>th.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>tbody>tr.success>td,.table>tfoot>tr.success>td,.table>thead>tr.success>th,.table>tbody>tr.success>th,.table>tfoot>tr.success>th{background-color:#dff0d8}.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover,.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr.success:hover>th{background-color:#d0e9c6}.table>thead>tr>td.info,.table>tbody>tr>td.info,.table>tfoot>tr>td.info,.table>thead>tr>th.info,.table>tbody>tr>th.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>tbody>tr.info>td,.table>tfoot>tr.info>td,.table>thead>tr.info>th,.table>tbody>tr.info>th,.table>tfoot>tr.info>th{background-color:#d9edf7}.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover,.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr.info:hover>th{background-color:#c4e3f3}.table>thead>tr>td.warning,.table>tbody>tr>td.warning,.table>tfoot>tr>td.warning,.table>thead>tr>th.warning,.table>tbody>tr>th.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>tbody>tr.warning>td,.table>tfoot>tr.warning>td,.table>thead>tr.warning>th,.table>tbody>tr.warning>th,.table>tfoot>tr.warning>th{background-color:#fcf8e3}.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover,.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr.warning:hover>th{background-color:#faf2cc}.table>thead>tr>td.danger,.table>tbody>tr>td.danger,.table>tfoot>tr>td.danger,.table>thead>tr>th.danger,.table>tbody>tr>th.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>tbody>tr.danger>td,.table>tfoot>tr.danger>td,.table>thead>tr.danger>th,.table>tbody>tr.danger>th,.table>tfoot>tr.danger>th{background-color:#f2dede}.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover,.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr.danger:hover>th{background-color:#ebcccc}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>thead>tr>th,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tfoot>tr>td{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>thead>tr>th:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.table-responsive>.table-bordered>thead>tr>th:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>th,.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>td{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=radio],input[type=checkbox]{margin:4px 0 0;margin-top:1px \9;line-height:normal}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=radio]:focus,input[type=checkbox]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#777;opacity:1}.form-control:-ms-input-placeholder{color:#777}.form-control::-webkit-input-placeholder{color:#777}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{cursor:not-allowed;background-color:#eee;opacity:1}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}input[type=date],input[type=time],input[type=datetime-local],input[type=month]{line-height:34px;line-height:1.42857143 \0}input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}.form-group{margin-bottom:15px}.radio,.checkbox{position:relative;display:block;min-height:20px;margin-top:10px;margin-bottom:10px}.radio label,.checkbox label{padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.radio input[type=radio],.radio-inline input[type=radio],.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox]{position:absolute;margin-top:4px \9;margin-left:-20px}.radio+.radio,.checkbox+.checkbox{margin-top:-5px}.radio-inline,.checkbox-inline{display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.radio-inline+.radio-inline,.checkbox-inline+.checkbox-inline{margin-top:0;margin-left:10px}input[type=radio][disabled],input[type=checkbox][disabled],input[type=radio].disabled,input[type=checkbox].disabled,fieldset[disabled] input[type=radio],fieldset[disabled] input[type=checkbox]{cursor:not-allowed}.radio-inline.disabled,.checkbox-inline.disabled,fieldset[disabled] .radio-inline,fieldset[disabled] .checkbox-inline{cursor:not-allowed}.radio.disabled label,.checkbox.disabled label,fieldset[disabled] .radio label,fieldset[disabled] .checkbox label{cursor:not-allowed}.form-control-static{padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm,.form-horizontal .form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}textarea.input-sm,select[multiple].input-sm{height:auto}.input-lg,.form-horizontal .form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}select.input-lg{height:46px;line-height:46px}textarea.input-lg,select[multiple].input-lg{height:auto}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:25px;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center}.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .help-block,.has-success .control-label,.has-success .radio,.has-success .checkbox,.has-success .radio-inline,.has-success .checkbox-inline{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .help-block,.has-warning .control-label,.has-warning .radio,.has-warning .checkbox,.has-warning .radio-inline,.has-warning .checkbox-inline{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .help-block,.has-error .control-label,.has-error .radio,.has-error .checkbox,.has-error .radio-inline,.has-error .checkbox-inline{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn,.form-inline .input-group .form-control{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .radio,.form-inline .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .radio label,.form-inline .checkbox label{padding-left:0}.form-inline .radio input[type=radio],.form-inline .checkbox input[type=checkbox]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .radio,.form-horizontal .checkbox,.form-horizontal .radio-inline,.form-horizontal .checkbox-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .radio,.form-horizontal .checkbox{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{top:0;right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:14.3px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn:focus,.btn:active:focus,.btn.active:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn:hover,.btn:focus{color:#333;text-decoration:none}.btn:active,.btn.active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{pointer-events:none;cursor:not-allowed;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default:hover,.btn-default:focus,.btn-default:active,.btn-default.active,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default:active,.btn-default.active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled,.btn-default[disabled],fieldset[disabled] .btn-default,.btn-default.disabled:hover,.btn-default[disabled]:hover,fieldset[disabled] .btn-default:hover,.btn-default.disabled:focus,.btn-default[disabled]:focus,fieldset[disabled] .btn-default:focus,.btn-default.disabled:active,.btn-default[disabled]:active,fieldset[disabled] .btn-default:active,.btn-default.disabled.active,.btn-default[disabled].active,fieldset[disabled] .btn-default.active{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#428bca;border-color:#357ebd}.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#3071a9;border-color:#285e8e}.btn-primary:active,.btn-primary.active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled,.btn-primary[disabled],fieldset[disabled] .btn-primary,.btn-primary.disabled:hover,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary:hover,.btn-primary.disabled:focus,.btn-primary[disabled]:focus,fieldset[disabled] .btn-primary:focus,.btn-primary.disabled:active,.btn-primary[disabled]:active,fieldset[disabled] .btn-primary:active,.btn-primary.disabled.active,.btn-primary[disabled].active,fieldset[disabled] .btn-primary.active{background-color:#428bca;border-color:#357ebd}.btn-primary .badge{color:#428bca;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success:active,.btn-success.active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled,.btn-success[disabled],fieldset[disabled] .btn-success,.btn-success.disabled:hover,.btn-success[disabled]:hover,fieldset[disabled] .btn-success:hover,.btn-success.disabled:focus,.btn-success[disabled]:focus,fieldset[disabled] .btn-success:focus,.btn-success.disabled:active,.btn-success[disabled]:active,fieldset[disabled] .btn-success:active,.btn-success.disabled.active,.btn-success[disabled].active,fieldset[disabled] .btn-success.active{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info:active,.btn-info.active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled,.btn-info[disabled],fieldset[disabled] .btn-info,.btn-info.disabled:hover,.btn-info[disabled]:hover,fieldset[disabled] .btn-info:hover,.btn-info.disabled:focus,.btn-info[disabled]:focus,fieldset[disabled] .btn-info:focus,.btn-info.disabled:active,.btn-info[disabled]:active,fieldset[disabled] .btn-info:active,.btn-info.disabled.active,.btn-info[disabled].active,fieldset[disabled] .btn-info.active{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning:hover,.btn-warning:focus,.btn-warning:active,.btn-warning.active,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning:active,.btn-warning.active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled,.btn-warning[disabled],fieldset[disabled] .btn-warning,.btn-warning.disabled:hover,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning:hover,.btn-warning.disabled:focus,.btn-warning[disabled]:focus,fieldset[disabled] .btn-warning:focus,.btn-warning.disabled:active,.btn-warning[disabled]:active,fieldset[disabled] .btn-warning:active,.btn-warning.disabled.active,.btn-warning[disabled].active,fieldset[disabled] .btn-warning.active{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger:hover,.btn-danger:focus,.btn-danger:active,.btn-danger.active,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger:active,.btn-danger.active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled,.btn-danger[disabled],fieldset[disabled] .btn-danger,.btn-danger.disabled:hover,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger:hover,.btn-danger.disabled:focus,.btn-danger[disabled]:focus,fieldset[disabled] .btn-danger:focus,.btn-danger.disabled:active,.btn-danger[disabled]:active,fieldset[disabled] .btn-danger:active,.btn-danger.disabled.active,.btn-danger[disabled].active,fieldset[disabled] .btn-danger.active{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#428bca;cursor:pointer;border-radius:0}.btn-link,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:hover,.btn-link:focus,.btn-link:active{border-color:transparent}.btn-link:hover,.btn-link:focus{color:#2a6496;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,fieldset[disabled] .btn-link:hover,.btn-link[disabled]:focus,fieldset[disabled] .btn-link:focus{color:#777;text-decoration:none}.btn-lg,.btn-group-lg>.btn{padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}.btn-sm,.btn-group-sm>.btn{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-xs,.btn-group-xs>.btn{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=submit].btn-block,input[type=reset].btn-block,input[type=button].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition:height .35s ease;-o-transition:height .35s ease;transition:height .35s ease}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px solid;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#fff;text-decoration:none;background-color:#428bca;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#777}.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px solid}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;float:left}.btn-group>.btn:hover,.btn-group-vertical>.btn:hover,.btn-group>.btn:focus,.btn-group-vertical>.btn:focus,.btn-group>.btn:active,.btn-group-vertical>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn.active{z-index:2}.btn-group>.btn:focus,.btn-group-vertical>.btn:focus{outline:0}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child>.btn:last-child,.btn-group>.btn-group:first-child>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn>input[type=radio],[data-toggle=buttons]>.btn>input[type=checkbox]{position:absolute;z-index:-1;filter:alpha(opacity=0);opacity:0}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn,select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn,select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn{height:auto}.input-group-addon,.input-group-btn,.input-group .form-control{display:table-cell}.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child),.input-group .form-control:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=radio],.input-group-addon input[type=checkbox]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group-btn:last-child>.btn-group:not(:last-child)>.btn{border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:first-child>.btn-group:not(:first-child)>.btn{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:hover,.input-group-btn>.btn:focus,.input-group-btn>.btn:active{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:hover,.nav>li.disabled>a:focus{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{background-color:#eee;border-color:#428bca}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:hover,.nav-pills>li.active>a:focus{color:#fff;background-color:#428bca}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;-webkit-overflow-scrolling:touch;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:340px}@media (max-width:480px) and (orientation:landscape){.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:200px}}.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030;-webkit-transform:translate3d(0,0,0);-o-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}@media (min-width:768px){.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:hover,.navbar-brand:focus{text-decoration:none}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu>li>a,.navbar-nav .open .dropdown-menu .dropdown-header{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:hover,.navbar-nav .open .dropdown-menu>li>a:focus{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}.navbar-nav.navbar-right:last-child{margin-right:-15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1)}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn,.navbar-form .input-group .form-control{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .radio,.navbar-form .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .radio label,.navbar-form .checkbox label{padding-left:0}.navbar-form .radio input[type=radio],.navbar-form .checkbox input[type=checkbox]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-form.navbar-right:last-child{margin-right:-15px}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}.navbar-text.navbar-right:last-child{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:hover,.navbar-default .navbar-brand:focus{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:hover,.navbar-default .navbar-nav>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:hover,.navbar-default .navbar-nav>.disabled>a:focus{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:hover,.navbar-default .navbar-toggle:focus{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:hover,.navbar-default .navbar-nav>.open>a:focus{color:#555;background-color:#e7e7e7}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:hover,.navbar-default .btn-link:focus{color:#333}.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:hover,.navbar-default .btn-link[disabled]:focus,fieldset[disabled] .navbar-default .btn-link:focus{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#777}.navbar-inverse .navbar-brand:hover,.navbar-inverse .navbar-brand:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#777}.navbar-inverse .navbar-nav>li>a{color:#777}.navbar-inverse .navbar-nav>li>a:hover,.navbar-inverse .navbar-nav>li>a:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:hover,.navbar-inverse .navbar-nav>.active>a:focus{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:hover,.navbar-inverse .navbar-nav>.disabled>a:focus{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:hover,.navbar-inverse .navbar-toggle:focus{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:hover,.navbar-inverse .navbar-nav>.open>a:focus{color:#fff;background-color:#080808}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#777}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#777}.navbar-inverse .btn-link:hover,.navbar-inverse .btn-link:focus{color:#fff}.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:hover,.navbar-inverse .btn-link[disabled]:focus,fieldset[disabled] .navbar-inverse .btn-link:focus{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#428bca;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:hover,.pagination>li>span:hover,.pagination>li>a:focus,.pagination>li>span:focus{color:#2a6496;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>span,.pagination>.active>a:hover,.pagination>.active>span:hover,.pagination>.active>a:focus,.pagination>.active>span:focus{z-index:2;color:#fff;cursor:default;background-color:#428bca;border-color:#428bca}.pagination>.disabled>span,.pagination>.disabled>span:hover,.pagination>.disabled>span:focus,.pagination>.disabled>a,.pagination>.disabled>a:hover,.pagination>.disabled>a:focus{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:hover,a.label:focus{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:hover,.label-default[href]:focus{background-color:#5e5e5e}.label-primary{background-color:#428bca}.label-primary[href]:hover,.label-primary[href]:focus{background-color:#3071a9}.label-success{background-color:#5cb85c}.label-success[href]:hover,.label-success[href]:focus{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:hover,.label-info[href]:focus{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:hover,.label-warning[href]:focus{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:hover,.label-danger[href]:focus{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-xs .badge{top:0;padding:1px 5px}a.badge:hover,a.badge:focus{color:#fff;text-decoration:none;cursor:pointer}a.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#428bca;background-color:#fff}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding:30px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron h1,.jumbotron .h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron{border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron{padding-right:60px;padding-left:60px}.jumbotron h1,.jumbotron .h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.thumbnail>img,.thumbnail a>img{margin-right:auto;margin-left:auto}a.thumbnail:hover,a.thumbnail:focus,a.thumbnail.active{border-color:#428bca}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#428bca;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-striped .progress-bar,.progress-bar-striped{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress.active .progress-bar,.progress-bar.active{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar[aria-valuenow="1"],.progress-bar[aria-valuenow="2"]{min-width:30px}.progress-bar[aria-valuenow="0"]{min-width:30px;color:#777;background-color:transparent;background-image:none;-webkit-box-shadow:none;box-shadow:none}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media,.media-body{overflow:hidden;zoom:1}.media,.media .media{margin-top:15px}.media:first-child{margin-top:0}.media-object{display:block}.media-heading{margin:0 0 5px}.media>.pull-left{margin-right:10px}.media>.pull-right{margin-left:10px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}a.list-group-item{color:#555}a.list-group-item .list-group-item-heading{color:#333}a.list-group-item:hover,a.list-group-item:focus{color:#555;text-decoration:none;background-color:#f5f5f5}.list-group-item.disabled,.list-group-item.disabled:hover,.list-group-item.disabled:focus{color:#777;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{z-index:2;color:#fff;background-color:#428bca;border-color:#428bca}.list-group-item.active .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>.small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:hover .list-group-item-text,.list-group-item.active:focus .list-group-item-text{color:#e1edf7}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:hover,a.list-group-item-success:focus{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:hover,a.list-group-item-success.active:focus{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:hover,a.list-group-item-info:focus{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:hover,a.list-group-item-info.active:focus{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:hover,a.list-group-item-warning:focus{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:hover,a.list-group-item-warning.active:focus{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:hover,a.list-group-item-danger:focus{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:hover,a.list-group-item-danger.active:focus{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group{margin-bottom:0}.panel>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.table,.panel>.table-responsive>.table,.panel>.panel-collapse>.table{margin-bottom:0}.panel>.table:first-child,.panel>.table-responsive:first-child>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table:last-child,.panel>.table-responsive:last-child>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child th,.panel>.table>tbody:first-child>tr:first-child td{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#428bca}.panel-primary>.panel-heading{color:#fff;background-color:#428bca;border-color:#428bca}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#428bca}.panel-primary>.panel-heading .badge{color:#428bca;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#428bca}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive iframe,.embed-responsive embed,.embed-responsive object{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.close:hover,.close:focus{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:translate3d(0,-25%,0);-o-transform:translate3d(0,-25%,0);transform:translate3d(0,-25%,0)}.modal.in .modal-dialog{-webkit-transform:translate3d(0,0,0);-o-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5)}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}.modal-header{min-height:16.43px;padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-size:12px;line-height:1.4;visibility:visible;filter:alpha(opacity=0);opacity:0}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{bottom:0;left:5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{right:5px;bottom:0;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;left:5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;right:5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;text-align:left;white-space:normal;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2)}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;font-weight:400;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{line-height:1}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:hover,.carousel-control:focus{color:#fff;text-decoration:none;filter:alpha(opacity=90);outline:0;opacity:.9}.carousel-control .icon-prev,.carousel-control .icon-next,.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right{position:absolute;top:50%;z-index:5;display:inline-block}.carousel-control .icon-prev,.carousel-control .glyphicon-chevron-left{left:50%;margin-left:-10px}.carousel-control .icon-next,.carousel-control .glyphicon-chevron-right{right:50%;margin-right:-10px}.carousel-control .icon-prev,.carousel-control .icon-next{width:20px;height:20px;margin-top:-10px;font-family:serif}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000 \9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-prev,.carousel-control .icon-next{width:30px;height:30px;margin-top:-15px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-15px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-15px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.clearfix:before,.clearfix:after,.dl-horizontal dd:before,.dl-horizontal dd:after,.container:before,.container:after,.container-fluid:before,.container-fluid:after,.row:before,.row:after,.form-horizontal .form-group:before,.form-horizontal .form-group:after,.btn-toolbar:before,.btn-toolbar:after,.btn-group-vertical>.btn-group:before,.btn-group-vertical>.btn-group:after,.nav:before,.nav:after,.navbar:before,.navbar:after,.navbar-header:before,.navbar-header:after,.navbar-collapse:before,.navbar-collapse:after,.pager:before,.pager:after,.panel-body:before,.panel-body:after,.modal-footer:before,.modal-footer:after{display:table;content:" "}.clearfix:after,.dl-horizontal dd:after,.container:after,.container-fluid:after,.row:after,.form-horizontal .form-group:after,.btn-toolbar:after,.btn-group-vertical>.btn-group:after,.nav:after,.navbar:after,.navbar-header:after,.navbar-collapse:after,.pager:after,.panel-body:after,.modal-footer:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important;visibility:hidden!important}.affix{position:fixed;-webkit-transform:translate3d(0,0,0);-o-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}@-ms-viewport{width:device-width}.visible-xs,.visible-sm,.visible-md,.visible-lg{display:none!important}.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table}tr.visible-xs{display:table-row!important}th.visible-xs,td.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table}tr.visible-sm{display:table-row!important}th.visible-sm,td.visible-sm{display:table-cell!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table}tr.visible-md{display:table-row!important}th.visible-md,td.visible-md{display:table-cell!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table}tr.visible-lg{display:table-row!important}th.visible-lg,td.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table}tr.visible-print{display:table-row!important}th.visible-print,td.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}} \ No newline at end of file diff --git a/samples/httpsession-gemfire-p2p/src/main/webapp/index.jsp b/samples/httpsession-gemfire-p2p/src/main/webapp/index.jsp new file mode 100644 index 00000000..b178e5e8 --- /dev/null +++ b/samples/httpsession-gemfire-p2p/src/main/webapp/index.jsp @@ -0,0 +1,48 @@ +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> + + + + Session Attributes + + + + +
+

Description

+

This application demonstrates how to use a GemFire instance to back your session. Notice that there is no JSESSIONID cookie. We are also able to customize the way of identifying what the requested session id is.

+ +

Try it

+ +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + +
Attribute NameAttribute Value
+
+ + diff --git a/settings.gradle b/settings.gradle index b9ff71c2..a44c9540 100644 --- a/settings.gradle +++ b/settings.gradle @@ -8,6 +8,10 @@ include 'samples:findbyusername' include 'samples:hazelcast' include 'samples:hazelcast-spring' include 'samples:httpsession' +include 'samples:httpsession-gemfire-clientserver' +include 'samples:httpsession-gemfire-clientserver-xml' +include 'samples:httpsession-gemfire-p2p' +include 'samples:httpsession-gemfire-p2p-xml' include 'samples:httpsession-xml' include 'samples:rest' include 'samples:security' @@ -15,4 +19,5 @@ include 'samples:users' include 'samples:websocket' include 'spring-session' +include 'spring-session-data-gemfire' include 'spring-session-data-redis' diff --git a/spring-session-data-gemfire/build.gradle b/spring-session-data-gemfire/build.gradle new file mode 100644 index 00000000..578c6db2 --- /dev/null +++ b/spring-session-data-gemfire/build.gradle @@ -0,0 +1,22 @@ +apply from: JAVA_GRADLE +apply from: MAVEN_GRADLE +apply plugin: 'spring-io' + +description = "Aggregator for Spring Session and Spring Data GemFire" + +dependencies { + compile project(':spring-session') + compile("org.springframework.data:spring-data-gemfire:$springDataGemFireVersion") { + exclude group: "org.slf4j", module: 'slf4j-api' + exclude group: "org.slf4j", module: 'jcl-over-slf4j' + } + runtime "org.springframework.shell:spring-shell:1.0.0.RELEASE" +} + +dependencyManagement { + springIoTestRuntime { + imports { + mavenBom "io.spring.platform:platform-bom:${springIoVersion}" + } + } +} diff --git a/spring-session/build.gradle b/spring-session/build.gradle index f0113246..abdc9907 100644 --- a/spring-session/build.gradle +++ b/spring-session/build.gradle @@ -17,6 +17,7 @@ configurations { dependencies { optional "org.springframework.data:spring-data-redis:$springDataRedisVersion", "com.hazelcast:hazelcast:$hazelcastVersion", + "org.springframework.data:spring-data-gemfire:$springDataGemFireVersion", "org.springframework:spring-context:$springVersion", "org.springframework:spring-web:$springVersion", "org.springframework:spring-messaging:$springVersion", @@ -25,8 +26,11 @@ dependencies { integrationTestCompile "redis.clients:jedis:2.4.1", "org.apache.commons:commons-pool2:2.2" + integrationTestRuntime "org.springframework.shell:spring-shell:1.0.0.RELEASE" + testCompile "junit:junit:$junitVersion", - 'org.mockito:mockito-core:1.9.5', + 'org.mockito:mockito-core:1.10.19', + "edu.umd.cs.mtc:multithreadedtc:1.01", "org.springframework:spring-test:$springVersion", "org.assertj:assertj-core:$assertjVersion", "org.springframework.security:spring-security-core:$springSecurityVersion" diff --git a/spring-session/src/integration-test/java/org/springframework/session/data/gemfire/AbstractGemFireIntegrationTests.java b/spring-session/src/integration-test/java/org/springframework/session/data/gemfire/AbstractGemFireIntegrationTests.java new file mode 100644 index 00000000..098b1a40 --- /dev/null +++ b/spring-session/src/integration-test/java/org/springframework/session/data/gemfire/AbstractGemFireIntegrationTests.java @@ -0,0 +1,441 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * 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. + */ + +package org.springframework.session.data.gemfire; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.File; +import java.io.IOException; +import java.net.Socket; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.junit.Before; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationListener; +import org.springframework.session.ExpiringSession; +import org.springframework.session.data.gemfire.support.GemFireUtils; +import org.springframework.session.events.AbstractSessionEvent; + +import com.gemstone.gemfire.cache.Cache; +import com.gemstone.gemfire.cache.CacheClosedException; +import com.gemstone.gemfire.cache.DataPolicy; +import com.gemstone.gemfire.cache.ExpirationAction; +import com.gemstone.gemfire.cache.ExpirationAttributes; +import com.gemstone.gemfire.cache.GemFireCache; +import com.gemstone.gemfire.cache.Region; +import com.gemstone.gemfire.cache.client.ClientCache; +import com.gemstone.gemfire.cache.client.ClientCacheFactory; +import com.gemstone.gemfire.cache.server.CacheServer; + +/** + * AbstractGemFireIntegrationTests is an abstract base class encapsulating common operations for writing + * Spring Session GemFire integration tests. + * + * @author John Blum + * @see org.springframework.session.ExpiringSession + * @see org.springframework.session.events.AbstractSessionEvent + * @see com.gemstone.gemfire.cache.Cache + * @see com.gemstone.gemfire.cache.DataPolicy + * @see com.gemstone.gemfire.cache.ExpirationAttributes + * @see com.gemstone.gemfire.cache.GemFireCache + * @see com.gemstone.gemfire.cache.Region + * @see com.gemstone.gemfire.cache.client.ClientCache + * @see com.gemstone.gemfire.cache.server.CacheServer + * @since 1.1.0 + */ +@SuppressWarnings("unused") +public class AbstractGemFireIntegrationTests { + + protected static final boolean DEFAULT_ENABLE_QUERY_DEBUGGING = false; + protected static final boolean GEMFIRE_QUERY_DEBUG = Boolean.getBoolean("spring.session.data.gemfire.query.debug"); + + protected static final int DEFAULT_GEMFIRE_SERVER_PORT = CacheServer.DEFAULT_PORT; + + protected static final long DEFAULT_WAIT_DURATION = TimeUnit.SECONDS.toMillis(20); + protected static final long DEFAULT_WAIT_INTERVAL = 500l; + + protected static final File WORKING_DIRECTORY = new File(System.getProperty("user.dir")); + + protected static final String DEFAULT_PROCESS_CONTROL_FILENAME = "process.ctl"; + + protected static final String GEMFIRE_LOG_FILE_NAME = System.getProperty( + "spring.session.data.gemfire.log-file", "server.log"); + + protected static final String GEMFIRE_LOG_LEVEL = System.getProperty( + "spring.session.data.gemfire.log-level", "warning"); + + @Autowired + protected Cache gemfireCache; + + @Autowired + protected GemFireOperationsSessionRepository gemfireSessionRepository; + + @Before + public void setup() { + System.setProperty("gemfire.Query.VERBOSE", String.valueOf(isQueryDebuggingEnabled())); + } + + /* (non-Javadoc) */ + protected static File createDirectory(String pathname) { + File directory = new File(WORKING_DIRECTORY, pathname); + + assertThat(directory.isDirectory() || directory.mkdirs()).as( + String.format("Failed to create directory (%1$s)", directory)).isTrue(); + + directory.deleteOnExit(); + + return directory; + } + + /* (non-Javadoc) */ + protected static List createJavaProcessCommandLine(Class type, String... args) { + List commandLine = new ArrayList(); + + String javaHome = System.getProperty("java.home"); + String javaExe = new File(new File(javaHome, "bin"), "java").getAbsolutePath(); + + commandLine.add(javaExe); + commandLine.add("-server"); + commandLine.add("-ea"); + commandLine.add(String.format("-Dgemfire.log-file=%1$s", GEMFIRE_LOG_FILE_NAME)); + commandLine.add(String.format("-Dgemfire.log-level=%1$s", GEMFIRE_LOG_LEVEL)); + commandLine.add(String.format("-Dgemfire.Query.VERBOSE=%1$s", GEMFIRE_QUERY_DEBUG)); + commandLine.addAll(extractJvmArguments(args)); + commandLine.add("-classpath"); + commandLine.add(System.getProperty("java.class.path")); + commandLine.add(type.getName()); + commandLine.addAll(extractProgramArguments(args)); + + // System.err.printf("Java process command-line is (%1$s)%n", commandLine); + + return commandLine; + } + + /* (non-Javadoc) */ + protected static List extractJvmArguments(final String... args) { + List jvmArgs = new ArrayList(args.length); + + for (String arg : args) { + if (arg.startsWith("-")) { + jvmArgs.add(arg); + } + } + + return jvmArgs; + } + + /* (non-Javadoc) */ + protected static List extractProgramArguments(final String... args) { + List jvmArgs = new ArrayList(args.length); + + for (String arg : args) { + if (!arg.startsWith("-")) { + jvmArgs.add(arg); + } + } + + return jvmArgs; + } + + /* (non-Javadoc) */ + protected static Process run(Class type, File directory, String... args) throws IOException { + return new ProcessBuilder() + .command(createJavaProcessCommandLine(type, args)) + .directory(directory) + .start(); + } + + /* (non-Javadoc) */ + protected static boolean waitForCacheServerToStart(CacheServer cacheServer) { + return waitForCacheServerToStart(cacheServer, DEFAULT_WAIT_DURATION); + } + + /* (non-Javadoc) */ + protected static boolean waitForCacheServerToStart(CacheServer cacheServer, long duration) { + return waitForCacheServerToStart(cacheServer.getBindAddress(), cacheServer.getPort(), duration); + } + + /* (non-Javadoc) */ + protected static boolean waitForCacheServerToStart(String host, int port) { + return waitForCacheServerToStart(host, port, DEFAULT_WAIT_DURATION); + } + + /* (non-Javadoc) */ + protected static boolean waitForCacheServerToStart(final String host, final int port, long duration) { + return waitOnCondition(new Condition() { + AtomicBoolean connected = new AtomicBoolean(false); + + public boolean evaluate() { + Socket socket = null; + + try { + if (!connected.get()) { + socket = new Socket(host, port); + connected.set(true); + } + } + catch (IOException ignore) { + } + finally { + GemFireUtils.close(socket); + } + + return connected.get(); + } + }, duration); + } + + // NOTE this method would not be necessary except Spring Sessions' build does not fork the test JVM + // for every test class. + /* (non-Javadoc) */ + protected static boolean waitForClientCacheToClose() { + return waitForClientCacheToClose(DEFAULT_WAIT_DURATION); + } + + /* (non-Javadoc) */ + protected static boolean waitForClientCacheToClose(long duration) { + try { + final ClientCache clientCache = ClientCacheFactory.getAnyInstance(); + + clientCache.close(); + + waitOnCondition(new Condition() { + public boolean evaluate() { + return clientCache.isClosed(); + } + }, duration); + + return clientCache.isClosed(); + } + catch (CacheClosedException ignore) { + return true; + } + + } + + /* (non-Javadoc) */ + protected static boolean waitForProcessToStart(Process process, File directory) { + return waitForProcessToStart(process, directory, DEFAULT_WAIT_DURATION); + } + + /* (non-Javadoc) */ + @SuppressWarnings("all") + protected static boolean waitForProcessToStart(Process process, File directory, long duration) { + final File processControl = new File(directory, DEFAULT_PROCESS_CONTROL_FILENAME); + + waitOnCondition(new Condition() { + public boolean evaluate() { + return processControl.isFile(); + } + }, duration); + + return process.isAlive(); + } + + /* (non-Javadoc) */ + protected static int waitForProcessToStop(Process process, File directory) { + return waitForProcessToStop(process, directory, DEFAULT_WAIT_DURATION); + } + + /* (non-Javadoc) */ + protected static int waitForProcessToStop(Process process, File directory, long duration) { + final long timeout = (System.currentTimeMillis() + duration); + + try { + while (process.isAlive() && System.currentTimeMillis() < timeout) { + if (process.waitFor(DEFAULT_WAIT_INTERVAL, TimeUnit.MILLISECONDS)) { + return process.exitValue(); + } + } + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + + return (process.isAlive() ? -1 : process.exitValue()); + } + + /* (non-Javadoc) */ + protected static boolean waitOnCondition(Condition condition) { + return waitOnCondition(condition, DEFAULT_WAIT_DURATION); + } + + /* (non-Javadoc) */ + @SuppressWarnings("all") + protected static boolean waitOnCondition(Condition condition, long duration) { + final long timeout = (System.currentTimeMillis() + duration); + + try { + while (!condition.evaluate() && System.currentTimeMillis() < timeout) { + synchronized (condition) { + TimeUnit.MILLISECONDS.timedWait(condition, DEFAULT_WAIT_INTERVAL); + } + } + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + + return condition.evaluate(); + } + + /* (non-Javadoc) */ + protected static File writeProcessControlFile(File path) throws IOException { + assertThat(path != null && path.isDirectory()).isTrue(); + + File processControl = new File(path, DEFAULT_PROCESS_CONTROL_FILENAME); + + assertThat(processControl.createNewFile()).isTrue(); + + processControl.deleteOnExit(); + + return processControl; + } + + /* (non-Javadoc) */ + protected void assertRegion(Region actualRegion, String expectedName, DataPolicy expectedDataPolicy) { + assertThat(actualRegion).isNotNull(); + assertThat(actualRegion.getName()).isEqualTo(expectedName); + assertThat(actualRegion.getFullPath()).isEqualTo(GemFireUtils.toRegionPath(expectedName)); + assertThat(actualRegion.getAttributes()).isNotNull(); + assertThat(actualRegion.getAttributes().getDataPolicy()).isEqualTo(expectedDataPolicy); + } + + /* (non-Javadoc) */ + protected void assertEntryIdleTimeout(Region region, ExpirationAction expectedAction, int expectedTimeout) { + assertEntryIdleTimeout(region.getAttributes().getEntryIdleTimeout(), expectedAction, expectedTimeout); + } + + /* (non-Javadoc) */ + protected void assertEntryIdleTimeout(ExpirationAttributes actualExpirationAttributes, + ExpirationAction expectedAction, int expectedTimeout) { + assertThat(actualExpirationAttributes).isNotNull(); + assertThat(actualExpirationAttributes.getAction()).isEqualTo(expectedAction); + assertThat(actualExpirationAttributes.getTimeout()).isEqualTo(expectedTimeout); + } + + /* (non-Javadoc) */ + protected boolean enableQueryDebugging() { + return DEFAULT_ENABLE_QUERY_DEBUGGING; + } + + /* (non-Javadoc) */ + protected boolean isQueryDebuggingEnabled() { + return (GEMFIRE_QUERY_DEBUG || enableQueryDebugging()); + } + + /* (non-Javadoc) */ + protected List listRegions(GemFireCache gemfireCache) { + Set> regions = gemfireCache.rootRegions(); + + List regionList = new ArrayList(regions.size()); + + for (Region region : regions) { + regionList.add(region.getFullPath()); + } + + return regionList; + } + + /* (non-Javadoc) */ + @SuppressWarnings("unchecked") + protected T createSession() { + T expiringSession = (T) gemfireSessionRepository.createSession(); + assertThat(expiringSession).isNotNull(); + return expiringSession; + } + + /* (non-Javadoc) */ + @SuppressWarnings("unchecked") + protected T createSession(String principalName) { + GemFireOperationsSessionRepository.GemFireSession session = createSession(); + session.setPrincipalName(principalName); + return (T) session; + } + + /* (non-Javadoc) */ + protected T expire(T session) { + session.setLastAccessedTime(0l); + return session; + } + + /* (non-Javadoc) */ + @SuppressWarnings("unchecked") + protected T get(String sessionId) { + return (T) gemfireSessionRepository.getSession(sessionId); + } + + /* (non-Javadoc) */ + protected T save(T session) { + gemfireSessionRepository.save(session); + return session; + } + + /* (non-Javadoc) */ + protected T touch(T session) { + session.setLastAccessedTime(System.currentTimeMillis()); + return session; + } + + /** + * The SessionEventListener class is a Spring {@link ApplicationListener} listening for Spring HTTP Session + * application events. + * + * @see org.springframework.context.ApplicationListener + * @see org.springframework.session.events.AbstractSessionEvent + */ + public static class SessionEventListener implements ApplicationListener { + + private volatile AbstractSessionEvent sessionEvent; + + /* (non-Javadoc) */ + @SuppressWarnings("unchecked") + public T getSessionEvent() { + T sessionEvent = (T) this.sessionEvent; + this.sessionEvent = null; + return sessionEvent; + } + + /* (non-Javadoc) */ + public void onApplicationEvent(AbstractSessionEvent event) { + sessionEvent = event; + } + + /* (non-Javadoc) */ + public T waitForSessionEvent(long duration) { + waitOnCondition(new Condition() { + public boolean evaluate() { + return (sessionEvent != null); + } + }, duration); + + return getSessionEvent(); + } + } + + /** + * The Condition interface defines a logical condition that must be satisfied before it is safe to proceed. + */ + protected interface Condition { + boolean evaluate(); + } + +} diff --git a/spring-session/src/integration-test/java/org/springframework/session/data/gemfire/ClientServerGemFireOperationsSessionRepositoryIntegrationTests.java b/spring-session/src/integration-test/java/org/springframework/session/data/gemfire/ClientServerGemFireOperationsSessionRepositoryIntegrationTests.java new file mode 100644 index 00000000..24c7bb53 --- /dev/null +++ b/spring-session/src/integration-test/java/org/springframework/session/data/gemfire/ClientServerGemFireOperationsSessionRepositoryIntegrationTests.java @@ -0,0 +1,371 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * 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. + */ + +package org.springframework.session.data.gemfire; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.File; +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Collections; +import java.util.Date; +import java.util.Properties; +import java.util.concurrent.TimeUnit; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; +import org.springframework.data.gemfire.CacheFactoryBean; +import org.springframework.data.gemfire.client.ClientCacheFactoryBean; +import org.springframework.data.gemfire.client.PoolFactoryBean; +import org.springframework.data.gemfire.config.GemfireConstants; +import org.springframework.data.gemfire.server.CacheServerFactoryBean; +import org.springframework.data.gemfire.support.ConnectionEndpoint; +import org.springframework.session.ExpiringSession; +import org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession; +import org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration; +import org.springframework.session.data.gemfire.support.GemFireUtils; +import org.springframework.session.events.AbstractSessionEvent; +import org.springframework.session.events.SessionCreatedEvent; +import org.springframework.session.events.SessionDeletedEvent; +import org.springframework.session.events.SessionExpiredEvent; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.util.FileSystemUtils; +import org.springframework.util.SocketUtils; + +import com.gemstone.gemfire.cache.Cache; +import com.gemstone.gemfire.cache.DataPolicy; +import com.gemstone.gemfire.cache.Region; +import com.gemstone.gemfire.cache.RegionAttributes; +import com.gemstone.gemfire.cache.client.ClientCache; +import com.gemstone.gemfire.cache.client.Pool; + +/** + * The ClientServerGemFireOperationsSessionRepositoryIntegrationTests class is a test suite of test cases testing + * the functionality of GemFire-backed Spring Sessions using a GemFire client-server topology. + * + * @author John Blum + * @see org.junit.Test + * @see org.junit.runner.RunWith + * @see org.springframework.session.data.gemfire.AbstractGemFireIntegrationTests + * @see org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession + * @see org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration + * @see org.springframework.test.context.ContextConfiguration + * @see org.springframework.test.context.junit4.SpringJUnit4ClassRunner + * @see org.springframework.test.context.web.WebAppConfiguration + * @see com.gemstone.gemfire.cache.Cache + * @see com.gemstone.gemfire.cache.client.ClientCache + * @see com.gemstone.gemfire.cache.client.Pool + * @see com.gemstone.gemfire.cache.server.CacheServer + * @since 1.0.0 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = + ClientServerGemFireOperationsSessionRepositoryIntegrationTests.SpringSessionGemFireClientConfiguration.class) +@WebAppConfiguration +@SuppressWarnings("unused") +public class ClientServerGemFireOperationsSessionRepositoryIntegrationTests extends AbstractGemFireIntegrationTests { + + private static final int MAX_INACTIVE_INTERVAL_IN_SECONDS = 1; + + private static final DateFormat TIMESTAMP = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss"); + + private static File processWorkingDirectory; + + private static Process gemfireServer; + + @Autowired + private SessionEventListener sessionEventListener; + + @BeforeClass + public static void startGemFireServer() throws IOException { + final long t0 = System.currentTimeMillis(); + + final int port = SocketUtils.findAvailableTcpPort(); + + System.err.printf("Starting GemFire Server running on [%1$s] listening on port [%2$d]%n", + InetAddress.getLocalHost().getHostName(), port); + + System.setProperty("spring.session.data.gemfire.port", String.valueOf(port)); + + String processWorkingDirectoryPathname = String.format("gemfire-client-server-tests-%1$s", + TIMESTAMP.format(new Date())); + + processWorkingDirectory = createDirectory(processWorkingDirectoryPathname); + gemfireServer = run(SpringSessionGemFireServerConfiguration.class, processWorkingDirectory, + String.format("-Dspring.session.data.gemfire.port=%1$d", port)); + + assertThat(waitForCacheServerToStart(SpringSessionGemFireServerConfiguration.SERVER_HOSTNAME, port)).isTrue(); + //assertThat(waitForProcessToStart(gemfireServer, processWorkingDirectory)).isTrue(); + + System.err.printf("GemFire Server [startup time = %1$d ms]%n", System.currentTimeMillis() - t0); + } + + @AfterClass + public static void stopGemFireServerAndDeleteArtifacts() { + if (gemfireServer != null) { + gemfireServer.destroyForcibly(); + System.err.printf("GemFire Server [exit code = %1$d]%n", + waitForProcessToStop(gemfireServer, processWorkingDirectory)); + } + + if (Boolean.valueOf(System.getProperty("spring.session.data.gemfire.fork.clean", Boolean.TRUE.toString()))) { + FileSystemUtils.deleteRecursively(processWorkingDirectory); + } + + assertThat(waitForClientCacheToClose(DEFAULT_WAIT_DURATION)).isTrue(); + } + + @Before + public void setup() { + assertThat(GemFireUtils.isClient(gemfireCache)).isTrue(); + + Region springSessionGemFireRegion = gemfireCache.getRegion( + GemFireHttpSessionConfiguration.DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME); + + assertThat(springSessionGemFireRegion).isNotNull(); + + RegionAttributes springSessionGemFireRegionAttributes = + springSessionGemFireRegion.getAttributes(); + + assertThat(springSessionGemFireRegionAttributes).isNotNull(); + assertThat(springSessionGemFireRegionAttributes.getDataPolicy()).isEqualTo(DataPolicy.EMPTY); + } + + @After + public void tearDown() { + sessionEventListener.getSessionEvent(); + } + + @Test + public void createSessionFiresSessionCreatedEvent() { + final long beforeOrAtCreationTime = System.currentTimeMillis(); + + ExpiringSession expectedSession = save(createSession()); + + AbstractSessionEvent sessionEvent = sessionEventListener.waitForSessionEvent(500); + + assertThat(sessionEvent).isInstanceOf(SessionCreatedEvent.class); + + ExpiringSession createdSession = sessionEvent.getSession(); + + assertThat(createdSession).isEqualTo(expectedSession); + assertThat(createdSession.getId()).isNotNull(); + assertThat(createdSession.getCreationTime()).isGreaterThanOrEqualTo(beforeOrAtCreationTime); + assertThat(createdSession.getLastAccessedTime()).isEqualTo(createdSession.getCreationTime()); + assertThat(createdSession.getMaxInactiveIntervalInSeconds()).isEqualTo(MAX_INACTIVE_INTERVAL_IN_SECONDS); + + gemfireSessionRepository.delete(expectedSession.getId()); + } + + @Test + public void getExistingNonExpiredSessionBeforeAndAfterExpiration() { + ExpiringSession expectedSession = save(touch(createSession())); + + AbstractSessionEvent sessionEvent = sessionEventListener.waitForSessionEvent(500); + + assertThat(sessionEvent).isInstanceOf(SessionCreatedEvent.class); + assertThat(sessionEvent.getSession()).isEqualTo(expectedSession); + assertThat(sessionEventListener.getSessionEvent()).isNull(); + + ExpiringSession savedSession = gemfireSessionRepository.getSession(expectedSession.getId()); + + assertThat(savedSession).isEqualTo(expectedSession); + + // NOTE for some reason or another, performing a GemFire (Client)Cache Region.get(key) + // causes a Region CREATE event... o.O + // calling sessionEventListener.getSessionEvent() here to clear the event + sessionEventListener.getSessionEvent(); + + sessionEvent = sessionEventListener.waitForSessionEvent(TimeUnit.SECONDS.toMillis( + MAX_INACTIVE_INTERVAL_IN_SECONDS + 1)); + + assertThat(sessionEvent).isInstanceOf(SessionExpiredEvent.class); + assertThat(sessionEvent.getSessionId()).isEqualTo(expectedSession.getId()); + + ExpiringSession expiredSession = gemfireSessionRepository.getSession(expectedSession.getId()); + + assertThat(expiredSession).isNull(); + } + + @Test + public void deleteExistingNonExpiredSessionFiresSessionDeletedEventAndReturnsNullOnGet() { + ExpiringSession expectedSession = save(touch(createSession())); + + AbstractSessionEvent sessionEvent = sessionEventListener.waitForSessionEvent(500); + + assertThat(sessionEvent).isInstanceOf(SessionCreatedEvent.class); + assertThat(sessionEvent.getSession()).isEqualTo(expectedSession); + + gemfireSessionRepository.delete(expectedSession.getId()); + + sessionEvent = sessionEventListener.waitForSessionEvent(500); + + assertThat(sessionEvent).isInstanceOf(SessionDeletedEvent.class); + assertThat(sessionEvent.getSessionId()).isEqualTo(expectedSession.getId()); + + ExpiringSession deletedSession = gemfireSessionRepository.getSession(expectedSession.getId()); + + assertThat(deletedSession).isNull(); + } + + @EnableGemFireHttpSession(maxInactiveIntervalInSeconds = MAX_INACTIVE_INTERVAL_IN_SECONDS) + static class SpringSessionGemFireClientConfiguration { + + //TODO remove when SGF-458 is released + static { + System.setProperty("gemfire.log-level", GEMFIRE_LOG_LEVEL); + } + + @Bean + PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() { + return new PropertySourcesPlaceholderConfigurer(); + } + + @Bean + Properties gemfireProperties() { + Properties gemfireProperties = new Properties(); + //TODO uncomment when SGF-458 is released. + //gemfireProperties.setProperty("name", ClientServerGemFireOperationsSessionRepositoryIntegrationTests.class.getName()); + //gemfireProperties.setProperty("log-level", GEMFIRE_LOG_LEVEL); + return gemfireProperties; + } + + @Bean(name = GemfireConstants.DEFAULT_GEMFIRE_POOL_NAME) + PoolFactoryBean gemfirePool(@Value("${spring.session.data.gemfire.port:"+DEFAULT_GEMFIRE_SERVER_PORT+"}") int port) { + PoolFactoryBean poolFactory = new PoolFactoryBean(); + + // TODO uncomment when SGF-458 is released + //poolFactory.setProperties(gemfireProperties()); + poolFactory.setName(GemfireConstants.DEFAULT_GEMFIRE_POOL_NAME); + poolFactory.setFreeConnectionTimeout(5000); // 5 seconds + poolFactory.setKeepAlive(false); + poolFactory.setMaxConnections(SpringSessionGemFireServerConfiguration.MAX_CONNECTIONS); + poolFactory.setPingInterval(TimeUnit.SECONDS.toMillis(5)); + poolFactory.setReadTimeout(2000); // 2 seconds + poolFactory.setRetryAttempts(2); + poolFactory.setSubscriptionEnabled(true); + poolFactory.setThreadLocalConnections(false); + + poolFactory.setServerEndpoints(Collections.singletonList(new ConnectionEndpoint( + SpringSessionGemFireServerConfiguration.SERVER_HOSTNAME, port))); + + return poolFactory; + } + + @Bean + ClientCacheFactoryBean gemfireCache(Pool gemfirePool) { + ClientCacheFactoryBean clientCacheFactory = new ClientCacheFactoryBean(); + + clientCacheFactory.setClose(true); + clientCacheFactory.setPool(gemfirePool); + clientCacheFactory.setProperties(gemfireProperties()); + clientCacheFactory.setUseBeanFactoryLocator(false); + + return clientCacheFactory; + } + + @Bean + public SessionEventListener sessionEventListener() { + return new SessionEventListener(); + } + + // used for debugging purposes + public static void main(final String[] args) { + ConfigurableApplicationContext applicationContext = new AnnotationConfigApplicationContext( + SpringSessionGemFireClientConfiguration.class); + + applicationContext.registerShutdownHook(); + + ClientCache clientCache = applicationContext.getBean(ClientCache.class); + + for (InetSocketAddress server : clientCache.getCurrentServers()) { + System.err.printf("GemFire Server [host: %1$s, port: %2$d]%n", + server.getHostName(), server.getPort()); + } + } + } + + @EnableGemFireHttpSession(maxInactiveIntervalInSeconds = MAX_INACTIVE_INTERVAL_IN_SECONDS) + static class SpringSessionGemFireServerConfiguration { + + static final int MAX_CONNECTIONS = 50; + static final String SERVER_HOSTNAME = "localhost"; + + @Bean + PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() { + return new PropertySourcesPlaceholderConfigurer(); + } + + @Bean + Properties gemfireProperties() { + Properties gemfireProperties = new Properties(); + + gemfireProperties.setProperty("name", SpringSessionGemFireServerConfiguration.class.getName()); + gemfireProperties.setProperty("mcast-port", "0"); + gemfireProperties.setProperty("log-file", "server.log"); + gemfireProperties.setProperty("log-level", GEMFIRE_LOG_LEVEL); + + return gemfireProperties; + } + + @Bean + CacheFactoryBean gemfireCache() { + CacheFactoryBean cacheFactory = new CacheFactoryBean(); + + cacheFactory.setProperties(gemfireProperties()); + cacheFactory.setUseBeanFactoryLocator(false); + + return cacheFactory; + } + + @Bean + CacheServerFactoryBean gemfireCacheServer(Cache gemfireCache, + @Value("${spring.session.data.gemfire.port:"+DEFAULT_GEMFIRE_SERVER_PORT+"}") int port) { + + CacheServerFactoryBean cacheServerFactory = new CacheServerFactoryBean(); + + cacheServerFactory.setAutoStartup(true); + cacheServerFactory.setBindAddress(SERVER_HOSTNAME); + cacheServerFactory.setCache(gemfireCache); + cacheServerFactory.setMaxConnections(MAX_CONNECTIONS); + cacheServerFactory.setPort(port); + + return cacheServerFactory; + } + + public static void main(final String[] args) throws IOException { + new AnnotationConfigApplicationContext(SpringSessionGemFireServerConfiguration.class).registerShutdownHook(); + writeProcessControlFile(WORKING_DIRECTORY); + } + } + +} diff --git a/spring-session/src/integration-test/java/org/springframework/session/data/gemfire/GemFireOperationsSessionRepositoryIntegrationTests.java b/spring-session/src/integration-test/java/org/springframework/session/data/gemfire/GemFireOperationsSessionRepositoryIntegrationTests.java new file mode 100644 index 00000000..7265f198 --- /dev/null +++ b/spring-session/src/integration-test/java/org/springframework/session/data/gemfire/GemFireOperationsSessionRepositoryIntegrationTests.java @@ -0,0 +1,304 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * 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. + */ + +package org.springframework.session.data.gemfire; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.session.data.gemfire.GemFireOperationsSessionRepository.GemFireSession; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.context.annotation.Bean; +import org.springframework.data.gemfire.CacheFactoryBean; +import org.springframework.session.ExpiringSession; +import org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; + +import com.gemstone.gemfire.cache.DataPolicy; +import com.gemstone.gemfire.cache.ExpirationAction; +import com.gemstone.gemfire.cache.Region; +import com.gemstone.gemfire.cache.query.Query; +import com.gemstone.gemfire.cache.query.QueryService; +import com.gemstone.gemfire.cache.query.SelectResults; +import com.gemstone.gemfire.pdx.PdxReader; +import com.gemstone.gemfire.pdx.PdxSerializable; +import com.gemstone.gemfire.pdx.PdxWriter; + +/** + * The GemFireOperationsSessionRepositoryIntegrationTests class is a test suite of test cases testing + * the findByPrincipalName query method on the GemFireOpeationsSessionRepository class. + * + * @author John Blum + * @see org.junit.Test + * @see org.junit.runner.RunWith + * @see org.springframework.session.data.gemfire.AbstractGemFireIntegrationTests + * @see org.springframework.session.data.gemfire.GemFireOperationsSessionRepository + * @see org.springframework.test.context.ContextConfiguration + * @see org.springframework.test.context.junit4.SpringJUnit4ClassRunner + * @see org.springframework.test.context.web.WebAppConfiguration + * @since 1.1.0 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration +@WebAppConfiguration +@SuppressWarnings("unused") +public class GemFireOperationsSessionRepositoryIntegrationTests extends AbstractGemFireIntegrationTests { + + private static final int MAX_INACTIVE_INTERVAL_IN_SECONDS = 300; + + private static final String GEMFIRE_LOG_LEVEL = "warning"; + private static final String SPRING_SESSION_GEMFIRE_REGION_NAME = "TestPartitionedSessions"; + + @Before + public void setup() { + assertThat(gemfireCache).isNotNull(); + assertThat(gemfireSessionRepository).isNotNull(); + assertThat(gemfireSessionRepository.getMaxInactiveIntervalInSeconds()).isEqualTo( + MAX_INACTIVE_INTERVAL_IN_SECONDS); + + Region sessionRegion = gemfireCache.getRegion(SPRING_SESSION_GEMFIRE_REGION_NAME); + + assertRegion(sessionRegion, SPRING_SESSION_GEMFIRE_REGION_NAME, DataPolicy.PARTITION); + assertEntryIdleTimeout(sessionRegion, ExpirationAction.INVALIDATE, MAX_INACTIVE_INTERVAL_IN_SECONDS); + } + + protected Map doFindByPrincipalName(String principalName) { + return gemfireSessionRepository.findByPrincipalName(principalName); + } + + @SuppressWarnings("unchecked") + protected Map doFindByPrincipalName(String regionName, String principalName) { + try { + Region region = gemfireCache.getRegion(regionName); + + assertThat(region).isNotNull(); + + QueryService queryService = region.getRegionService().getQueryService(); + + String queryString = String.format("SELECT s FROM %1$s s WHERE s.principalName = $1", region.getFullPath()); + + Query query = queryService.newQuery(queryString); + + SelectResults results = (SelectResults) query.execute( + new Object[] { principalName }); + + Map sessions = new HashMap(results.size()); + + for (ExpiringSession session : results.asList()) { + sessions.put(session.getId(), session); + } + + return sessions; + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + protected boolean enableQueryDebugging() { + return true; + } + + @Test + public void findSessionsByPrincipalName() { + ExpiringSession sessionOne = save(touch(createSession("robWinch"))); + ExpiringSession sessionTwo = save(touch(createSession("johnBlum"))); + ExpiringSession sessionThree = save(touch(createSession("robWinch"))); + ExpiringSession sessionFour = save(touch(createSession("johnBlum"))); + ExpiringSession sessionFive = save(touch(createSession("robWinch"))); + + assertThat(get(sessionOne.getId())).isEqualTo(sessionOne); + assertThat(get(sessionTwo.getId())).isEqualTo(sessionTwo); + assertThat(get(sessionThree.getId())).isEqualTo(sessionThree); + assertThat(get(sessionFour.getId())).isEqualTo(sessionFour); + assertThat(get(sessionFive.getId())).isEqualTo(sessionFive); + + Map johnBlumSessions = doFindByPrincipalName("johnBlum"); + + assertThat(johnBlumSessions).isNotNull(); + assertThat(johnBlumSessions.size()).isEqualTo(2); + assertThat(johnBlumSessions.containsKey(sessionOne.getId())).isFalse(); + assertThat(johnBlumSessions.containsKey(sessionThree.getId())).isFalse(); + assertThat(johnBlumSessions.containsKey(sessionFive.getId())).isFalse(); + assertThat(johnBlumSessions.get(sessionTwo.getId())).isEqualTo(sessionTwo); + assertThat(johnBlumSessions.get(sessionFour.getId())).isEqualTo(sessionFour); + + Map robWinchSessions = doFindByPrincipalName("robWinch"); + + assertThat(robWinchSessions).isNotNull(); + assertThat(robWinchSessions.size()).isEqualTo(3); + assertThat(robWinchSessions.containsKey(sessionTwo.getId())).isFalse(); + assertThat(robWinchSessions.containsKey(sessionFour.getId())).isFalse(); + assertThat(robWinchSessions.get(sessionOne.getId())).isEqualTo(sessionOne); + assertThat(robWinchSessions.get(sessionThree.getId())).isEqualTo(sessionThree); + assertThat(robWinchSessions.get(sessionFive.getId())).isEqualTo(sessionFive); + } + + @Test + public void findsNoSessionsByNonExistingPrincipal() { + Map nonExistingPrincipalSessions = doFindByPrincipalName("nonExistingPrincipalName"); + + assertThat(nonExistingPrincipalSessions).isNotNull(); + assertThat(nonExistingPrincipalSessions.isEmpty()).isTrue(); + } + + @Test + public void saveAndReadSessionWithAttributes() { + ExpiringSession expectedSession = gemfireSessionRepository.createSession(); + + assertThat(expectedSession).isInstanceOf(GemFireSession.class); + + ((GemFireSession) expectedSession).setPrincipalName("jblum"); + + List expectedAttributeNames = Arrays.asList( + "booleanAttribute", "numericAttribute", "stringAttribute", "personAttribute"); + + Person jonDoe = new Person("Jon", "Doe"); + + expectedSession.setAttribute(expectedAttributeNames.get(0), true); + expectedSession.setAttribute(expectedAttributeNames.get(1), Math.PI); + expectedSession.setAttribute(expectedAttributeNames.get(2), "test"); + expectedSession.setAttribute(expectedAttributeNames.get(3), jonDoe); + + gemfireSessionRepository.save(touch(expectedSession)); + + ExpiringSession savedSession = gemfireSessionRepository.getSession(expectedSession.getId()); + + assertThat(savedSession).isEqualTo(expectedSession); + assertThat(savedSession).isInstanceOf(GemFireSession.class); + assertThat(((GemFireSession) savedSession).getPrincipalName()).isEqualTo("jblum"); + + assertThat(savedSession.getAttributeNames().containsAll(expectedAttributeNames)).as( + String.format("Expected (%1$s); but was (%2$s)", expectedAttributeNames,savedSession.getAttributeNames())) + .isTrue(); + + assertThat(Boolean.valueOf(String.valueOf(savedSession.getAttribute(expectedAttributeNames.get(0))))).isTrue(); + assertThat(Double.valueOf(String.valueOf(savedSession.getAttribute(expectedAttributeNames.get(1))))) + .isEqualTo(Math.PI); + assertThat(String.valueOf(savedSession.getAttribute(expectedAttributeNames.get(2)))).isEqualTo("test"); + assertThat(savedSession.getAttribute(expectedAttributeNames.get(3))).isEqualTo(jonDoe); + } + + @EnableGemFireHttpSession(regionName = SPRING_SESSION_GEMFIRE_REGION_NAME, + maxInactiveIntervalInSeconds = MAX_INACTIVE_INTERVAL_IN_SECONDS) + static class SpringSessionGemFireConfiguration { + + @Bean + Properties gemfireProperties() { + Properties gemfireProperties = new Properties(); + + gemfireProperties.setProperty("name", GemFireOperationsSessionRepositoryIntegrationTests.class.getName()); + gemfireProperties.setProperty("mcast-port", "0"); + gemfireProperties.setProperty("log-level", GEMFIRE_LOG_LEVEL); + + return gemfireProperties; + } + + @Bean + CacheFactoryBean gemfireCache() { + CacheFactoryBean gemfireCache = new CacheFactoryBean(); + + gemfireCache.setLazyInitialize(false); + gemfireCache.setProperties(gemfireProperties()); + gemfireCache.setUseBeanFactoryLocator(false); + + return gemfireCache; + } + } + + public static class Person implements PdxSerializable { + + private String firstName; + private String lastName; + + public Person() { + } + + public Person(String firstName, String lastName) { + this.firstName = validate(firstName); + this.lastName = validate(lastName); + } + + private String validate(String value) { + Assert.hasText(value, String.format("The String value (%1$s) must be specified!", value)); + return value; + } + + public String getFirstName() { + return firstName; + } + + public String getLastName() { + return lastName; + } + + public String getName() { + return String.format("%1$s %2$s", getFirstName(), getLastName()); + } + + public void toData(PdxWriter pdxWriter) { + pdxWriter.writeString("firstName", getFirstName()); + pdxWriter.writeString("lastName", getLastName()); + } + + public void fromData(final PdxReader pdxReader) { + this.firstName = pdxReader.readString("firstName"); + this.lastName = pdxReader.readString("lastName"); + } + + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + + if (!(obj instanceof Person)) { + return false; + } + + Person that = (Person) obj; + + return ObjectUtils.nullSafeEquals(this.getFirstName(), that.getFirstName()) + && ObjectUtils.nullSafeEquals(this.getLastName(), that.getLastName()); + } + + @Override + public int hashCode() { + int hashValue = 17; + hashValue = 37 * hashValue + ObjectUtils.nullSafeHashCode(getFirstName()); + hashValue = 37 * hashValue + ObjectUtils.nullSafeHashCode(getLastName()); + return hashValue; + } + + @Override + public String toString() { + return getName(); + } + } + +} diff --git a/spring-session/src/integration-test/java/org/springframework/session/data/gemfire/config/annotation/web/http/EnableGemFireHttpSessionEventsIntegrationTests.java b/spring-session/src/integration-test/java/org/springframework/session/data/gemfire/config/annotation/web/http/EnableGemFireHttpSessionEventsIntegrationTests.java new file mode 100644 index 00000000..e6a760b0 --- /dev/null +++ b/spring-session/src/integration-test/java/org/springframework/session/data/gemfire/config/annotation/web/http/EnableGemFireHttpSessionEventsIntegrationTests.java @@ -0,0 +1,253 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * 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. + */ + +package org.springframework.session.data.gemfire.config.annotation.web.http; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Properties; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.data.gemfire.CacheFactoryBean; +import org.springframework.session.ExpiringSession; +import org.springframework.session.data.gemfire.AbstractGemFireIntegrationTests; +import org.springframework.session.data.gemfire.support.GemFireUtils; +import org.springframework.session.events.AbstractSessionEvent; +import org.springframework.session.events.SessionCreatedEvent; +import org.springframework.session.events.SessionDeletedEvent; +import org.springframework.session.events.SessionExpiredEvent; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; + +import com.gemstone.gemfire.cache.DataPolicy; +import com.gemstone.gemfire.cache.ExpirationAction; +import com.gemstone.gemfire.cache.Region; +import com.gemstone.gemfire.cache.RegionShortcut; + +/** + * The EnableGemFireHttpSessionEventsIntegrationTests class is a test suite of test cases testing the Session Event + * functionality and behavior of the GemFireOperationsSessionRepository and GemFire's configuration. + * + * @author John Blum + * @see org.junit.Test + * @see org.junit.runner.RunWith + * @see org.springframework.session.ExpiringSession + * @see org.springframework.session.data.gemfire.AbstractGemFireIntegrationTests + * @see org.springframework.session.data.gemfire.GemFireOperationsSessionRepository + * @see org.springframework.session.events.SessionCreatedEvent + * @see org.springframework.session.events.SessionDeletedEvent + * @see org.springframework.session.events.SessionExpiredEvent + * @see org.springframework.test.context.ContextConfiguration + * @see org.springframework.test.context.junit4.SpringJUnit4ClassRunner + * @see org.springframework.test.context.web.WebAppConfiguration + * @see com.gemstone.gemfire.cache.Region + * @since 1.1.0 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration +@WebAppConfiguration +@SuppressWarnings("unused") +public class EnableGemFireHttpSessionEventsIntegrationTests extends AbstractGemFireIntegrationTests { + + private static final int MAX_INACTIVE_INTERVAL_IN_SECONDS = 1; + + private static final String GEMFIRE_LOG_LEVEL = "warning"; + private static final String SPRING_SESSION_GEMFIRE_REGION_NAME = "TestReplicatedSessions"; + + @Autowired + private SessionEventListener sessionEventListener; + + @Before + public void setup() { + assertThat(GemFireUtils.isPeer(gemfireCache)).isTrue(); + assertThat(gemfireSessionRepository).isNotNull(); + assertThat(gemfireSessionRepository.getMaxInactiveIntervalInSeconds()).isEqualTo( + MAX_INACTIVE_INTERVAL_IN_SECONDS); + assertThat(sessionEventListener).isNotNull(); + + Region sessionRegion = gemfireCache.getRegion(SPRING_SESSION_GEMFIRE_REGION_NAME); + + assertRegion(sessionRegion, SPRING_SESSION_GEMFIRE_REGION_NAME, DataPolicy.REPLICATE); + assertEntryIdleTimeout(sessionRegion, ExpirationAction.INVALIDATE, MAX_INACTIVE_INTERVAL_IN_SECONDS); + } + + @After + public void tearDown() { + sessionEventListener.getSessionEvent(); + } + + @Test + public void sessionCreatedEvent() { + final long beforeOrAtCreationTime = System.currentTimeMillis(); + + ExpiringSession expectedSession = save(createSession()); + + AbstractSessionEvent sessionEvent = sessionEventListener.getSessionEvent(); + + assertThat(sessionEvent).isInstanceOf(SessionCreatedEvent.class); + + ExpiringSession createdSession = sessionEvent.getSession(); + + assertThat(createdSession).isEqualTo(expectedSession); + assertThat(createdSession.getId()).isNotNull(); + assertThat(createdSession.getCreationTime()).isGreaterThanOrEqualTo(beforeOrAtCreationTime); + assertThat(createdSession.getLastAccessedTime()).isEqualTo(createdSession.getCreationTime()); + assertThat(createdSession.getMaxInactiveIntervalInSeconds()).isEqualTo(MAX_INACTIVE_INTERVAL_IN_SECONDS); + assertThat(createdSession.isExpired()).isFalse(); + } + + @Test + public void getExistingNonExpiredSession() { + ExpiringSession expectedSession = save(touch(createSession())); + + assertThat(expectedSession.isExpired()).isFalse(); + + // NOTE though unlikely, a possible race condition exists between save and get... + ExpiringSession savedSession = gemfireSessionRepository.getSession(expectedSession.getId()); + + assertThat(savedSession).isEqualTo(expectedSession); + } + + @Test + public void getExistingExpiredSession() { + ExpiringSession expectedSession = save(expire(createSession())); + + AbstractSessionEvent sessionEvent = sessionEventListener.getSessionEvent(); + + assertThat(sessionEvent).isInstanceOf(SessionCreatedEvent.class); + + ExpiringSession createdSession = sessionEvent.getSession(); + + assertThat(createdSession).isEqualTo(expectedSession); + assertThat(createdSession.isExpired()).isTrue(); + assertThat(gemfireSessionRepository.getSession(createdSession.getId())).isNull(); + } + + @Test + public void getNonExistingSession() { + assertThat(gemfireSessionRepository.getSession(UUID.randomUUID().toString())).isNull(); + } + + @Test + public void deleteExistingNonExpiredSession() { + ExpiringSession expectedSession = save(touch(createSession())); + ExpiringSession savedSession = gemfireSessionRepository.getSession(expectedSession.getId()); + + assertThat(savedSession).isEqualTo(expectedSession); + assertThat(savedSession.isExpired()).isFalse(); + + gemfireSessionRepository.delete(savedSession.getId()); + + AbstractSessionEvent sessionEvent = sessionEventListener.getSessionEvent(); + + assertThat(sessionEvent).isInstanceOf(SessionDeletedEvent.class); + assertThat(sessionEvent.getSessionId()).isEqualTo(savedSession.getId()); + + ExpiringSession deletedSession = sessionEvent.getSession(); + + assertThat(deletedSession).isEqualTo(savedSession); + assertThat(gemfireSessionRepository.getSession(deletedSession.getId())).isNull(); + } + + @Test + public void deleteExistingExpiredSession() { + ExpiringSession expectedSession = save(createSession()); + + AbstractSessionEvent sessionEvent = sessionEventListener.getSessionEvent(); + + assertThat(sessionEvent).isInstanceOf(SessionCreatedEvent.class); + + ExpiringSession createdSession = sessionEvent.getSession(); + + assertThat(createdSession).isEqualTo(expectedSession); + + sessionEvent = sessionEventListener.waitForSessionEvent(TimeUnit.SECONDS.toMillis( + gemfireSessionRepository.getMaxInactiveIntervalInSeconds() + 1)); + + assertThat(sessionEvent).isInstanceOf(SessionExpiredEvent.class); + + ExpiringSession expiredSession = sessionEvent.getSession(); + + assertThat(expiredSession).isEqualTo(createdSession); + assertThat(expiredSession.isExpired()).isTrue(); + + gemfireSessionRepository.delete(expectedSession.getId()); + + sessionEvent = sessionEventListener.getSessionEvent(); + + assertThat(sessionEvent).isInstanceOf(SessionDeletedEvent.class); + assertThat(sessionEvent.getSession()).isNull(); + assertThat(sessionEvent.getSessionId()).isEqualTo(expiredSession.getId()); + assertThat(gemfireSessionRepository.getSession(sessionEvent.getSessionId())).isNull(); + } + + @Test + public void deleteNonExistingSession() { + String expectedSessionId = UUID.randomUUID().toString(); + + assertThat(gemfireSessionRepository.getSession(expectedSessionId)).isNull(); + + gemfireSessionRepository.delete(expectedSessionId); + + AbstractSessionEvent sessionEvent = sessionEventListener.getSessionEvent(); + + assertThat(sessionEvent).isInstanceOf(SessionDeletedEvent.class); + assertThat(sessionEvent.getSession()).isNull(); + assertThat(sessionEvent.getSessionId()).isEqualTo(expectedSessionId); + } + + @EnableGemFireHttpSession(regionName = SPRING_SESSION_GEMFIRE_REGION_NAME, + maxInactiveIntervalInSeconds = MAX_INACTIVE_INTERVAL_IN_SECONDS, + serverRegionShortcut = RegionShortcut.REPLICATE) + static class SpringSessionGemFireConfiguration { + + @Bean + Properties gemfireProperties() { + Properties gemfireProperties = new Properties(); + + gemfireProperties.setProperty("name", EnableGemFireHttpSessionEventsIntegrationTests.class.getName()); + gemfireProperties.setProperty("mcast-port", "0"); + gemfireProperties.setProperty("log-level", GEMFIRE_LOG_LEVEL); + + return gemfireProperties; + } + + @Bean + CacheFactoryBean gemfireCache() { + CacheFactoryBean gemfireCache = new CacheFactoryBean(); + + gemfireCache.setLazyInitialize(false); + gemfireCache.setProperties(gemfireProperties()); + gemfireCache.setUseBeanFactoryLocator(false); + + return gemfireCache; + } + + @Bean + SessionEventListener sessionEventListener() { + return new SessionEventListener(); + } + } + +} diff --git a/spring-session/src/integration-test/java/org/springframework/session/data/gemfire/config/annotation/web/http/GemFireHttpSessionConfigurationXmlTests.java b/spring-session/src/integration-test/java/org/springframework/session/data/gemfire/config/annotation/web/http/GemFireHttpSessionConfigurationXmlTests.java new file mode 100644 index 00000000..77c90a61 --- /dev/null +++ b/spring-session/src/integration-test/java/org/springframework/session/data/gemfire/config/annotation/web/http/GemFireHttpSessionConfigurationXmlTests.java @@ -0,0 +1,67 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * 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. + */ + +package org.springframework.session.data.gemfire.config.annotation.web.http; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.session.ExpiringSession; +import org.springframework.session.data.gemfire.AbstractGemFireIntegrationTests; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; + +import com.gemstone.gemfire.cache.Cache; +import com.gemstone.gemfire.cache.DataPolicy; +import com.gemstone.gemfire.cache.ExpirationAction; +import com.gemstone.gemfire.cache.Region; + +/** + * The GemFireHttpSessionConfigurationXmlTests class is a test suite of test cases testing the configuration of + * Spring Session backed by GemFire using XML configuration meta-data. + * + * @author John Blum + * @see org.junit.Test + * @see org.springframework.session.ExpiringSession + * @see org.springframework.session.data.gemfire.AbstractGemFireIntegrationTests + * @see org.springframework.test.context.ContextConfiguration + * @see org.springframework.test.context.web.WebAppConfiguration + * @see org.springframework.test.context.junit4.SpringJUnit4ClassRunner + * @since 1.1.0 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration +@WebAppConfiguration +@SuppressWarnings("unused") +public class GemFireHttpSessionConfigurationXmlTests extends AbstractGemFireIntegrationTests { + + @Autowired + private Cache gemfireCache; + + @Test + public void gemfireCacheConfigurationIsValid() { + assertThat(gemfireCache).isNotNull(); + + Region example = gemfireCache.getRegion("Example"); + + assertRegion(example, "Example", DataPolicy.NORMAL); + assertEntryIdleTimeout(example, ExpirationAction.INVALIDATE, 3600); + } + +} diff --git a/spring-session/src/main/java/org/springframework/session/data/gemfire/AbstractGemFireOperationsSessionRepository.java b/spring-session/src/main/java/org/springframework/session/data/gemfire/AbstractGemFireOperationsSessionRepository.java new file mode 100644 index 00000000..464ca9c6 --- /dev/null +++ b/spring-session/src/main/java/org/springframework/session/data/gemfire/AbstractGemFireOperationsSessionRepository.java @@ -0,0 +1,766 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * 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. + */ + +package org.springframework.session.data.gemfire; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.ApplicationEventPublisherAware; +import org.springframework.data.gemfire.GemfireAccessor; +import org.springframework.data.gemfire.GemfireOperations; +import org.springframework.session.ExpiringSession; +import org.springframework.session.FindByPrincipalNameSessionRepository; +import org.springframework.session.Session; +import org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration; +import org.springframework.session.events.SessionCreatedEvent; +import org.springframework.session.events.SessionDeletedEvent; +import org.springframework.session.events.SessionDestroyedEvent; +import org.springframework.session.events.SessionExpiredEvent; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +import com.gemstone.gemfire.DataSerializable; +import com.gemstone.gemfire.DataSerializer; +import com.gemstone.gemfire.Delta; +import com.gemstone.gemfire.Instantiator; +import com.gemstone.gemfire.InvalidDeltaException; +import com.gemstone.gemfire.cache.EntryEvent; +import com.gemstone.gemfire.cache.Region; +import com.gemstone.gemfire.cache.util.CacheListenerAdapter; + +/** + * AbstractGemFireOperationsSessionRepository is an abstract base class encapsulating functionality common + * to all implementations that support SessionRepository operations backed by GemFire. + * + * @author John Blum + * @see org.springframework.beans.factory.InitializingBean + * @see org.springframework.context.ApplicationEventPublisher + * @see org.springframework.context.ApplicationEventPublisherAware + * @see org.springframework.data.gemfire.GemfireAccessor + * @see org.springframework.data.gemfire.GemfireOperations + * @see org.springframework.session.ExpiringSession + * @see org.springframework.session.FindByPrincipalNameSessionRepository + * @see org.springframework.session.Session + * @see org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration + * @see com.gemstone.gemfire.cache.Region + * @see com.gemstone.gemfire.cache.util.CacheListenerAdapter + * @since 1.1.0 + */ +public abstract class AbstractGemFireOperationsSessionRepository extends CacheListenerAdapter + implements InitializingBean, FindByPrincipalNameSessionRepository, + ApplicationEventPublisherAware { + + private int maxInactiveIntervalInSeconds = GemFireHttpSessionConfiguration.DEFAULT_MAX_INACTIVE_INTERVAL_IN_SECONDS; + + private ApplicationEventPublisher applicationEventPublisher = new ApplicationEventPublisher() { + public void publishEvent(ApplicationEvent event) { + } + }; + + private final GemfireOperations template; + + protected final Log logger = newLogger(); + + private String fullyQualifiedRegionName; + + /** + * Constructs an instance of AbstractGemFireOperationsSessionRepository with a required GemfireOperations instance + * used to perform GemFire data access operations and interactions supporting the SessionRepository operations. + * + * @param template the GemfireOperations instance used to interact with GemFire. + * @see org.springframework.data.gemfire.GemfireOperations + */ + public AbstractGemFireOperationsSessionRepository(GemfireOperations template) { + Assert.notNull(template, "GemfireOperations must not be null"); + this.template = template; + } + + /** + * Used for testing purposes only to override the Log implementation with a mock. + * + * @return an instance of Log constructed from Apache commons-logging LogFactory. + * @see org.apache.commons.logging.LogFactory#getLog(Class) + */ + Log newLogger() { + return LogFactory.getLog(getClass()); + } + + /** + * Sets the ApplicationEventPublisher used to publish Session events corresponding to GemFire cache events. + * + * @param applicationEventPublisher the Spring ApplicationEventPublisher used to publish Session-based events. + * @see org.springframework.context.ApplicationEventPublisher + */ + public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { + Assert.notNull(applicationEventPublisher, "ApplicationEventPublisher must not be null"); + this.applicationEventPublisher = applicationEventPublisher; + } + + /** + * Gets the ApplicationEventPublisher used to publish Session events corresponding to GemFire cache events. + * + * @return the Spring ApplicationEventPublisher used to publish Session-based events. + * @see org.springframework.context.ApplicationEventPublisher + */ + protected ApplicationEventPublisher getApplicationEventPublisher() { + return applicationEventPublisher; + } + + /** + * Gets the fully-qualified name of the GemFire cache {@link Region} used to store and manage Session data. + * + * @return a String indicating the fully qualified name of the GemFire cache {@link Region} used to store + * and manage Session data. + */ + protected String getFullyQualifiedRegionName() { + return fullyQualifiedRegionName; + } + + /** + * Sets the maximum interval in seconds in which a Session can remain inactive before it is considered expired. + * + * @param maxInactiveIntervalInSeconds an integer value specifying the maximum interval in seconds that a Session + * can remain inactive before it is considered expired. + */ + public void setMaxInactiveIntervalInSeconds(int maxInactiveIntervalInSeconds) { + this.maxInactiveIntervalInSeconds = maxInactiveIntervalInSeconds; + } + + /** + * Gets the maximum interval in seconds in which a Session can remain inactive before it is considered expired. + * + * @return an integer value specifying the maximum interval in seconds that a Session can remain inactive + * before it is considered expired. + */ + public int getMaxInactiveIntervalInSeconds() { + return maxInactiveIntervalInSeconds; + } + + /** + * Gets a reference to the GemfireOperations (template) used to perform data access operations + * and other interactions on the GemFire cache {@link Region} backing this SessionRepository. + * + * @return a reference to the GemfireOperations used to interact with GemFire. + * @see org.springframework.data.gemfire.GemfireOperations + */ + public GemfireOperations getTemplate() { + return template; + } + + /** + * Callback method during Spring bean initialization that will capture the fully-qualified name + * of the GemFire cache {@link Region} used to manage Session state and register this SessionRepository + * as a GemFire {@link com.gemstone.gemfire.cache.CacheListener}. + * + * @throws Exception if an error occurs during the initialization process. + */ + public void afterPropertiesSet() throws Exception { + GemfireOperations template = getTemplate(); + + Assert.isInstanceOf(GemfireAccessor.class, template); + + Region region = ((GemfireAccessor) template).getRegion(); + + fullyQualifiedRegionName = region.getFullPath(); + region.getAttributesMutator().addCacheListener(this); + } + + /** + * Callback method triggered when an entry is created in the GemFire cache {@link Region}. + * + * @param event an EntryEvent containing the details of the cache operation. + * @see com.gemstone.gemfire.cache.EntryEvent + * @see #handleCreated(String, ExpiringSession) + */ + @Override + public void afterCreate(EntryEvent event) { + handleCreated(event.getKey().toString(), event.getNewValue()); + } + + /** + * Callback method triggered when an entry is destroyed in the GemFire cache {@link Region}. + * + * @param event an EntryEvent containing the details of the cache operation. + * @see com.gemstone.gemfire.cache.EntryEvent + * @see #handleDestroyed(String, ExpiringSession) + */ + @Override + public void afterDestroy(EntryEvent event) { + handleDestroyed(event.getKey().toString(), event.getOldValue()); + } + + /** + * Callback method triggered when an entry is invalidated in the GemFire cache {@link Region}. + * + * @param event an EntryEvent containing the details of the cache operation. + * @see com.gemstone.gemfire.cache.EntryEvent + * @see #handleExpired(String, ExpiringSession) + */ + @Override + public void afterInvalidate(EntryEvent event) { + handleExpired(event.getKey().toString(), event.getOldValue()); + } + + /** + * Causes Session created events to be published to the Spring application context. + * + * @param sessionId a String indicating the ID of the Session. + * @param session a reference to the Session triggering the event. + * @see org.springframework.session.events.SessionCreatedEvent + * @see org.springframework.session.ExpiringSession + * @see #publishEvent(ApplicationEvent) + */ + protected void handleCreated(String sessionId, ExpiringSession session) { + publishEvent(session != null ? new SessionCreatedEvent(this, session) + : new SessionCreatedEvent(this, sessionId)); + } + + /** + * Causes Session deleted events to be published to the Spring application context. + * + * @param sessionId a String indicating the ID of the Session. + * @param session a reference to the Session triggering the event. + * @see org.springframework.session.events.SessionDeletedEvent + * @see org.springframework.session.ExpiringSession + * @see #publishEvent(ApplicationEvent) + */ + protected void handleDeleted(String sessionId, ExpiringSession session) { + publishEvent(session != null ? new SessionDeletedEvent(this, session) + : new SessionDeletedEvent(this, sessionId)); + } + + /** + * Causes Session destroyed events to be published to the Spring application context. + * + * @param sessionId a String indicating the ID of the Session. + * @param session a reference to the Session triggering the event. + * @see org.springframework.session.events.SessionDestroyedEvent + * @see org.springframework.session.ExpiringSession + * @see #publishEvent(ApplicationEvent) + */ + protected void handleDestroyed(String sessionId, ExpiringSession session) { + publishEvent(session != null ? new SessionDestroyedEvent(this, session) + : new SessionDestroyedEvent(this, sessionId)); + } + + /** + * Causes Session expired events to be published to the Spring application context. + * + * @param sessionId a String indicating the ID of the Session. + * @param session a reference to the Session triggering the event. + * @see org.springframework.session.events.SessionExpiredEvent + * @see org.springframework.session.ExpiringSession + * @see #publishEvent(ApplicationEvent) + */ + protected void handleExpired(String sessionId, ExpiringSession session) { + publishEvent(session != null ? new SessionExpiredEvent(this, session) + : new SessionExpiredEvent(this, sessionId)); + } + + /** + * Publishes the specified ApplicationEvent to the Spring application context. + * + * @param event the ApplicationEvent to publish. + * @see org.springframework.context.ApplicationEventPublisher#publishEvent(ApplicationEvent) + * @see org.springframework.context.ApplicationEvent + */ + protected void publishEvent(ApplicationEvent event) { + try { + getApplicationEventPublisher().publishEvent(event); + } + catch (Throwable t) { + logger.error(String.format("error occurred publishing event (%1$s)", event), t); + } + } + + /** + * GemFireSession is a GemFire representation model of a Spring {@link ExpiringSession} for storing and accessing + * Session state information in GemFire. This class implements GemFire's {@link DataSerializable} interface + * to better handle replication of Session information across the GemFire cluster. + * + * @see java.lang.Comparable + * @see org.springframework.session.ExpiringSession + * @see org.springframework.session.data.gemfire.AbstractGemFireOperationsSessionRepository.GemFireSessionAttributes + * @see com.gemstone.gemfire.DataSerializable + * @see com.gemstone.gemfire.DataSerializer + * @see com.gemstone.gemfire.Delta + * @see com.gemstone.gemfire.Instantiator + */ + public static class GemFireSession implements Comparable, DataSerializable, Delta, ExpiringSession { + + protected static final boolean DEFAULT_ALLOW_JAVA_SERIALIZATION = true; + + protected static final DateFormat TO_STRING_DATE_FORMAT = new SimpleDateFormat("YYYY-MM-dd-HH-mm-ss"); + + static { + Instantiator.register(new Instantiator(GemFireSession.class, 800813552) { + @Override public DataSerializable newInstance() { + return new GemFireSession(); + } + }); + } + + private transient boolean delta = false; + + private int maxInactiveIntervalInSeconds; + + private long creationTime; + private long lastAccessedTime; + + private transient final GemFireSessionAttributes sessionAttributes = new GemFireSessionAttributes(this); + + private String id; + + /* (non-Javadoc) */ + protected GemFireSession() { + this(UUID.randomUUID().toString()); + } + + /* (non-Javadoc) */ + protected GemFireSession(String id) { + this.id = validateId(id); + this.creationTime = System.currentTimeMillis(); + this.lastAccessedTime = this.creationTime; + } + + /* (non-Javadoc) */ + protected GemFireSession(ExpiringSession session) { + Assert.notNull(session, "The ExpiringSession to copy cannot be null"); + + this.id = session.getId(); + this.creationTime = session.getCreationTime(); + this.lastAccessedTime = session.getLastAccessedTime(); + this.maxInactiveIntervalInSeconds = session.getMaxInactiveIntervalInSeconds(); + this.sessionAttributes.from(session); + } + + /* (non-Javadoc) */ + public static GemFireSession create(int maxInactiveIntervalInSeconds) { + GemFireSession session = new GemFireSession(); + session.setMaxInactiveIntervalInSeconds(maxInactiveIntervalInSeconds); + return session; + } + + /* (non-Javadoc) */ + public static GemFireSession from(ExpiringSession expiringSession) { + GemFireSession session = new GemFireSession(expiringSession); + session.setLastAccessedTime(System.currentTimeMillis()); + return session; + } + + /* (non-Javadoc) */ + private String validateId(String id) { + Assert.hasText(id, "ID must be specified"); + return id; + } + + /* (non-Javadoc) */ + protected boolean allowJavaSerialization() { + return DEFAULT_ALLOW_JAVA_SERIALIZATION; + } + + /* (non-Javadoc) */ + public synchronized String getId() { + return id; + } + + /* (non-Javadoc) */ + public synchronized long getCreationTime() { + return creationTime; + } + + /* (non-Javadoc) */ + public void setAttribute(String attributeName, Object attributeValue) { + sessionAttributes.setAttribute(attributeName, attributeValue); + } + + /* (non-Javadoc) */ + public void removeAttribute(String attributeName) { + sessionAttributes.removeAttribute(attributeName); + } + + /* (non-Javadoc) */ + public T getAttribute(String attributeName) { + return sessionAttributes.getAttribute(attributeName); + } + + /* (non-Javadoc) */ + public Set getAttributeNames() { + return sessionAttributes.getAttributeNames(); + } + + /* (non-Javadoc) */ + public synchronized boolean isExpired() { + long lastAccessedTime = getLastAccessedTime(); + long maxInactiveIntervalInSeconds = getMaxInactiveIntervalInSeconds(); + + return (maxInactiveIntervalInSeconds >= 0 + && (idleTimeout(maxInactiveIntervalInSeconds) >= lastAccessedTime)); + } + + /* (non-Javadoc) */ + private long idleTimeout(long maxInactiveIntervalInSeconds) { + return (System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(maxInactiveIntervalInSeconds)); + } + + /* (non-Javadoc) */ + public synchronized void setLastAccessedTime(long lastAccessedTime) { + this.delta |= (this.lastAccessedTime != lastAccessedTime); + this.lastAccessedTime = lastAccessedTime; + } + + /* (non-Javadoc) */ + public synchronized long getLastAccessedTime() { + return lastAccessedTime; + } + + /* (non-Javadoc) */ + public synchronized void setMaxInactiveIntervalInSeconds(final int maxInactiveIntervalInSeconds) { + this.delta |= (this.maxInactiveIntervalInSeconds != maxInactiveIntervalInSeconds); + this.maxInactiveIntervalInSeconds = maxInactiveIntervalInSeconds; + } + + /* (non-Javadoc) */ + public synchronized int getMaxInactiveIntervalInSeconds() { + return maxInactiveIntervalInSeconds; + } + + /* (non-Javadoc) */ + public synchronized void setPrincipalName(String principalName) { + setAttribute(Session.PRINCIPAL_NAME_ATTRIBUTE_NAME, principalName); + } + + /* (non-Javadoc) */ + public synchronized String getPrincipalName() { + return getAttribute(Session.PRINCIPAL_NAME_ATTRIBUTE_NAME); + } + + /* (non-Javadoc) */ + public synchronized void toData(DataOutput out) throws IOException { + out.writeUTF(getId()); + out.writeLong(getCreationTime()); + out.writeLong(getLastAccessedTime()); + out.writeInt(getMaxInactiveIntervalInSeconds()); + + String principalName = getPrincipalName(); + int length = (StringUtils.hasText(principalName) ? principalName.length() : 0); + + out.writeInt(length); + + if (length > 0) { + out.writeUTF(principalName); + } + + writeObject(sessionAttributes, out); + + this.delta = false; + } + + /* (non-Javadoc) */ + void writeObject(Object obj, DataOutput out) throws IOException { + DataSerializer.writeObject(obj, out, allowJavaSerialization()); + } + + /* (non-Javadoc) */ + public synchronized void fromData(DataInput in) throws ClassNotFoundException, IOException { + id = in.readUTF(); + creationTime = in.readLong(); + setLastAccessedTime(in.readLong()); + setMaxInactiveIntervalInSeconds(in.readInt()); + + int principalNameLength = in.readInt(); + + if (principalNameLength > 0) { + setPrincipalName(in.readUTF()); + } + + sessionAttributes.from(this.readObject(in)); + + this.delta = false; + } + + /* (non-Javadoc) */ + T readObject(DataInput in) throws ClassNotFoundException, IOException { + return DataSerializer.readObject(in); + } + + /* (non-Javadoc) */ + public synchronized boolean hasDelta() { + return (delta || sessionAttributes.hasDelta()); + } + + /* (non-Javadoc) */ + public synchronized void toDelta(DataOutput out) throws IOException { + out.writeLong(getLastAccessedTime()); + out.writeInt(getMaxInactiveIntervalInSeconds()); + sessionAttributes.toDelta(out); + this.delta = false; + } + + /* (non-Javadoc) */ + public synchronized void fromDelta(DataInput in) throws IOException { + setLastAccessedTime(in.readLong()); + setMaxInactiveIntervalInSeconds(in.readInt()); + sessionAttributes.fromDelta(in); + this.delta = false; + } + + /* (non-Javadoc) */ + @SuppressWarnings("all") + public int compareTo(ExpiringSession session) { + return (Long.valueOf(getCreationTime()).compareTo(session.getCreationTime())); + } + + /* (non-Javadoc) */ + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + + if (!(obj instanceof Session)) { + return false; + } + + Session that = (Session) obj; + + return this.getId().equals(that.getId()); + } + + /* (non-Javadoc) */ + @Override + public int hashCode() { + int hashValue = 17; + hashValue = 37 * hashValue + getId().hashCode(); + return hashValue; + } + + /* (non-Javadoc) */ + @Override + public synchronized String toString() { + return String.format("{ @type = %1$s, id = %2$s, creationTime = %3$s, lastAccessedTime = %4$s" + + ", maxInactiveIntervalInSeconds = %5$s, principalName = %6$s }", getClass().getName(), getId(), + toString(getCreationTime()), toString(getLastAccessedTime()), getMaxInactiveIntervalInSeconds(), + getPrincipalName()); + } + + /* (non-Javadoc) */ + private String toString(long timestamp) { + return TO_STRING_DATE_FORMAT.format(new Date(timestamp)); + } + } + + /** + * The GemFireSessionAttributes class is a container for a Session attributes that implements both + * the {@link DataSerializable} and {@link Delta} GemFire interfaces for efficient storage and distribution + * (replication) in GemFire. + * + * @see com.gemstone.gemfire.DataSerializable + * @see com.gemstone.gemfire.DataSerializer + * @see com.gemstone.gemfire.Delta + * @see com.gemstone.gemfire.Instantiator + */ + public static class GemFireSessionAttributes implements DataSerializable, Delta { + + protected static final boolean DEFAULT_ALLOW_JAVA_SERIALIZATION = true; + + static { + Instantiator.register(new Instantiator(GemFireSessionAttributes.class, 800828008) { + @Override public DataSerializable newInstance() { + return new GemFireSessionAttributes(); + } + }); + } + + private transient final Map sessionAttributes = new HashMap(); + private transient final Map sessionAttributeDeltas = new HashMap(); + + private transient final Object lock; + + /* (non-Javadoc) */ + protected GemFireSessionAttributes() { + this.lock = this; + } + + /* (non-Javadoc) */ + protected GemFireSessionAttributes(Object lock) { + this.lock = (lock != null ? lock : this); + } + + /* (non-Javadoc) */ + public void setAttribute(String attributeName, Object attributeValue) { + synchronized (lock) { + if (attributeValue != null) { + if (!attributeValue.equals(sessionAttributes.put(attributeName, attributeValue))) { + sessionAttributeDeltas.put(attributeName, attributeValue); + } + } + else { + removeAttribute(attributeName); + } + } + } + + /* (non-Javadoc) */ + public void removeAttribute(String attributeName) { + synchronized (lock) { + if (sessionAttributes.remove(attributeName) != null) { + sessionAttributeDeltas.put(attributeName, null); + } + } + } + + /* (non-Javadoc) */ + @SuppressWarnings("unchecked") + public T getAttribute(String attributeName) { + synchronized (lock) { + return (T) sessionAttributes.get(attributeName); + } + } + + /* (non-Javadoc) */ + public Set getAttributeNames() { + synchronized (lock) { + return Collections.unmodifiableSet(new HashSet(sessionAttributes.keySet())); + } + } + + /* (non-Javadoc) */ + protected boolean allowJavaSerialization() { + return DEFAULT_ALLOW_JAVA_SERIALIZATION; + } + + /* (non-Javadoc) */ + public void from(Session session) { + synchronized (lock) { + for (String attributeName : session.getAttributeNames()) { + setAttribute(attributeName, session.getAttribute(attributeName)); + } + } + } + + /* (non-Javadoc) */ + public void from(GemFireSessionAttributes sessionAttributes) { + synchronized (lock) { + for (String attributeName : sessionAttributes.getAttributeNames()) { + setAttribute(attributeName, sessionAttributes.getAttribute(attributeName)); + } + } + } + + /* (non-Javadoc) */ + public void toData(DataOutput out) throws IOException { + synchronized (lock) { + Set attributeNames = getAttributeNames(); + + out.writeInt(attributeNames.size()); + + for (String attributeName : attributeNames) { + out.writeUTF(attributeName); + writeObject(getAttribute(attributeName), out); + } + } + } + + /* (non-Javadoc) */ + void writeObject(Object obj, DataOutput out) throws IOException { + DataSerializer.writeObject(obj, out, allowJavaSerialization()); + } + + /* (non-Javadoc) */ + public void fromData(DataInput in) throws IOException, ClassNotFoundException { + synchronized (lock) { + for (int count = in.readInt(); count > 0; count--) { + setAttribute(in.readUTF(), readObject(in)); + } + + sessionAttributeDeltas.clear(); + } + } + + /* (non-Javadoc) */ + T readObject(DataInput in) throws ClassNotFoundException , IOException { + return DataSerializer.readObject(in); + } + + /* (non-Javadoc) */ + public boolean hasDelta() { + synchronized (lock) { + return !sessionAttributeDeltas.isEmpty(); + } + } + + /* (non-Javadoc) */ + public void toDelta(DataOutput out) throws IOException { + synchronized (lock) { + out.writeInt(sessionAttributeDeltas.size()); + + for (Map.Entry entry : sessionAttributeDeltas.entrySet()) { + out.writeUTF(entry.getKey()); + writeObject(entry.getValue(), out); + } + + sessionAttributeDeltas.clear(); + } + } + + /* (non-Javadoc) */ + public void fromDelta(DataInput in) throws InvalidDeltaException, IOException { + synchronized (lock) { + try { + int count = in.readInt(); + + Map deltas = new HashMap(count); + + while (count-- > 0) { + deltas.put(in.readUTF(), readObject(in)); + } + + for (Map.Entry entry : deltas.entrySet()) { + setAttribute(entry.getKey(), entry.getValue()); + sessionAttributeDeltas.remove(entry.getKey()); + } + } + catch (ClassNotFoundException e) { + throw new InvalidDeltaException("class type in data not found", e); + } + } + } + + @Override + public String toString() { + return sessionAttributes.toString(); + } + } + +} diff --git a/spring-session/src/main/java/org/springframework/session/data/gemfire/GemFireOperationsSessionRepository.java b/spring-session/src/main/java/org/springframework/session/data/gemfire/GemFireOperationsSessionRepository.java new file mode 100644 index 00000000..cbab51bc --- /dev/null +++ b/spring-session/src/main/java/org/springframework/session/data/gemfire/GemFireOperationsSessionRepository.java @@ -0,0 +1,135 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * 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. + */ + +package org.springframework.session.data.gemfire; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.data.gemfire.GemfireOperations; +import org.springframework.session.ExpiringSession; + +import com.gemstone.gemfire.cache.query.SelectResults; + +/** + * The GemFireOperationsSessionRepository class is a Spring SessionRepository implementation that interfaces with + * and uses GemFire to back and store Spring Sessions. + * + * @author John Blum + * @see org.springframework.data.gemfire.GemfireOperations + * @see org.springframework.session.ExpiringSession + * @see org.springframework.session.Session + * @see org.springframework.session.data.gemfire.AbstractGemFireOperationsSessionRepository + * @since 1.1.0 + */ +public class GemFireOperationsSessionRepository extends AbstractGemFireOperationsSessionRepository { + + // GemFire OQL query used to look up Sessions by principal name. + protected static final String FIND_SESSIONS_BY_PRINCIPAL_NAME_QUERY = + "SELECT s FROM %1$s s WHERE s.principalName = $1"; + + /** + * Constructs an instance of GemFireOperationsSessionRepository initialized with the required GemfireOperations + * object used to perform data access operations to manage Session state. + * + * @param template the GemfireOperations object used to access and manage Session state in GemFire. + * @see org.springframework.data.gemfire.GemfireOperations + */ + public GemFireOperationsSessionRepository(GemfireOperations template) { + super(template); + } + + /** + * Looks up all the available Sessions tied to the specific user identified by principal name. + * + * @param principalName the principal name (i.e. username) to search for all existing Spring Sessions. + * @return a mapping of Session ID to Session instances. + * @see org.springframework.session.ExpiringSession + */ + public Map findByPrincipalName(String principalName) { + SelectResults results = getTemplate().find(String.format( + FIND_SESSIONS_BY_PRINCIPAL_NAME_QUERY, getFullyQualifiedRegionName()), principalName); + + Map sessions = new HashMap(results.size()); + + for (ExpiringSession session : results.asList()) { + sessions.put(session.getId(), session); + } + + return sessions; + } + + /** + * Constructs a new {@link ExpiringSession} instance backed by GemFire. + * + * @return an instance of {@link ExpiringSession} backed by GemFire. + * @see org.springframework.session.data.gemfire.GemFireOperationsSessionRepository.GemFireSession#create(int) + * @see org.springframework.session.ExpiringSession + * @see #getMaxInactiveIntervalInSeconds() + */ + public ExpiringSession createSession() { + return GemFireSession.create(getMaxInactiveIntervalInSeconds()); + } + + /** + * Gets a copy of an existing, non-expired {@link ExpiringSession} by ID. If the Session is expired, + * then it is deleted. + * + * @param sessionId a String indicating the ID of the Session to get. + * @return an existing {@link ExpiringSession} by ID or null if not Session exists. + * @see org.springframework.session.data.gemfire.GemFireOperationsSessionRepository.GemFireSession#from(ExpiringSession) + * @see org.springframework.session.ExpiringSession + * @see #delete(String) + */ + public ExpiringSession getSession(String sessionId) { + ExpiringSession storedSession = getTemplate().get(sessionId); + + if (storedSession != null) { + if (storedSession.isExpired()) { + delete(storedSession.getId()); + } + else { + return GemFireSession.from(storedSession); + } + } + + return null; + } + + /** + * Saves the specified {@link ExpiringSession} to GemFire. + * + * @param session the {@link ExpiringSession} to save. + * @see org.springframework.data.gemfire.GemfireOperations#put(Object, Object) + * @see org.springframework.session.ExpiringSession + */ + public void save(ExpiringSession session) { + getTemplate().put(session.getId(), new GemFireSession(session)); + } + + /** + * Deletes (removes) any existing {@link ExpiringSession} from GemFire. This operation also results in + * a SessionDeletedEvent. + * + * @param sessionId a String indicating the ID of the Session to remove from GemFire. + * @see org.springframework.data.gemfire.GemfireOperations#remove(Object) + * @see #handleDeleted(String, ExpiringSession) + */ + public void delete(String sessionId) { + handleDeleted(sessionId, getTemplate().remove(sessionId)); + } + +} diff --git a/spring-session/src/main/java/org/springframework/session/data/gemfire/config/annotation/web/http/EnableGemFireHttpSession.java b/spring-session/src/main/java/org/springframework/session/data/gemfire/config/annotation/web/http/EnableGemFireHttpSession.java new file mode 100644 index 00000000..713dc193 --- /dev/null +++ b/spring-session/src/main/java/org/springframework/session/data/gemfire/config/annotation/web/http/EnableGemFireHttpSession.java @@ -0,0 +1,144 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * 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. + */ + +package org.springframework.session.data.gemfire.config.annotation.web.http; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +import com.gemstone.gemfire.cache.RegionShortcut; +import com.gemstone.gemfire.cache.client.ClientRegionShortcut; + +/** + * Add this annotation to an {@code @Configuration} class to expose the SessionRepositoryFilter + * as a bean named "springSessionRepositoryFilter" and backed by Pivotal GemFire or Apache Geode. + * + * In order to leverage the annotation, a single Pivotal GemFire/Apache Geode {@link com.gemstone.gemfire.cache.Cache} + * or {@link com.gemstone.gemfire.cache.client.ClientCache} instance must be provided. + * + * For example: + * + *
+ * 
+ * {@literal @Configuration}
+ * {@literal @EnableGemFireHttpSession}
+ * public class GemFirePeerCacheHttpSessionConfiguration {
+ *
+ *     {@literal @Bean}
+ *     public Properties gemfireProperties() {
+ *       Properties gemfireProperties = new Properties();
+ *       gemfireProperties.setProperty("name", "ExamplePeer");
+ *       gemfireProperties.setProperty("mcast-port", "0");
+ *       gemfireProperties.setProperty("log-level", "warning");
+ *       return gemfireProperties;
+ *     }
+ *
+ *     {@literal @Bean}
+ *     public CacheFactoryBean gemfireCache() throws Exception {
+ *       CacheFactoryBean clientCacheFactoryBean = new CacheFactoryBean();
+ *       clientCacheFactoryBean.setLazyInitialize(false);
+ *       clientCacheFactoryBean.setProperties(gemfireProperties());
+ *       clientCacheFactoryBean.setUseBeanFactoryLocator(false);
+ *       return clientCacheFactoryBean;
+ *     }
+ * }
+ * 
+ * 
+ * + * Alternatively, a Spring Session can be configured to use Pivotal GemFire (Apache Geode) as a client + * using a dedicated GemFire Server cluster and a {@link com.gemstone.gemfire.cache.client.ClientCache}. + * For example: + * + * + * {@literal @Configuration} + * {@literal @EnableGemFireHttpSession} + * public class GemFireClientCacheHttpSessionConfiguration { + * + * {@literal @Bean} + * public Properties gemfireProperties() { + * Properties gemfireProperties = new Properties(); + * gemfireProperties.setProperty("name", "ExampleClient"); + * gemfireProperties.setProperty("log-level", "warning"); + * return gemfireProperties; + * } + * + * {@literal @Bean} + * public ClientCacheFactoryBean gemfireCache() throws Exception { + * ClientCacheFactoryBean clientCacheFactoryBean = new ClientCacheFactoryBean(); + * clientCacheFactoryBean.setLazyInitialize(false); + * clientCacheFactoryBean.setProperties(gemfireProperties()); + * clientCacheFactoryBean.setUseBeanFactoryLocator(false); + * return clientCacheFactoryBean; + * } + * } + * + * + * More advanced configurations can extend {@link GemFireHttpSessionConfiguration} instead. + * + * @author John Blum + * @see org.springframework.context.annotation.Configuration + * @see org.springframework.context.annotation.Import + * @see org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration + * @see org.springframework.session.config.annotation.web.http.EnableSpringHttpSession + * @since 1.1.0 + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@SuppressWarnings("unused") +@Target(ElementType.TYPE) +@Configuration +@Import(GemFireHttpSessionConfiguration.class) +public @interface EnableGemFireHttpSession { + + /** + * Defines the GemFire ClientCache Region DataPolicy. + * + * @return a ClientRegionShortcut used to specify and configure the ClientCache Region DataPolicy. + * @see com.gemstone.gemfire.cache.client.ClientRegionShortcut + */ + ClientRegionShortcut clientRegionShortcut() default ClientRegionShortcut.PROXY; + + /** + * Defines the maximum interval in seconds that a Session can remain inactive before it is considered expired. + * Defaults to 1800 seconds, or 30 minutes. + * + * @return an integer value defining the maximum inactive interval in seconds for declaring a Session expired. + */ + int maxInactiveIntervalInSeconds() default 1800; + + /** + * Defines the name of the GemFire (Client)Cache Region used to store Sessions. + * + * @return a String specifying the name of the GemFire (Client)Cache Region used to store Sessions. + * @see com.gemstone.gemfire.cache.Region#getName() + */ + String regionName() default "ClusteredSpringSessions"; + + /** + * Defines the GemFire, Peer Cache Region DataPolicy. + * + * @return a RegionShortcut used to specify and configure the Peer Cache Region DataPolicy. + * @see com.gemstone.gemfire.cache.RegionShortcut + */ + RegionShortcut serverRegionShortcut() default RegionShortcut.PARTITION; + +} diff --git a/spring-session/src/main/java/org/springframework/session/data/gemfire/config/annotation/web/http/GemFireHttpSessionConfiguration.java b/spring-session/src/main/java/org/springframework/session/data/gemfire/config/annotation/web/http/GemFireHttpSessionConfiguration.java new file mode 100644 index 00000000..d84ef331 --- /dev/null +++ b/spring-session/src/main/java/org/springframework/session/data/gemfire/config/annotation/web/http/GemFireHttpSessionConfiguration.java @@ -0,0 +1,372 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * 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. + */ + +package org.springframework.session.data.gemfire.config.annotation.web.http; + +import java.util.concurrent.TimeUnit; + +import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.DependsOn; +import org.springframework.context.annotation.ImportAware; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.data.gemfire.GemfireOperations; +import org.springframework.data.gemfire.GemfireTemplate; +import org.springframework.data.gemfire.IndexFactoryBean; +import org.springframework.data.gemfire.IndexType; +import org.springframework.data.gemfire.RegionAttributesFactoryBean; +import org.springframework.session.ExpiringSession; +import org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration; +import org.springframework.session.data.gemfire.AbstractGemFireOperationsSessionRepository.GemFireSession; +import org.springframework.session.data.gemfire.GemFireOperationsSessionRepository; +import org.springframework.session.data.gemfire.config.annotation.web.http.support.GemFireCacheTypeAwareRegionFactoryBean; +import org.springframework.session.data.gemfire.support.GemFireUtils; +import org.springframework.util.StringUtils; + +import com.gemstone.gemfire.cache.ExpirationAction; +import com.gemstone.gemfire.cache.ExpirationAttributes; +import com.gemstone.gemfire.cache.GemFireCache; +import com.gemstone.gemfire.cache.Region; +import com.gemstone.gemfire.cache.RegionAttributes; +import com.gemstone.gemfire.cache.RegionShortcut; +import com.gemstone.gemfire.cache.client.ClientRegionShortcut; + +/** + * The GemFireHttpSessionConfiguration class is a Spring @Configuration class used to configure and initialize + * Pivotal GemFire (or Apache Geode) as a clustered, replicated HttpSession provider implementation in Spring Session. + * + * @author John Blum + * @see org.springframework.beans.factory.BeanClassLoaderAware + * @see org.springframework.context.annotation.Bean + * @see org.springframework.context.annotation.Configuration + * @see org.springframework.context.annotation.ImportAware + * @see org.springframework.data.gemfire.GemfireOperations + * @see org.springframework.data.gemfire.GemfireTemplate + * @see org.springframework.data.gemfire.IndexFactoryBean + * @see org.springframework.data.gemfire.RegionAttributesFactoryBean + * @see org.springframework.session.ExpiringSession + * @see org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration + * @see org.springframework.session.data.gemfire.GemFireOperationsSessionRepository + * @see org.springframework.session.data.gemfire.config.annotation.web.http.support.GemFireCacheTypeAwareRegionFactoryBean + * @see com.gemstone.gemfire.cache.ExpirationAttributes + * @see com.gemstone.gemfire.cache.GemFireCache + * @see com.gemstone.gemfire.cache.Region + * @see com.gemstone.gemfire.cache.RegionAttributes + * @since 1.1.0 + */ +@Configuration +@SuppressWarnings("unused") +public class GemFireHttpSessionConfiguration extends SpringHttpSessionConfiguration + implements BeanClassLoaderAware, ImportAware { + + public static final int DEFAULT_MAX_INACTIVE_INTERVAL_IN_SECONDS = (int) TimeUnit.MINUTES.toSeconds(30); + + protected static final Class SPRING_SESSION_GEMFIRE_REGION_KEY_CONSTRAINT = Object.class; + protected static final Class SPRING_SESSION_GEMFIRE_REGION_VALUE_CONSTRAINT = GemFireSession.class; + + public static final ClientRegionShortcut DEFAULT_CLIENT_REGION_SHORTCUT = ClientRegionShortcut.PROXY; + + public static final RegionShortcut DEFAULT_SERVER_REGION_SHORTCUT = RegionShortcut.PARTITION; + + public static final String DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME = "ClusteredSpringSessions"; + + private int maxInactiveIntervalInSeconds = DEFAULT_MAX_INACTIVE_INTERVAL_IN_SECONDS; + + private ClassLoader beanClassLoader; + + private ClientRegionShortcut clientRegionShortcut = DEFAULT_CLIENT_REGION_SHORTCUT; + + private RegionShortcut serverRegionShortcut = DEFAULT_SERVER_REGION_SHORTCUT; + + private String springSessionGemFireRegionName = DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME; + + /** + * Sets a reference to the {@link ClassLoader} used to load bean definition class types in a Spring context. + * + * @param beanClassLoader the ClassLoader used by the Spring container to load bean class types. + * @see org.springframework.beans.factory.BeanClassLoaderAware#setBeanClassLoader(ClassLoader) + * @see java.lang.ClassLoader + */ + public void setBeanClassLoader(ClassLoader beanClassLoader) { + this.beanClassLoader = beanClassLoader; + } + + /** + * Gets a reference to the {@link ClassLoader} used to load bean definition class types in a Spring context. + * + * @return the ClassLoader used by the Spring container to load bean class types. + * @see java.lang.ClassLoader + */ + protected ClassLoader getBeanClassLoader() { + return beanClassLoader; + } + + /** + * Sets the {@link ClientRegionShortcut} used to configure the GemFire ClientCache Region + * that will store Spring Sessions. + * + * @param shortcut the ClientRegionShortcut used to configure the GemFire ClientCache Region. + * @see com.gemstone.gemfire.cache.client.ClientRegionShortcut + */ + public void setClientRegionShortcut(ClientRegionShortcut shortcut) { + this.clientRegionShortcut = shortcut; + } + + /** + * Gets the {@link ClientRegionShortcut} used to configure the GemFire ClientCache Region + * that will store Spring Sessions. Defaults to {@link ClientRegionShortcut#PROXY}. + * + * @return the ClientRegionShortcut used to configure the GemFire ClientCache Region. + * @see com.gemstone.gemfire.cache.client.ClientRegionShortcut + */ + protected ClientRegionShortcut getClientRegionShortcut() { + return (clientRegionShortcut != null ? clientRegionShortcut : DEFAULT_CLIENT_REGION_SHORTCUT); + } + + /** + * Sets the maximum interval in seconds in which a Session can remain inactive before it is considered expired. + * + * @param maxInactiveIntervalInSeconds an integer value specifying the maximum interval in seconds that a Session + * can remain inactive before it is considered expired. + */ + public void setMaxInactiveIntervalInSeconds(int maxInactiveIntervalInSeconds) { + this.maxInactiveIntervalInSeconds = maxInactiveIntervalInSeconds; + } + + /** + * Gets the maximum interval in seconds in which a Session can remain inactive before it is considered expired. + * + * @return an integer value specifying the maximum interval in seconds that a Session can remain inactive + * before it is considered expired. + */ + protected int getMaxInactiveIntervalInSeconds() { + return maxInactiveIntervalInSeconds; + } + + /** + * Sets the {@link RegionShortcut} used to configure the GemFire Cache Region that will store Spring Sessions. + * + * @param shortcut the RegionShortcut used to configure the GemFire Cache Region. + * @see com.gemstone.gemfire.cache.RegionShortcut + */ + public void setServerRegionShortcut(RegionShortcut shortcut) { + serverRegionShortcut = shortcut; + } + + /** + * Gets the {@link RegionShortcut} used to configure the GemFire Cache Region that will store Spring Sessions. + * Defaults to {@link RegionShortcut#PARTITION}. + * + * @return the RegionShortcut used to configure the GemFire Cache Region. + * @see com.gemstone.gemfire.cache.RegionShortcut + */ + protected RegionShortcut getServerRegionShortcut() { + return (serverRegionShortcut != null ? serverRegionShortcut : DEFAULT_SERVER_REGION_SHORTCUT); + } + + /** + * Sets the name of the Gemfire (Client)Cache Region used to store Sessions. + * + * @param springSessionGemFireRegionName a String specifying the name of the GemFire (Client)Cache Region + * used to store the Session. + */ + public void setSpringSessionGemFireRegionName(String springSessionGemFireRegionName) { + this.springSessionGemFireRegionName = springSessionGemFireRegionName; + } + + /** + * Gets the name of the Gemfire (Client)Cache Region used to store Sessions. Defaults to 'ClusteredSpringSessions'. + * + * @return a String specifying the name of the GemFire (Client)Cache Region + * used to store the Session. + * @see com.gemstone.gemfire.cache.Region#getName() + */ + protected String getSpringSessionGemFireRegionName() { + return (StringUtils.hasText(springSessionGemFireRegionName) ? springSessionGemFireRegionName + : DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME); + } + + /** + * Callback with the {@link AnnotationMetadata} of the class containing @Import annotation that imported + * this @Configuration class. + * + * @param importMetadata the AnnotationMetadata of the class importing this @Configuration class. + * @see org.springframework.session.data.gemfire.config.annotation.web.http.EnableGemFireHttpSession + * @see org.springframework.core.type.AnnotationMetadata + */ + public void setImportMetadata(AnnotationMetadata importMetadata) { + AnnotationAttributes enableGemFireHttpSessionAnnotationAttributes = AnnotationAttributes.fromMap( + importMetadata.getAnnotationAttributes(EnableGemFireHttpSession.class.getName())); + + setClientRegionShortcut(ClientRegionShortcut.class.cast(enableGemFireHttpSessionAnnotationAttributes.getEnum( + "clientRegionShortcut"))); + + setMaxInactiveIntervalInSeconds(enableGemFireHttpSessionAnnotationAttributes.getNumber( + "maxInactiveIntervalInSeconds").intValue()); + + setServerRegionShortcut(RegionShortcut.class.cast(enableGemFireHttpSessionAnnotationAttributes.getEnum( + "serverRegionShortcut"))); + + setSpringSessionGemFireRegionName(enableGemFireHttpSessionAnnotationAttributes.getString("regionName")); + } + + /** + * Defines the Spring SessionRepository bean used to interact with GemFire as a Spring Session provider. + * + * @param gemfireOperations an instance of {@link GemfireOperations} used to manage Spring Sessions in GemFire. + * @return a GemFireOperationsSessionRepository for managing (clustering/replicating) Sessions using GemFire. + * @see org.springframework.session.data.gemfire.GemFireOperationsSessionRepository + * @see org.springframework.data.gemfire.GemfireOperations + */ + @Bean + public GemFireOperationsSessionRepository sessionRepository(@Qualifier("sessionRegionTemplate") + GemfireOperations gemfireOperations) { + + GemFireOperationsSessionRepository sessionRepository = new GemFireOperationsSessionRepository(gemfireOperations); + + sessionRepository.setMaxInactiveIntervalInSeconds(getMaxInactiveIntervalInSeconds()); + + return sessionRepository; + } + + /** + * Defines a Spring GemfireTemplate bean used to interact with GemFire's (Client)Cache {@link Region} + * storing Sessions. + * + * @param gemFireCache reference to the single GemFire cache instance used by the {@link GemfireTemplate} + * to perform GemFire cache data access operations. + * @return a {@link GemfireTemplate} used to interact with GemFire's (Client)Cache {@link Region} storing Sessions. + * @see org.springframework.data.gemfire.GemfireTemplate + * @see com.gemstone.gemfire.cache.Region + */ + @Bean + @DependsOn(DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME) + public GemfireTemplate sessionRegionTemplate(GemFireCache gemFireCache) { + return new GemfireTemplate(gemFireCache.getRegion(getSpringSessionGemFireRegionName())); + } + + /** + * Defines a Spring GemFire {@link com.gemstone.gemfire.cache.Cache} {@link Region} bean used to store + * and manage Sessions using either a client-server or peer-to-peer (p2p) topology. + * + * @param gemfireCache a reference to the GemFire {@link com.gemstone.gemfire.cache.Cache}. + * @param sessionRegionAttributes the GemFire {@link RegionAttributes} used to configure the {@link Region}. + * @return a {@link GemFireCacheTypeAwareRegionFactoryBean} used to configure and initialize a GemFire Cache + * {@link Region} for storing and managing Sessions. + * @see org.springframework.session.data.gemfire.config.annotation.web.http.support.GemFireCacheTypeAwareRegionFactoryBean + * @see com.gemstone.gemfire.cache.GemFireCache + * @see com.gemstone.gemfire.cache.RegionAttributes + * @see #getClientRegionShortcut() + * @see #getSpringSessionGemFireRegionName() + * @see #getServerRegionShortcut() + */ + @Bean(name = DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME) + public GemFireCacheTypeAwareRegionFactoryBean sessionRegion(GemFireCache gemfireCache, + RegionAttributes sessionRegionAttributes) { + + GemFireCacheTypeAwareRegionFactoryBean serverRegion = + new GemFireCacheTypeAwareRegionFactoryBean(); + + serverRegion.setGemfireCache(gemfireCache); + serverRegion.setClientRegionShortcut(getClientRegionShortcut()); + serverRegion.setRegionAttributes(sessionRegionAttributes); + serverRegion.setRegionName(getSpringSessionGemFireRegionName()); + serverRegion.setServerRegionShortcut(getServerRegionShortcut()); + + return serverRegion; + } + + /** + * Defines a Spring GemFire {@link RegionAttributes} bean used to configure and initialize the GemFire cache + * {@link Region} storing Sessions. Expiration is also configured for the {@link Region} on the basis that the + * GemFire cache {@link Region} is a not a proxy, on either the client or server. + * + * @param gemfireCache a reference to the GemFire cache. + * @return an instance of {@link RegionAttributes} used to configure and initialize the GemFire cache {@link Region} + * for storing and managing Sessions. + * @see org.springframework.data.gemfire.RegionAttributesFactoryBean + * @see com.gemstone.gemfire.cache.GemFireCache + * @see com.gemstone.gemfire.cache.PartitionAttributes + * @see #isExpirationAllowed(GemFireCache) + */ + @Bean + @SuppressWarnings({ "unchecked", "deprecation" }) + public RegionAttributesFactoryBean sessionRegionAttributes(GemFireCache gemfireCache) { + RegionAttributesFactoryBean regionAttributes = new RegionAttributesFactoryBean(); + + regionAttributes.setKeyConstraint(SPRING_SESSION_GEMFIRE_REGION_KEY_CONSTRAINT); + regionAttributes.setValueConstraint(SPRING_SESSION_GEMFIRE_REGION_VALUE_CONSTRAINT); + + if (isExpirationAllowed(gemfireCache)) { + regionAttributes.setStatisticsEnabled(true); + regionAttributes.setEntryIdleTimeout(new ExpirationAttributes( + Math.max(getMaxInactiveIntervalInSeconds(), 0), ExpirationAction.INVALIDATE)); + } + + return regionAttributes; + } + + /** + * Determines whether expiration configuration is allowed to be set on the GemFire cache {@link Region} + * used to store and manage Sessions. + * + * @param gemfireCache a reference to the GemFire cache. + * @return a boolean indicating if a {@link Region} can be configured for Region entry idle-timeout expiration. + * @see GemFireUtils#isClient(GemFireCache) + * @see GemFireUtils#isProxy(ClientRegionShortcut) + * @see GemFireUtils#isProxy(RegionShortcut) + */ + boolean isExpirationAllowed(GemFireCache gemfireCache) { + return !(GemFireUtils.isClient(gemfireCache) ? GemFireUtils.isProxy(getClientRegionShortcut()) + : GemFireUtils.isProxy(getServerRegionShortcut())); + } + + /** + * Defines a Spring GemFire Index bean on the GemFire cache {@link Region} storing and managing Sessions, + * specifically on the 'principalName' property for quick lookup and queries. This index will only be created + * on a server @{link Region}. + * + * @param gemfireCache a reference to the GemFire cache. + * @return a IndexFactoryBean creating an GemFire Index on the 'principalName' property of Sessions stored + * in the GemFire cache {@link Region}. + * @see org.springframework.data.gemfire.IndexFactoryBean + * @see com.gemstone.gemfire.cache.GemFireCache + */ + @Bean + @DependsOn(DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME) + public IndexFactoryBean principalNameIndex(final GemFireCache gemfireCache) { + IndexFactoryBean index = new IndexFactoryBean() { + @Override public void afterPropertiesSet() throws Exception { + if (GemFireUtils.isPeer(gemfireCache)) { + super.afterPropertiesSet(); + } + } + }; + + index.setCache(gemfireCache); + index.setName("principalNameIdx"); + index.setExpression("principalName"); + index.setFrom(GemFireUtils.toRegionPath(getSpringSessionGemFireRegionName())); + index.setOverride(true); + index.setType(IndexType.HASH); + + return index; + } + +} diff --git a/spring-session/src/main/java/org/springframework/session/data/gemfire/config/annotation/web/http/support/GemFireCacheTypeAwareRegionFactoryBean.java b/spring-session/src/main/java/org/springframework/session/data/gemfire/config/annotation/web/http/support/GemFireCacheTypeAwareRegionFactoryBean.java new file mode 100644 index 00000000..579a9674 --- /dev/null +++ b/spring-session/src/main/java/org/springframework/session/data/gemfire/config/annotation/web/http/support/GemFireCacheTypeAwareRegionFactoryBean.java @@ -0,0 +1,314 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * 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. + */ + +package org.springframework.session.data.gemfire.config.annotation.web.http.support; + +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.data.gemfire.GenericRegionFactoryBean; +import org.springframework.data.gemfire.client.ClientRegionFactoryBean; +import org.springframework.data.gemfire.client.Interest; +import org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration; +import org.springframework.session.data.gemfire.support.GemFireUtils; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +import com.gemstone.gemfire.cache.GemFireCache; +import com.gemstone.gemfire.cache.InterestResultPolicy; +import com.gemstone.gemfire.cache.Region; +import com.gemstone.gemfire.cache.RegionAttributes; +import com.gemstone.gemfire.cache.RegionShortcut; +import com.gemstone.gemfire.cache.client.ClientRegionShortcut; + +/** + * The GemFireCacheTypeAwareRegionFactoryBean class is a Spring {@link FactoryBean} used to construct, configure + * and initialize the GemFire cache {@link Region} used to store and manage Session state. + * + * @author John Blum + * @see org.springframework.beans.factory.FactoryBean + * @see org.springframework.beans.factory.InitializingBean + * @see org.springframework.data.gemfire.GenericRegionFactoryBean + * @see org.springframework.data.gemfire.client.ClientRegionFactoryBean + * @see org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration + * @see com.gemstone.gemfire.cache.GemFireCache + * @see com.gemstone.gemfire.cache.InterestResultPolicy + * @see com.gemstone.gemfire.cache.Region + * @see com.gemstone.gemfire.cache.RegionAttributes + * @see com.gemstone.gemfire.cache.RegionShortcut + * @see com.gemstone.gemfire.cache.client.ClientRegionShortcut + * @since 1.1.0 + */ +public class GemFireCacheTypeAwareRegionFactoryBean implements FactoryBean>, InitializingBean { + + protected static final ClientRegionShortcut DEFAULT_CLIENT_REGION_SHORTCUT = + GemFireHttpSessionConfiguration.DEFAULT_CLIENT_REGION_SHORTCUT; + + protected static final RegionShortcut DEFAULT_SERVER_REGION_SHORTCUT = + GemFireHttpSessionConfiguration.DEFAULT_SERVER_REGION_SHORTCUT; + + protected static final String DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME = + GemFireHttpSessionConfiguration.DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME; + + private ClientRegionShortcut clientRegionShortcut; + + private GemFireCache gemfireCache; + + private Region region; + + private RegionAttributes regionAttributes; + + private RegionShortcut serverRegionShortcut; + + private String regionName; + + /** + * Post-construction initialization callback to create, configure and initialize the GemFire cache {@link Region} + * used to store, replicate (distribute) and manage Session state. This method intelligently handles + * both client-server and peer-to-peer (p2p) GemFire supported distributed system topologies. + * + * @throws Exception if the initialization of the GemFire cache {@link Region} fails. + * @see org.springframework.session.data.gemfire.support.GemFireUtils#isClient(GemFireCache) + * @see #getGemfireCache() + * @see #newClientRegion(GemFireCache) + * @see #newServerRegion(GemFireCache) + */ + public void afterPropertiesSet() throws Exception { + GemFireCache gemfireCache = getGemfireCache(); + + region = (GemFireUtils.isClient(gemfireCache) ? newClientRegion(gemfireCache) + : newServerRegion(gemfireCache)); + } + + /** + * Constructs a GemFire cache {@link Region} using a peer-to-peer (p2p) GemFire topology to store + * and manage Session state in a GemFire server cluster accessible from a GemFire cache client. + * + * @param gemfireCache a reference to the GemFire {@link com.gemstone.gemfire.cache.Cache}. + * @return a peer-to-peer-based GemFire cache {@link Region} to store and manage Session state. + * @throws Exception if the instantiation, configuration and initialization + * of the GemFire cache {@link Region} fails. + * @see org.springframework.data.gemfire.GenericRegionFactoryBean + * @see com.gemstone.gemfire.cache.GemFireCache + * @see com.gemstone.gemfire.cache.Region + * @see #getRegionAttributes() + * @see #getRegionName() + * @see #getServerRegionShortcut() + */ + protected Region newServerRegion(GemFireCache gemfireCache) throws Exception { + GenericRegionFactoryBean serverRegion = new GenericRegionFactoryBean(); + + serverRegion.setCache(gemfireCache); + serverRegion.setAttributes(getRegionAttributes()); + serverRegion.setRegionName(getRegionName()); + serverRegion.setShortcut(getServerRegionShortcut()); + serverRegion.afterPropertiesSet(); + + return serverRegion.getObject(); + } + + /** + * Constructs a GemFire cache {@link Region} using the client-server GemFire topology to store + * and manage Session state in a GemFire server cluster accessible from a GemFire cache client. + * + * @param gemfireCache a reference to the GemFire {@link com.gemstone.gemfire.cache.Cache}. + * @return a client-server-based GemFire cache {@link Region} to store and manage Session state. + * @throws Exception if the instantiation, configuration and initialization + * of the GemFire cache {@link Region} fails. + * @see org.springframework.data.gemfire.client.ClientRegionFactoryBean + * @see com.gemstone.gemfire.cache.GemFireCache + * @see com.gemstone.gemfire.cache.Region + * @see #getClientRegionShortcut() + * @see #getRegionAttributes() + * @see #getRegionName() + * @see #registerInterests(boolean) + */ + protected Region newClientRegion(GemFireCache gemfireCache) throws Exception { + ClientRegionFactoryBean clientRegion = new ClientRegionFactoryBean(); + + ClientRegionShortcut shortcut = getClientRegionShortcut(); + + clientRegion.setCache(gemfireCache); + clientRegion.setAttributes(getRegionAttributes()); + clientRegion.setInterests(registerInterests(!GemFireUtils.isLocal(shortcut))); + clientRegion.setRegionName(getRegionName()); + clientRegion.setShortcut(shortcut); + clientRegion.afterPropertiesSet(); + + return clientRegion.getObject(); + } + + /** + * Registers interests in all keys when the client {@link Region} is non-local. + * + * @return an array of Interests specifying the server notifications of interests to the client. + * @see org.springframework.data.gemfire.client.Interest + */ + /** + * Decides whether interests will be registered for all keys. Interests is only registered on a client + * and typically only when the client is a (CACHING) PROXY to the server (i.e. non-LOCAL only). + * + * @param register a boolean value indicating whether interests should be registered. + * @return an array of Interests KEY/VALUE registrations. + * @see org.springframework.data.gemfire.client.Interest + */ + @SuppressWarnings("unchecked") + protected Interest[] registerInterests(boolean register) { + return (!register ? new Interest[0] : new Interest[] { + new Interest("ALL_KEYS", InterestResultPolicy.KEYS) + }); + } + + /** + * Returns a reference to the constructed GemFire cache {@link Region} used to store and manage Session state. + * + * @return the {@link Region} used to store and manage Session state. + * @throws Exception if the {@link Region} reference cannot be obtained. + * @see com.gemstone.gemfire.cache.Region + */ + public Region getObject() throws Exception { + return region; + } + + /** + * Returns the specific type of GemFire cache {@link Region} this factory creates when initialized + * or Region.class when uninitialized. + * + * @return the GemFire cache {@link Region} class type constructed by this factory. + * @see com.gemstone.gemfire.cache.Region + * @see java.lang.Class + */ + public Class getObjectType() { + return (region != null ? region.getClass() : Region.class); + } + + /** + * Returns true indicating the GemFire cache {@link Region} created by this factory is the sole instance. + * + * @return true to indicate the GemFire cache {@link Region} storing and managing Sessions is a Singleton. + */ + public boolean isSingleton() { + return true; + } + + /** + * Sets the {@link Region} data policy used by the GemFire cache client to manage Session state. + * + * @param clientRegionShortcut a {@link ClientRegionShortcut} to specify the client {@link Region} + * data management policy. + * @see com.gemstone.gemfire.cache.client.ClientRegionShortcut + */ + public void setClientRegionShortcut(ClientRegionShortcut clientRegionShortcut) { + this.clientRegionShortcut = clientRegionShortcut; + } + + /** + * Returns the {@link Region} data policy used by the GemFire cache client to manage Session state. Defaults to + * {@link ClientRegionShortcut#PROXY}. + * + * @return a {@link ClientRegionShortcut} specifying the client {@link Region} data management policy. + * @see org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration#DEFAULT_CLIENT_REGION_SHORTCUT + * @see com.gemstone.gemfire.cache.client.ClientRegionShortcut + */ + protected ClientRegionShortcut getClientRegionShortcut() { + return (clientRegionShortcut != null ? clientRegionShortcut : DEFAULT_CLIENT_REGION_SHORTCUT); + } + + /** + * Sets a reference to the GemFire cache used to construct the appropriate {@link Region}. + * + * @param gemfireCache a reference to the GemFire cache. + * @throws IllegalArgumentException if the {@link GemFireCache} reference is null. + */ + public void setGemfireCache(GemFireCache gemfireCache) { + Assert.notNull(gemfireCache, "The GemFireCache reference must not be null"); + this.gemfireCache = gemfireCache; + } + + /** + * Returns a reference to the GemFire cache used to construct the appropriate {@link Region}. + * + * @return a reference to the GemFire cache. + * @throws IllegalStateException if the {@link GemFireCache} reference is null. + */ + protected GemFireCache getGemfireCache() { + Assert.state(gemfireCache != null, "A reference to a GemFireCache was not properly configured"); + return gemfireCache; + } + + /** + * Sets the GemFire {@link RegionAttributes} used to configure the GemFire cache {@link Region} used to + * store and manage Session state. + * + * @param regionAttributes the GemFire {@link RegionAttributes} used to configure the GemFire cache {@link Region}. + * @see com.gemstone.gemfire.cache.RegionAttributes + */ + public void setRegionAttributes(RegionAttributes regionAttributes) { + this.regionAttributes = regionAttributes; + } + + /** + * Returns the GemFire {@link RegionAttributes} used to configure the GemFire cache {@link Region} used to + * store and manage Session state. + * + * @return the GemFire {@link RegionAttributes} used to configure the GemFire cache {@link Region}. + * @see com.gemstone.gemfire.cache.RegionAttributes + */ + protected RegionAttributes getRegionAttributes() { + return regionAttributes; + } + + /** + * Sets the name of the GemFire cache {@link Region} use to store and manage Session state. + * + * @param regionName a String specifying the name of the GemFire cache {@link Region}. + */ + public void setRegionName(final String regionName) { + this.regionName = regionName; + } + + /** + * Returns the configured name of the GemFire cache {@link Region} use to store and manage Session state. + * Defaults to "ClusteredSpringSessions" + * + * @return a String specifying the name of the GemFire cache {@link Region}. + * @see com.gemstone.gemfire.cache.Region#getName() + */ + protected String getRegionName() { + return (StringUtils.hasText(regionName) ? regionName : DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME); + } + + /** + * Sets the {@link Region} data policy used by the GemFire peer cache to manage Session state. + * + * @param serverRegionShortcut a {@link RegionShortcut} to specify the peer {@link Region} data management policy. + * @see com.gemstone.gemfire.cache.RegionShortcut + */ + public void setServerRegionShortcut(RegionShortcut serverRegionShortcut) { + this.serverRegionShortcut = serverRegionShortcut; + } + + /** + * Returns the {@link Region} data policy used by the GemFire peer cache to manage Session state. Defaults to + * {@link RegionShortcut#PARTITION}. + * + * @return a {@link RegionShortcut} specifying the peer {@link Region} data management policy. + * @see com.gemstone.gemfire.cache.RegionShortcut + */ + protected RegionShortcut getServerRegionShortcut() { + return (serverRegionShortcut != null ? serverRegionShortcut : DEFAULT_SERVER_REGION_SHORTCUT); + } + +} diff --git a/spring-session/src/main/java/org/springframework/session/data/gemfire/support/GemFireUtils.java b/spring-session/src/main/java/org/springframework/session/data/gemfire/support/GemFireUtils.java new file mode 100644 index 00000000..515d30b1 --- /dev/null +++ b/spring-session/src/main/java/org/springframework/session/data/gemfire/support/GemFireUtils.java @@ -0,0 +1,155 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * 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. + */ + +package org.springframework.session.data.gemfire.support; + +import java.io.Closeable; +import java.io.IOException; + +import com.gemstone.gemfire.cache.Cache; +import com.gemstone.gemfire.cache.GemFireCache; +import com.gemstone.gemfire.cache.Region; +import com.gemstone.gemfire.cache.RegionShortcut; +import com.gemstone.gemfire.cache.client.ClientCache; +import com.gemstone.gemfire.cache.client.ClientRegionShortcut; +import com.gemstone.gemfire.internal.cache.GemFireCacheImpl; + +/** + * GemFireUtils is an abstract, extensible utility class for working with GemFire types and functionality + * and is used by Spring Session's GemFire adapter support classes. + * + * @author John Blum + * @since 1.0.0 + */ +public abstract class GemFireUtils { + + /** + * Null-safe method to close the given {@link Closeable} object. + * + * @param obj the {@link Closeable} object to close. + * @return true if the {@link Closeable} object is not null and was successfully closed, + * otherwise return false. + * @see java.io.Closeable + */ + public static boolean close(Closeable obj) { + if (obj != null) { + try { + obj.close(); + return true; + } + catch (IOException ignore) { + } + } + + return false; + } + + /** + * Determines whether the GemFire cache is a client. + * + * @param gemFireCache a reference to the GemFire cache. + * @return a boolean value indicating whether the GemFire cache is a client. + * @see com.gemstone.gemfire.cache.client.ClientCache + * @see com.gemstone.gemfire.cache.GemFireCache + */ + public static boolean isClient(GemFireCache gemFireCache) { + boolean client = (gemFireCache instanceof ClientCache); + client &= (!(gemFireCache instanceof GemFireCacheImpl) || ((GemFireCacheImpl) gemFireCache).isClient()); + return client; + } + + /** + * Determines whether the GemFire cache is a peer. + * + * @param gemFireCache a reference to the GemFire cache. + * @return a boolean value indicating whether the GemFire cache is a peer. + * @see com.gemstone.gemfire.cache.Cache + * @see com.gemstone.gemfire.cache.GemFireCache + */ + public static boolean isPeer(GemFireCache gemFireCache) { + return (gemFireCache instanceof Cache && !isClient(gemFireCache)); + } + + /** + * Determines whether the given {@link ClientRegionShortcut} is local only. + * + * @param shortcut the ClientRegionShortcut to evaluate. + * @return a boolean value indicating if the {@link ClientRegionShortcut} is local or not. + * @see com.gemstone.gemfire.cache.client.ClientRegionShortcut + */ + public static boolean isLocal(ClientRegionShortcut shortcut) { + switch (shortcut) { + case LOCAL: + case LOCAL_HEAP_LRU: + case LOCAL_OVERFLOW: + case LOCAL_PERSISTENT: + case LOCAL_PERSISTENT_OVERFLOW: + return true; + default: + return false; + } + } + + /** + * Determines whether the client {@link ClientRegionShortcut} is a proxy-based shortcut. + * NOTE: "proxy"-based Regions keep no local state. + * + * @param shortcut the client {@link ClientRegionShortcut} to evaluate. + * @return a boolean value indicating whether the client {@link ClientRegionShortcut} refers to + * a proxy-based shortcut. + * @see com.gemstone.gemfire.cache.client.ClientRegionShortcut + */ + public static boolean isProxy(ClientRegionShortcut shortcut) { + switch (shortcut) { + case PROXY: + return true; + default: + return false; + } + } + + /** + * Determines whether the peer {@link RegionShortcut} is a proxy-based shortcut. NOTE: "proxy"-based Regions + * keep no local state. + * + * @param shortcut the peer {@link RegionShortcut} to evaluate. + * @return a boolean value indicating whether the peer {@link RegionShortcut} refers to a proxy-based shortcut. + * @see com.gemstone.gemfire.cache.RegionShortcut + */ + public static boolean isProxy(RegionShortcut shortcut) { + switch (shortcut) { + case PARTITION_PROXY: + case PARTITION_PROXY_REDUNDANT: + case REPLICATE_PROXY: + return true; + default: + return false; + } + } + + /** + * Converts a {@link Region} name to a {@link Region} path. + * + * @param regionName a String specifying the name of the {@link Region}. + * @return a String path for the given {@link Region} by name. + * @see com.gemstone.gemfire.cache.Region#getFullPath() + * @see com.gemstone.gemfire.cache.Region#getName() + */ + public static String toRegionPath(String regionName) { + return String.format("%1$s%2$s", Region.SEPARATOR, regionName); + } + +} diff --git a/spring-session/src/test/java/org/springframework/session/data/gemfire/AbstractGemFireOperationsSessionRepositoryTest.java b/spring-session/src/test/java/org/springframework/session/data/gemfire/AbstractGemFireOperationsSessionRepositoryTest.java new file mode 100644 index 00000000..8c7be855 --- /dev/null +++ b/spring-session/src/test/java/org/springframework/session/data/gemfire/AbstractGemFireOperationsSessionRepositoryTest.java @@ -0,0 +1,1389 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * 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. + */ + +package org.springframework.session.data.gemfire; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Matchers.isA; +import static org.mockito.Matchers.same; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.springframework.session.data.gemfire.AbstractGemFireOperationsSessionRepository.GemFireSession; +import static org.springframework.session.data.gemfire.AbstractGemFireOperationsSessionRepository.GemFireSessionAttributes; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.DataOutput; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.logging.Log; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.data.gemfire.GemfireOperations; +import org.springframework.data.gemfire.GemfireTemplate; +import org.springframework.session.ExpiringSession; +import org.springframework.session.Session; +import org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration; +import org.springframework.session.events.AbstractSessionEvent; +import org.springframework.session.events.SessionCreatedEvent; +import org.springframework.session.events.SessionDeletedEvent; +import org.springframework.session.events.SessionDestroyedEvent; +import org.springframework.session.events.SessionExpiredEvent; + +import com.gemstone.gemfire.cache.AttributesMutator; +import com.gemstone.gemfire.cache.EntryEvent; +import com.gemstone.gemfire.cache.Region; + +import edu.umd.cs.mtc.MultithreadedTestCase; +import edu.umd.cs.mtc.TestFramework; + +/** + * The AbstractGemFireOperationsSessionRepositoryTest class is a test suite of test cases testing the contract + * and functionality of the AbstractGemFireOperationsSessionRepository class. + * + * @author John Blum + * @see org.junit.Rule + * @see org.junit.Test + * @see org.junit.rules.ExpectedException + * @see org.junit.runner.RunWith + * @see org.mockito.Mock + * @see org.mockito.Mockito + * @see org.mockito.runners.MockitoJUnitRunner + * @see org.springframework.data.gemfire.GemfireOperations + * @see org.springframework.session.ExpiringSession + * @see org.springframework.session.Session + * @see org.springframework.session.data.gemfire.AbstractGemFireOperationsSessionRepository + * @see edu.umd.cs.mtc.MultithreadedTestCase + * @see edu.umd.cs.mtc.TestFramework + * @since 1.1.0 + */ +@RunWith(MockitoJUnitRunner.class) +public class AbstractGemFireOperationsSessionRepositoryTest { + + protected static final int MAX_INACTIVE_INTERVAL_IN_SECONDS = 600; + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Mock + private GemfireOperations mockGemfireOperations; + + @Mock + private Log mockLog; + + private AbstractGemFireOperationsSessionRepository sessionRepository; + + @Before + public void setup() { + sessionRepository = new TestGemFireOperationsSessionRepository(mockGemfireOperations) { + @Override Log newLogger() { + return mockLog; + } + }; + } + + protected static Set asSet(E... elements) { + Set set = new HashSet(elements.length); + Collections.addAll(set, elements); + return set; + } + + protected ExpiringSession mockSession(String sessionId, long creationAndLastAccessedTime, + int maxInactiveIntervalInSeconds) { + + return mockSession(sessionId, creationAndLastAccessedTime, creationAndLastAccessedTime, + maxInactiveIntervalInSeconds); + } + + protected ExpiringSession mockSession(String sessionId, long creationTime, long lastAccessedTime, + int maxInactiveIntervalInSeconds) { + + ExpiringSession mockSession = mock(ExpiringSession.class, sessionId); + + when(mockSession.getId()).thenReturn(sessionId); + when(mockSession.getCreationTime()).thenReturn(creationTime); + when(mockSession.getLastAccessedTime()).thenReturn(lastAccessedTime); + when(mockSession.getMaxInactiveIntervalInSeconds()).thenReturn(maxInactiveIntervalInSeconds); + + return mockSession; + } + + @Test + public void constructGemFireOperationsSessionRepositoryWithNullTemplate() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("GemfireOperations must not be null"); + new TestGemFireOperationsSessionRepository(null); + } + + @Test + @SuppressWarnings("unchecked") + public void gemfireOperationsSessionRepositoryIsProperlyConstructedAndInitialized() throws Exception { + ApplicationEventPublisher mockApplicationEventPublisher = mock(ApplicationEventPublisher.class); + AttributesMutator mockAttributesMutator = mock(AttributesMutator.class); + Region mockRegion = mock(Region.class); + + when(mockRegion.getFullPath()).thenReturn("/Example"); + when(mockRegion.getAttributesMutator()).thenReturn(mockAttributesMutator); + + GemfireTemplate template = new GemfireTemplate(mockRegion); + + AbstractGemFireOperationsSessionRepository sessionRepository = + new TestGemFireOperationsSessionRepository(template); + + ApplicationEventPublisher applicationEventPublisher = sessionRepository.getApplicationEventPublisher(); + + assertThat(applicationEventPublisher).isNotNull(); + assertThat(sessionRepository.getFullyQualifiedRegionName()).isNull(); + assertThat(sessionRepository.getMaxInactiveIntervalInSeconds()).isEqualTo( + GemFireHttpSessionConfiguration.DEFAULT_MAX_INACTIVE_INTERVAL_IN_SECONDS); + assertThat(sessionRepository.getTemplate()).isSameAs(template); + + sessionRepository.setApplicationEventPublisher(mockApplicationEventPublisher); + sessionRepository.setMaxInactiveIntervalInSeconds(300); + sessionRepository.afterPropertiesSet(); + + assertThat(sessionRepository.getApplicationEventPublisher()).isSameAs(mockApplicationEventPublisher); + assertThat(sessionRepository.getFullyQualifiedRegionName()).isEqualTo("/Example"); + assertThat(sessionRepository.getMaxInactiveIntervalInSeconds()).isEqualTo(300); + assertThat(sessionRepository.getTemplate()).isSameAs(template); + + verify(mockRegion, times(1)).getAttributesMutator(); + verify(mockRegion, times(1)).getFullPath(); + verify(mockAttributesMutator, times(1)).addCacheListener(same(sessionRepository)); + } + + @Test + public void maxInactiveIntervalInSecondsAllowsNegativeValuesAndExtremelyLargeValues() { + assertThat(sessionRepository.getMaxInactiveIntervalInSeconds()).isEqualTo( + GemFireHttpSessionConfiguration.DEFAULT_MAX_INACTIVE_INTERVAL_IN_SECONDS); + + sessionRepository.setMaxInactiveIntervalInSeconds(-1); + + assertThat(sessionRepository.getMaxInactiveIntervalInSeconds()).isEqualTo(-1); + + sessionRepository.setMaxInactiveIntervalInSeconds(Integer.MIN_VALUE); + + assertThat(sessionRepository.getMaxInactiveIntervalInSeconds()).isEqualTo(Integer.MIN_VALUE); + + sessionRepository.setMaxInactiveIntervalInSeconds(3600); + + assertThat(sessionRepository.getMaxInactiveIntervalInSeconds()).isEqualTo(3600); + + sessionRepository.setMaxInactiveIntervalInSeconds(Integer.MAX_VALUE); + + assertThat(sessionRepository.getMaxInactiveIntervalInSeconds()).isEqualTo(Integer.MAX_VALUE); + } + + @Test + @SuppressWarnings("unchecked") + public void afterCreateWithSessionPublishesSessionCreatedEvent() { + final String sessionId = "abc123"; + final ExpiringSession mockSession = mock(ExpiringSession.class); + + when(mockSession.getId()).thenReturn(sessionId); + + ApplicationEventPublisher mockApplicationEventPublisher = mock(ApplicationEventPublisher.class); + + doAnswer(new Answer() { + public Void answer(final InvocationOnMock invocation) throws Throwable { + ApplicationEvent applicationEvent = invocation.getArgumentAt(0, ApplicationEvent.class); + + assertThat(applicationEvent).isInstanceOf(SessionCreatedEvent.class); + + AbstractSessionEvent sessionEvent = (AbstractSessionEvent) applicationEvent; + + assertThat(sessionEvent.getSource()).isEqualTo(sessionRepository); + assertThat(sessionEvent.getSession()).isEqualTo(mockSession); + assertThat(sessionEvent.getSessionId()).isEqualTo(sessionId); + + return null; + } + }).when(mockApplicationEventPublisher).publishEvent(isA(ApplicationEvent.class)); + + EntryEvent mockEntryEvent = mock(EntryEvent.class); + + when(mockEntryEvent.getKey()).thenReturn(sessionId); + when(mockEntryEvent.getNewValue()).thenReturn(mockSession); + when(mockEntryEvent.getOldValue()).thenReturn(null); + + sessionRepository.setApplicationEventPublisher(mockApplicationEventPublisher); + sessionRepository.afterCreate(mockEntryEvent); + + assertThat(sessionRepository.getApplicationEventPublisher()).isSameAs(mockApplicationEventPublisher); + + verify(mockEntryEvent, times(1)).getKey(); + verify(mockEntryEvent, times(1)).getNewValue(); + verify(mockEntryEvent, never()).getOldValue(); + verify(mockSession, times(1)).getId(); + verify(mockApplicationEventPublisher, times(1)).publishEvent(isA(SessionCreatedEvent.class)); + } + + @Test + @SuppressWarnings("unchecked") + public void afterCreateWithSessionIdPublishesSessionCreatedEvent() { + final String sessionId = "abc123"; + + ApplicationEventPublisher mockApplicationEventPublisher = mock(ApplicationEventPublisher.class); + + doAnswer(new Answer() { + public Void answer(final InvocationOnMock invocation) throws Throwable { + ApplicationEvent applicationEvent = invocation.getArgumentAt(0, ApplicationEvent.class); + + assertThat(applicationEvent).isInstanceOf(SessionCreatedEvent.class); + + AbstractSessionEvent sessionEvent = (AbstractSessionEvent) applicationEvent; + + assertThat(sessionEvent.getSource()).isEqualTo(sessionRepository); + assertThat(sessionEvent.getSession()).isNull(); + assertThat(sessionEvent.getSessionId()).isEqualTo(sessionId); + + return null; + } + }).when(mockApplicationEventPublisher).publishEvent(isA(ApplicationEvent.class)); + + EntryEvent mockEntryEvent = mock(EntryEvent.class); + + when(mockEntryEvent.getKey()).thenReturn(sessionId); + when(mockEntryEvent.getNewValue()).thenReturn(null); + when(mockEntryEvent.getOldValue()).thenReturn(null); + + sessionRepository.setApplicationEventPublisher(mockApplicationEventPublisher); + sessionRepository.afterCreate(mockEntryEvent); + + assertThat(sessionRepository.getApplicationEventPublisher()).isSameAs(mockApplicationEventPublisher); + + verify(mockEntryEvent, times(1)).getKey(); + verify(mockEntryEvent, times(1)).getNewValue(); + verify(mockEntryEvent, never()).getOldValue(); + verify(mockApplicationEventPublisher, times(1)).publishEvent(isA(SessionCreatedEvent.class)); + } + + @Test + @SuppressWarnings("unchecked") + public void afterDestroyWithSessionPublishesSessionDestroyedEvent() { + final String sessionId = "abc123"; + final ExpiringSession mockSession = mock(ExpiringSession.class); + + when(mockSession.getId()).thenReturn(sessionId); + + ApplicationEventPublisher mockApplicationEventPublisher = mock(ApplicationEventPublisher.class); + + doAnswer(new Answer() { + public Void answer(final InvocationOnMock invocation) throws Throwable { + ApplicationEvent applicationEvent = invocation.getArgumentAt(0, ApplicationEvent.class); + + assertThat(applicationEvent).isInstanceOf(SessionDestroyedEvent.class); + + AbstractSessionEvent sessionEvent = (AbstractSessionEvent) applicationEvent; + + assertThat(sessionEvent.getSource()).isEqualTo(sessionRepository); + assertThat(sessionEvent.getSession()).isEqualTo(mockSession); + assertThat(sessionEvent.getSessionId()).isEqualTo(sessionId); + + return null; + } + }).when(mockApplicationEventPublisher).publishEvent(isA(ApplicationEvent.class)); + + EntryEvent mockEntryEvent = mock(EntryEvent.class); + + when(mockEntryEvent.getKey()).thenReturn(sessionId); + when(mockEntryEvent.getNewValue()).thenReturn(null); + when(mockEntryEvent.getOldValue()).thenReturn(mockSession); + + sessionRepository.setApplicationEventPublisher(mockApplicationEventPublisher); + sessionRepository.afterDestroy(mockEntryEvent); + + assertThat(sessionRepository.getApplicationEventPublisher()).isSameAs(mockApplicationEventPublisher); + + verify(mockEntryEvent, times(1)).getKey(); + verify(mockEntryEvent, never()).getNewValue(); + verify(mockEntryEvent, times(1)).getOldValue(); + verify(mockSession, times(1)).getId(); + verify(mockApplicationEventPublisher, times(1)).publishEvent(isA(SessionDestroyedEvent.class)); + } + + @Test + @SuppressWarnings("unchecked") + public void afterDestroyWithSessionIdPublishesSessionDestroyedEvent() { + final String sessionId = "abc123"; + + ApplicationEventPublisher mockApplicationEventPublisher = mock(ApplicationEventPublisher.class); + + doAnswer(new Answer() { + public Void answer(final InvocationOnMock invocation) throws Throwable { + ApplicationEvent applicationEvent = invocation.getArgumentAt(0, ApplicationEvent.class); + + assertThat(applicationEvent).isInstanceOf(SessionDestroyedEvent.class); + + AbstractSessionEvent sessionEvent = (AbstractSessionEvent) applicationEvent; + + assertThat(sessionEvent.getSource()).isEqualTo(sessionRepository); + assertThat(sessionEvent.getSession()).isNull(); + assertThat(sessionEvent.getSessionId()).isEqualTo(sessionId); + + return null; + } + }).when(mockApplicationEventPublisher).publishEvent(isA(ApplicationEvent.class)); + + EntryEvent mockEntryEvent = mock(EntryEvent.class); + + when(mockEntryEvent.getKey()).thenReturn(sessionId); + when(mockEntryEvent.getNewValue()).thenReturn(null); + when(mockEntryEvent.getOldValue()).thenReturn(null); + + sessionRepository.setApplicationEventPublisher(mockApplicationEventPublisher); + sessionRepository.afterDestroy(mockEntryEvent); + + assertThat(sessionRepository.getApplicationEventPublisher()).isSameAs(mockApplicationEventPublisher); + + verify(mockEntryEvent, times(1)).getKey(); + verify(mockEntryEvent, never()).getNewValue(); + verify(mockEntryEvent, times(1)).getOldValue(); + verify(mockApplicationEventPublisher, times(1)).publishEvent(isA(SessionDestroyedEvent.class)); + } + + @Test + @SuppressWarnings("unchecked") + public void afterInvalidateWithSessionPublishesSessionExpiredEvent() { + final String sessionId = "abc123"; + final ExpiringSession mockSession = mock(ExpiringSession.class); + + when(mockSession.getId()).thenReturn(sessionId); + + ApplicationEventPublisher mockApplicationEventPublisher = mock(ApplicationEventPublisher.class); + + doAnswer(new Answer() { + public Void answer(final InvocationOnMock invocation) throws Throwable { + ApplicationEvent applicationEvent = invocation.getArgumentAt(0, ApplicationEvent.class); + + assertThat(applicationEvent).isInstanceOf(SessionExpiredEvent.class); + + AbstractSessionEvent sessionEvent = (AbstractSessionEvent) applicationEvent; + + assertThat(sessionEvent.getSource()).isEqualTo(sessionRepository); + assertThat(sessionEvent.getSession()).isEqualTo(mockSession); + assertThat(sessionEvent.getSessionId()).isEqualTo(sessionId); + + return null; + } + }).when(mockApplicationEventPublisher).publishEvent(isA(ApplicationEvent.class)); + + EntryEvent mockEntryEvent = mock(EntryEvent.class); + + when(mockEntryEvent.getKey()).thenReturn(sessionId); + when(mockEntryEvent.getNewValue()).thenReturn(null); + when(mockEntryEvent.getOldValue()).thenReturn(mockSession); + + sessionRepository.setApplicationEventPublisher(mockApplicationEventPublisher); + sessionRepository.afterInvalidate(mockEntryEvent); + + assertThat(sessionRepository.getApplicationEventPublisher()).isSameAs(mockApplicationEventPublisher); + + verify(mockEntryEvent, times(1)).getKey(); + verify(mockEntryEvent, never()).getNewValue(); + verify(mockEntryEvent, times(1)).getOldValue(); + verify(mockSession, times(1)).getId(); + verify(mockApplicationEventPublisher, times(1)).publishEvent(isA(SessionExpiredEvent.class)); + } + + @Test + @SuppressWarnings("unchecked") + public void afterInvalidateWithSessionIdPublishesSessionExpiredEvent() { + final String sessionId = "abc123"; + + ApplicationEventPublisher mockApplicationEventPublisher = mock(ApplicationEventPublisher.class); + + doAnswer(new Answer() { + public Void answer(final InvocationOnMock invocation) throws Throwable { + ApplicationEvent applicationEvent = invocation.getArgumentAt(0, ApplicationEvent.class); + + assertThat(applicationEvent).isInstanceOf(SessionExpiredEvent.class); + + AbstractSessionEvent sessionEvent = (AbstractSessionEvent) applicationEvent; + + assertThat(sessionEvent.getSource()).isEqualTo(sessionRepository); + assertThat(sessionEvent.getSession()).isNull(); + assertThat(sessionEvent.getSessionId()).isEqualTo(sessionId); + + return null; + } + }).when(mockApplicationEventPublisher).publishEvent(isA(ApplicationEvent.class)); + + EntryEvent mockEntryEvent = mock(EntryEvent.class); + + when(mockEntryEvent.getKey()).thenReturn(sessionId); + when(mockEntryEvent.getNewValue()).thenReturn(null); + when(mockEntryEvent.getOldValue()).thenReturn(null); + + sessionRepository.setApplicationEventPublisher(mockApplicationEventPublisher); + sessionRepository.afterInvalidate(mockEntryEvent); + + assertThat(sessionRepository.getApplicationEventPublisher()).isSameAs(mockApplicationEventPublisher); + + verify(mockEntryEvent, times(1)).getKey(); + verify(mockEntryEvent, never()).getNewValue(); + verify(mockEntryEvent, times(1)).getOldValue(); + verify(mockApplicationEventPublisher, times(1)).publishEvent(isA(SessionExpiredEvent.class)); + } + + @Test + public void handleDeletedWithSessionPublishesSessionDeletedEvent() { + final String sessionId = "abc123"; + final ExpiringSession mockSession = mock(ExpiringSession.class); + + when(mockSession.getId()).thenReturn(sessionId); + + ApplicationEventPublisher mockApplicationEventPublisher = mock(ApplicationEventPublisher.class); + + doAnswer(new Answer() { + public Void answer(final InvocationOnMock invocation) throws Throwable { + ApplicationEvent applicationEvent = invocation.getArgumentAt(0, ApplicationEvent.class); + + assertThat(applicationEvent).isInstanceOf(SessionDeletedEvent.class); + + AbstractSessionEvent sessionEvent = (AbstractSessionEvent) applicationEvent; + + assertThat(sessionEvent.getSource()).isEqualTo(sessionRepository); + assertThat(sessionEvent.getSession()).isEqualTo(mockSession); + assertThat(sessionEvent.getSessionId()).isEqualTo(sessionId); + + return null; + } + }).when(mockApplicationEventPublisher).publishEvent(isA(ApplicationEvent.class)); + + sessionRepository.setApplicationEventPublisher(mockApplicationEventPublisher); + sessionRepository.handleDeleted(sessionId, mockSession); + + assertThat(sessionRepository.getApplicationEventPublisher()).isSameAs(mockApplicationEventPublisher); + + verify(mockSession, times(1)).getId(); + verify(mockApplicationEventPublisher, times(1)).publishEvent(isA(SessionDeletedEvent.class)); + } + + @Test + public void handleDeletedWithSessionIdPublishesSessionDeletedEvent() { + final String sessionId = "abc123"; + + ApplicationEventPublisher mockApplicationEventPublisher = mock(ApplicationEventPublisher.class); + + doAnswer(new Answer() { + public Void answer(final InvocationOnMock invocation) throws Throwable { + ApplicationEvent applicationEvent = invocation.getArgumentAt(0, ApplicationEvent.class); + + assertThat(applicationEvent).isInstanceOf(SessionDeletedEvent.class); + + AbstractSessionEvent sessionEvent = (AbstractSessionEvent) applicationEvent; + + assertThat(sessionEvent.getSource()).isEqualTo(sessionRepository); + assertThat(sessionEvent.getSession()).isNull(); + assertThat(sessionEvent.getSessionId()).isEqualTo(sessionId); + + return null; + } + }).when(mockApplicationEventPublisher).publishEvent(isA(ApplicationEvent.class)); + + sessionRepository.setApplicationEventPublisher(mockApplicationEventPublisher); + sessionRepository.handleDeleted(sessionId, null); + + assertThat(sessionRepository.getApplicationEventPublisher()).isSameAs(mockApplicationEventPublisher); + + verify(mockApplicationEventPublisher, times(1)).publishEvent(isA(SessionDeletedEvent.class)); + } + + @Test + public void publishEventHandlesThrowable() { + ApplicationEvent mockApplicationEvent = mock(ApplicationEvent.class); + + ApplicationEventPublisher mockApplicationEventPublisher = mock(ApplicationEventPublisher.class); + + doThrow(new IllegalStateException("test")).when(mockApplicationEventPublisher) + .publishEvent(any(ApplicationEvent.class)); + + sessionRepository.setApplicationEventPublisher(mockApplicationEventPublisher); + sessionRepository.publishEvent(mockApplicationEvent); + + assertThat(sessionRepository.getApplicationEventPublisher()).isSameAs(mockApplicationEventPublisher); + + verify(mockApplicationEventPublisher, times(1)).publishEvent(eq(mockApplicationEvent)); + verify(mockLog, times(1)).error(eq(String.format("error occurred publishing event (%1$s)", mockApplicationEvent)), + isA(IllegalStateException.class)); + } + + @Test + public void constructGemFireSessionWithDefaultInitialization() { + final long beforeOrAtCreationTime = System.currentTimeMillis(); + + GemFireSession session = new GemFireSession(); + + assertThat(session.getId()).isNotNull(); + assertThat(session.getCreationTime()).isGreaterThanOrEqualTo(beforeOrAtCreationTime); + assertThat(session.getLastAccessedTime()).isGreaterThanOrEqualTo(beforeOrAtCreationTime); + assertThat(session.getMaxInactiveIntervalInSeconds()).isEqualTo(0); + assertThat(session.getAttributeNames()).isNotNull(); + assertThat(session.getAttributeNames().isEmpty()).isTrue(); + } + + @Test + public void constructGemFireSessionWithId() { + final long beforeOrAtCreationTime = System.currentTimeMillis(); + + GemFireSession session = new GemFireSession("1"); + + assertThat(session.getId()).isEqualTo("1"); + assertThat(session.getCreationTime()).isGreaterThanOrEqualTo(beforeOrAtCreationTime); + assertThat(session.getLastAccessedTime()).isGreaterThanOrEqualTo(beforeOrAtCreationTime); + assertThat(session.getMaxInactiveIntervalInSeconds()).isEqualTo(0); + assertThat(session.getAttributeNames()).isNotNull(); + assertThat(session.getAttributeNames().isEmpty()).isTrue(); + } + + @Test + public void constructGemFireSessionWithSession() { + final long expectedCreationTime = 1l; + final long expectedLastAccessTime = 2l; + + ExpiringSession mockSession = mockSession("2", expectedCreationTime, expectedLastAccessTime, + MAX_INACTIVE_INTERVAL_IN_SECONDS); + + Set expectedAttributedNames = asSet("attrOne", "attrTwo"); + + when(mockSession.getAttributeNames()).thenReturn(expectedAttributedNames); + when(mockSession.getAttribute(eq("attrOne"))).thenReturn("testOne"); + when(mockSession.getAttribute(eq("attrTwo"))).thenReturn("testTwo"); + + GemFireSession gemfireSession = new GemFireSession(mockSession); + + assertThat(gemfireSession.getId()).isEqualTo("2"); + assertThat(gemfireSession.getCreationTime()).isEqualTo(expectedCreationTime); + assertThat(gemfireSession.getLastAccessedTime()).isEqualTo(expectedLastAccessTime); + assertThat(gemfireSession.getMaxInactiveIntervalInSeconds()).isEqualTo(MAX_INACTIVE_INTERVAL_IN_SECONDS); + assertThat(gemfireSession.getAttributeNames()).isEqualTo(expectedAttributedNames); + assertThat(String.valueOf(gemfireSession.getAttribute("attrOne"))).isEqualTo("testOne"); + assertThat(String.valueOf(gemfireSession.getAttribute("attrTwo"))).isEqualTo("testTwo"); + + verify(mockSession, times(1)).getId(); + verify(mockSession, times(1)).getCreationTime(); + verify(mockSession, times(1)).getLastAccessedTime(); + verify(mockSession, times(1)).getMaxInactiveIntervalInSeconds(); + verify(mockSession, times(1)).getAttributeNames(); + verify(mockSession, times(1)).getAttribute(eq("attrOne")); + verify(mockSession, times(1)).getAttribute(eq("attrTwo")); + } + + @Test + public void constructGemFireSessionWithNullSession() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("The ExpiringSession to copy cannot be null"); + new GemFireSession((ExpiringSession) null); + } + + @Test + public void constructGemFireSessionWithUnspecifiedId() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("ID must be specified"); + new GemFireSession(" "); + } + + @Test + public void createNewGemFireSession() { + final long beforeOrAtCreationTime = System.currentTimeMillis(); + + GemFireSession session = GemFireSession.create(120); + + assertThat(session).isNotNull(); + assertThat(session.getId()).isNotNull(); + assertThat(session.getCreationTime()).isGreaterThanOrEqualTo(beforeOrAtCreationTime); + assertThat(session.getLastAccessedTime()).isEqualTo(session.getCreationTime()); + assertThat(session.getMaxInactiveIntervalInSeconds()).isEqualTo(120); + assertThat(session.getAttributeNames()).isNotNull(); + assertThat(session.getAttributeNames().isEmpty()).isTrue(); + } + + @Test + public void fromExistingSession() { + final long expectedCreationTime = 1l; + final long expectedLastAccessedTime = 2l; + + ExpiringSession mockSession = mockSession("4", expectedCreationTime, expectedLastAccessedTime, + MAX_INACTIVE_INTERVAL_IN_SECONDS); + + when(mockSession.getAttributeNames()).thenReturn(Collections.emptySet()); + + GemFireSession gemfireSession = GemFireSession.from(mockSession); + + assertThat(gemfireSession).isNotNull(); + assertThat(gemfireSession.getId()).isEqualTo("4"); + assertThat(gemfireSession.getCreationTime()).isEqualTo(expectedCreationTime); + assertThat(gemfireSession.getLastAccessedTime()).isNotEqualTo(expectedLastAccessedTime); + assertThat(gemfireSession.getLastAccessedTime()).isGreaterThanOrEqualTo(expectedCreationTime); + assertThat(gemfireSession.getLastAccessedTime()).isLessThanOrEqualTo(System.currentTimeMillis()); + assertThat(gemfireSession.getMaxInactiveIntervalInSeconds()).isEqualTo(MAX_INACTIVE_INTERVAL_IN_SECONDS); + assertThat(gemfireSession.getAttributeNames()).isNotNull(); + assertThat(gemfireSession.getAttributeNames().isEmpty()).isTrue(); + + verify(mockSession, times(1)).getId(); + verify(mockSession, times(1)).getCreationTime(); + verify(mockSession, times(1)).getLastAccessedTime(); + verify(mockSession, times(1)).getMaxInactiveIntervalInSeconds(); + verify(mockSession, times(1)).getAttributeNames(); + verify(mockSession, never()).getAttribute(anyString()); + } + + @Test + public void setGetAndRemoveAttribute() { + GemFireSession session = GemFireSession.create(60); + + assertThat(session).isNotNull(); + assertThat(session.getMaxInactiveIntervalInSeconds()).isEqualTo(60); + assertThat(session.getAttributeNames().isEmpty()).isTrue(); + + session.setAttribute("attrOne", "testOne"); + + assertThat(session.getAttributeNames()).isEqualTo(asSet("attrOne")); + assertThat(String.valueOf(session.getAttribute("attrOne"))).isEqualTo("testOne"); + assertThat(session.getAttribute("attrTwo")).isNull(); + + session.setAttribute("attrTwo", "testTwo"); + + assertThat(session.getAttributeNames()).isEqualTo(asSet("attrOne", "attrTwo")); + assertThat(String.valueOf(session.getAttribute("attrOne"))).isEqualTo("testOne"); + assertThat(String.valueOf(session.getAttribute("attrTwo"))).isEqualTo("testTwo"); + + session.setAttribute("attrTwo", null); + + assertThat(session.getAttributeNames()).isEqualTo(asSet("attrOne")); + assertThat(String.valueOf(session.getAttribute("attrOne"))).isEqualTo("testOne"); + assertThat(session.getAttribute("attrTwo")).isNull(); + + session.removeAttribute("attrOne"); + + assertThat(session.getAttribute("attrOne")).isNull(); + assertThat(session.getAttribute("attrTwo")).isNull(); + assertThat(session.getAttributeNames().isEmpty()).isTrue(); + } + + @Test + public void isExpiredIsFalseWhenMaxInactiveIntervalIsNegative() { + final int expectedMaxInactiveIntervalInSeconds = -1; + + GemFireSession session = GemFireSession.create(expectedMaxInactiveIntervalInSeconds); + + assertThat(session).isNotNull(); + assertThat(session.getMaxInactiveIntervalInSeconds()).isEqualTo(expectedMaxInactiveIntervalInSeconds); + assertThat(session.isExpired()).isFalse(); + } + + @Test + public void isExpiredIsFalseWhenSessionIsActive() { + final int expectedMaxInactiveIntervalInSeconds = (int) TimeUnit.HOURS.toSeconds(2); + + GemFireSession session = GemFireSession.create(expectedMaxInactiveIntervalInSeconds); + + assertThat(session).isNotNull(); + assertThat(session.getMaxInactiveIntervalInSeconds()).isEqualTo(expectedMaxInactiveIntervalInSeconds); + + final long now = System.currentTimeMillis(); + + session.setLastAccessedTime(now); + + assertThat(session.getLastAccessedTime()).isEqualTo(now); + assertThat(session.isExpired()).isFalse(); + } + + @Test + public void isExpiredIsTrueWhenSessionIsInactive() { + final int expectedMaxInactiveIntervalInSeconds = 60; + + GemFireSession session = GemFireSession.create(expectedMaxInactiveIntervalInSeconds); + + assertThat(session).isNotNull(); + assertThat(session.getMaxInactiveIntervalInSeconds()).isEqualTo(expectedMaxInactiveIntervalInSeconds); + + final long twoHoursAgo = (System.currentTimeMillis() - TimeUnit.HOURS.toMillis(2)); + + session.setLastAccessedTime(twoHoursAgo); + + assertThat(session.getLastAccessedTime()).isEqualTo(twoHoursAgo); + assertThat(session.isExpired()).isTrue(); + } + + @Test + public void setAndGetPrincipalName() { + GemFireSession session = GemFireSession.create(0); + + assertThat(session).isNotNull(); + assertThat(session.getPrincipalName()).isNull(); + + session.setPrincipalName("jblum"); + + assertThat(session.getPrincipalName()).isEqualTo("jblum"); + assertThat(session.getAttributeNames()).isEqualTo(asSet(Session.PRINCIPAL_NAME_ATTRIBUTE_NAME)); + assertThat(String.valueOf(session.getAttribute(Session.PRINCIPAL_NAME_ATTRIBUTE_NAME))).isEqualTo("jblum"); + + session.setAttribute(Session.PRINCIPAL_NAME_ATTRIBUTE_NAME, "rwinch"); + + assertThat(session.getAttributeNames()).isEqualTo(asSet(Session.PRINCIPAL_NAME_ATTRIBUTE_NAME)); + assertThat(String.valueOf(session.getAttribute(Session.PRINCIPAL_NAME_ATTRIBUTE_NAME))).isEqualTo("rwinch"); + assertThat(session.getPrincipalName()).isEqualTo("rwinch"); + + session.removeAttribute(Session.PRINCIPAL_NAME_ATTRIBUTE_NAME); + + assertThat(session.getPrincipalName()).isNull(); + } + + @Test + public void sessionToData() throws Exception { + GemFireSession session = new GemFireSession("1") { + @Override void writeObject(Object obj, DataOutput out) throws IOException { + assertThat(obj).isInstanceOf(GemFireSessionAttributes.class); + assertThat(out).isNotNull(); + } + }; + + session.setLastAccessedTime(123l); + session.setMaxInactiveIntervalInSeconds(MAX_INACTIVE_INTERVAL_IN_SECONDS); + session.setPrincipalName("jblum"); + + DataOutput mockDataOutput = mock(DataOutput.class); + + session.toData(mockDataOutput); + + verify(mockDataOutput, times(1)).writeUTF(eq("1")); + verify(mockDataOutput, times(1)).writeLong(eq(session.getCreationTime())); + verify(mockDataOutput, times(1)).writeLong(eq(session.getLastAccessedTime())); + verify(mockDataOutput, times(1)).writeInt(eq(session.getMaxInactiveIntervalInSeconds())); + verify(mockDataOutput, times(1)).writeInt(eq("jblum".length())); + verify(mockDataOutput, times(1)).writeUTF(eq(session.getPrincipalName())); + } + + @Test + public void sessionFromData() throws Exception { + final long expectedCreationTime = 1l; + final long expectedLastAccessedTime = 2l; + + final int expectedMaxInactiveIntervalInSeconds = (int) TimeUnit.HOURS.toSeconds(6); + + final String expectedPrincipalName = "jblum"; + + DataInput mockDataInput = mock(DataInput.class); + + when(mockDataInput.readUTF()).thenReturn("2").thenReturn(expectedPrincipalName); + when(mockDataInput.readLong()).thenReturn(expectedCreationTime).thenReturn(expectedLastAccessedTime); + when(mockDataInput.readInt()).thenReturn(expectedMaxInactiveIntervalInSeconds); + + GemFireSession session = new GemFireSession("1") { + @Override @SuppressWarnings("unchecked") + T readObject(DataInput in) throws ClassNotFoundException, IOException { + assertThat(in).isNotNull(); + + GemFireSessionAttributes sessionAttributes = new GemFireSessionAttributes(); + + sessionAttributes.setAttribute("attrOne", "testOne"); + sessionAttributes.setAttribute("attrTwo", "testTwo"); + + return (T) sessionAttributes; + } + }; + + session.fromData(mockDataInput); + + Set expectedAttributeNames = asSet("attrOne", "attrTwo", Session.PRINCIPAL_NAME_ATTRIBUTE_NAME); + + assertThat(session.getId()).isEqualTo("2"); + assertThat(session.getCreationTime()).isEqualTo(expectedCreationTime); + assertThat(session.getLastAccessedTime()).isEqualTo(expectedLastAccessedTime); + assertThat(session.getMaxInactiveIntervalInSeconds()).isEqualTo(expectedMaxInactiveIntervalInSeconds); + assertThat(session.getPrincipalName()).isEqualTo(expectedPrincipalName); + assertThat(session.getAttributeNames().size()).isEqualTo(3); + assertThat(session.getAttributeNames().containsAll(expectedAttributeNames)).isTrue(); + assertThat(String.valueOf(session.getAttribute("attrOne"))).isEqualTo("testOne"); + assertThat(String.valueOf(session.getAttribute("attrTwo"))).isEqualTo("testTwo"); + assertThat(String.valueOf(session.getAttribute(Session.PRINCIPAL_NAME_ATTRIBUTE_NAME))) + .isEqualTo(expectedPrincipalName); + + verify(mockDataInput, times(2)).readUTF(); + verify(mockDataInput, times(2)).readLong(); + verify(mockDataInput, times(2)).readInt(); + } + + @Test + public void sessionToDataThenFromDataWhenPrincipalNameIsNullGetsHandledProperly() + throws ClassNotFoundException, IOException { + + final long beforeOrAtCreationTime = System.currentTimeMillis(); + + GemFireSession expectedSession = new GemFireSession("123") { + @Override void writeObject(Object obj, DataOutput out) throws IOException { + assertThat(obj).isInstanceOf(GemFireSessionAttributes.class); + assertThat(out).isNotNull(); + } + }; + + assertThat(expectedSession.getId()).isEqualTo("123"); + assertThat(expectedSession.getCreationTime()).isGreaterThanOrEqualTo(beforeOrAtCreationTime); + assertThat(expectedSession.getLastAccessedTime()).isGreaterThanOrEqualTo(expectedSession.getCreationTime()); + assertThat(expectedSession.getMaxInactiveIntervalInSeconds()).isEqualTo(0); + assertThat(expectedSession.getPrincipalName()).isNull(); + + ByteArrayOutputStream outBytes = new ByteArrayOutputStream(); + + expectedSession.toData(new DataOutputStream(outBytes)); + + GemFireSession deserializedSession = new GemFireSession("0") { + @SuppressWarnings("unchecked") + @Override T readObject(DataInput in) throws ClassNotFoundException, IOException { + return (T) new GemFireSessionAttributes(); + } + }; + + deserializedSession.fromData(new DataInputStream(new ByteArrayInputStream(outBytes.toByteArray()))); + + assertThat(deserializedSession).isEqualTo(expectedSession); + assertThat(deserializedSession.getCreationTime()).isEqualTo(expectedSession.getCreationTime()); + assertThat(deserializedSession.getLastAccessedTime()).isEqualTo(expectedSession.getLastAccessedTime()); + assertThat(deserializedSession.getMaxInactiveIntervalInSeconds()).isEqualTo( + expectedSession.getMaxInactiveIntervalInSeconds()); + assertThat(deserializedSession.getPrincipalName()).isNull(); + } + + @Test + public void hasDeltaWhenNoSessionChangesIsFalse() { + assertThat(new GemFireSession().hasDelta()).isFalse(); + } + + @Test + public void hasDeltaWhenSessionAttributesChangeIsTrue() { + GemFireSession session = new GemFireSession(); + + assertThat(session.hasDelta()).isFalse(); + + session.setAttribute("attrOne", "test"); + + assertThat(session.hasDelta()).isTrue(); + } + + @Test + public void hasDeltaWhenSessionLastAccessedTimeIsUpdatedIsTrue() { + final long expectedLastAccessTime = 1l; + + GemFireSession session = new GemFireSession(); + + assertThat(session.getLastAccessedTime()).isNotEqualTo(expectedLastAccessTime); + assertThat(session.hasDelta()).isFalse(); + + session.setLastAccessedTime(expectedLastAccessTime); + + assertThat(session.getLastAccessedTime()).isEqualTo(expectedLastAccessTime); + assertThat(session.hasDelta()).isTrue(); + + session.setLastAccessedTime(expectedLastAccessTime); + + assertThat(session.getLastAccessedTime()).isEqualTo(expectedLastAccessTime); + assertThat(session.hasDelta()).isTrue(); + } + + @Test + public void hasDeltaWhenSessionMaxInactiveIntervalInSecondsIsUpdatedIsTrue() { + final int expectedMaxInactiveIntervalInSeconds = 300; + + GemFireSession session = new GemFireSession(); + + assertThat(session.getMaxInactiveIntervalInSeconds()).isNotEqualTo(expectedMaxInactiveIntervalInSeconds); + assertThat(session.hasDelta()).isFalse(); + + session.setMaxInactiveIntervalInSeconds(expectedMaxInactiveIntervalInSeconds); + + assertThat(session.getMaxInactiveIntervalInSeconds()).isEqualTo(expectedMaxInactiveIntervalInSeconds); + assertThat(session.hasDelta()).isTrue(); + + session.setMaxInactiveIntervalInSeconds(expectedMaxInactiveIntervalInSeconds); + + assertThat(session.getMaxInactiveIntervalInSeconds()).isEqualTo(expectedMaxInactiveIntervalInSeconds); + assertThat(session.hasDelta()).isTrue(); + } + + @Test + public void sessionToDelta() throws Exception { + final DataOutput mockDataOutput = mock(DataOutput.class); + + GemFireSession session = new GemFireSession() { + @Override void writeObject(Object obj, DataOutput out) throws IOException { + assertThat(String.valueOf(obj)).isEqualTo("test"); + assertThat(out).isSameAs(mockDataOutput); + } + }; + + session.setLastAccessedTime(1l); + session.setMaxInactiveIntervalInSeconds(300); + session.setAttribute("attrOne", "test"); + + assertThat(session.hasDelta()).isTrue(); + + session.toDelta(mockDataOutput); + + assertThat(session.hasDelta()).isFalse(); + + verify(mockDataOutput, times(1)).writeLong(eq(1l)); + verify(mockDataOutput, times(1)).writeInt(eq(300)); + verify(mockDataOutput, times(1)).writeInt(eq(1)); + verify(mockDataOutput, times(1)).writeUTF(eq("attrOne")); + } + + @Test + public void sessionFromDelta() throws Exception { + final DataInput mockDataInput = mock(DataInput.class); + + when(mockDataInput.readLong()).thenReturn(1l); + when(mockDataInput.readInt()).thenReturn(600).thenReturn(0); + + GemFireSession session = new GemFireSession() { + @Override @SuppressWarnings("unchecked") + T readObject(DataInput in) throws ClassNotFoundException, IOException { + assertThat(in).isSameAs(mockDataInput); + return (T) "test"; + } + }; + + session.fromDelta(mockDataInput); + + assertThat(session.hasDelta()).isFalse(); + assertThat(session.getLastAccessedTime()).isEqualTo(1l); + assertThat(session.getMaxInactiveIntervalInSeconds()).isEqualTo(600); + assertThat(session.getAttributeNames().isEmpty()).isTrue(); + + verify(mockDataInput, times(1)).readLong(); + verify(mockDataInput, times(2)).readInt(); + verify(mockDataInput, never()).readUTF(); + } + + @Test + public void sessionComparisons() { + final long twoHoursAgo = (System.currentTimeMillis() - TimeUnit.HOURS.toMillis(2)); + + GemFireSession sessionOne = new GemFireSession(mockSession("1", twoHoursAgo, MAX_INACTIVE_INTERVAL_IN_SECONDS)); + GemFireSession sessionTwo = new GemFireSession("2"); + + assertThat(sessionOne.getCreationTime()).isEqualTo(twoHoursAgo); + assertThat(sessionTwo.getCreationTime()).isGreaterThan(twoHoursAgo); + assertThat(sessionOne.compareTo(sessionTwo)).isLessThan(0); + assertThat(sessionOne.compareTo(sessionOne)).isEqualTo(0); + assertThat(sessionTwo.compareTo(sessionOne)).isGreaterThan(0); + } + + @Test + public void sessionEqualsDifferentSessionBasedOnId() { + GemFireSession sessionOne = new GemFireSession("1"); + + sessionOne.setLastAccessedTime(12345l); + sessionOne.setMaxInactiveIntervalInSeconds(120); + sessionOne.setPrincipalName("jblum"); + + GemFireSession sessionTwo = new GemFireSession("1"); + + sessionTwo.setLastAccessedTime(67890l); + sessionTwo.setMaxInactiveIntervalInSeconds(300); + sessionTwo.setPrincipalName("rwinch"); + + assertThat(sessionOne.getId().equals(sessionTwo.getId())).isTrue(); + assertThat(sessionOne.getLastAccessedTime() == sessionTwo.getLastAccessedTime()).isFalse(); + assertThat(sessionOne.getMaxInactiveIntervalInSeconds() == sessionTwo.getMaxInactiveIntervalInSeconds()).isFalse(); + assertThat(sessionOne.getPrincipalName().equals(sessionTwo.getPrincipalName())).isFalse(); + assertThat(sessionOne.equals(sessionTwo)).isTrue(); + } + + @Test + public void sessionHashCodeIsNotEqualToStringIdHashCode() { + GemFireSession session = new GemFireSession("1"); + + assertThat(session.getId()).isEqualTo("1"); + assertThat(session.hashCode()).isNotEqualTo("1".hashCode()); + } + + @Test + public void sessionAttributesFromSession() { + Session mockSession = mock(Session.class); + + when(mockSession.getAttributeNames()).thenReturn(asSet("attrOne", "attrTwo")); + when(mockSession.getAttribute(eq("attrOne"))).thenReturn("testOne"); + when(mockSession.getAttribute(eq("attrTwo"))).thenReturn("testTwo"); + + GemFireSessionAttributes sessionAttributes = new GemFireSessionAttributes(); + + assertThat(sessionAttributes.getAttributeNames().isEmpty()).isTrue(); + + sessionAttributes.from(mockSession); + + assertThat(sessionAttributes.getAttributeNames().size()).isEqualTo(2); + assertThat(sessionAttributes.getAttributeNames().containsAll(asSet("attrOne", "attrTwo"))).isTrue(); + assertThat(String.valueOf(sessionAttributes.getAttribute("attrOne"))).isEqualTo("testOne"); + assertThat(String.valueOf(sessionAttributes.getAttribute("attrTwo"))).isEqualTo("testTwo"); + + verify(mockSession, times(1)).getAttributeNames(); + verify(mockSession, times(1)).getAttribute(eq("attrOne")); + verify(mockSession, times(1)).getAttribute(eq("attrTwo")); + } + + @Test + public void sessionAttributesFromSessionAttributes() { + GemFireSessionAttributes source = new GemFireSessionAttributes(); + + source.setAttribute("attrOne", "testOne"); + source.setAttribute("attrTwo", "testTwo"); + + GemFireSessionAttributes target = new GemFireSessionAttributes(); + + assertThat(target.getAttributeNames().isEmpty()).isTrue(); + + target.from(source); + + assertThat(target.getAttributeNames().size()).isEqualTo(2); + assertThat(target.getAttributeNames().containsAll(asSet("attrOne", "attrTwo"))).isTrue(); + assertThat(String.valueOf(target.getAttribute("attrOne"))).isEqualTo("testOne"); + assertThat(String.valueOf(target.getAttribute("attrTwo"))).isEqualTo("testTwo"); + } + + @Test + public void sessionAttributesToData() throws Exception { + final DataOutput mockDataOutput = mock(DataOutput.class); + + GemFireSessionAttributes sessionAttributes = new GemFireSessionAttributes() { + private int count = 0; + @Override void writeObject(Object obj, DataOutput out) throws IOException { + assertThat(Arrays.asList("testOne", "testTwo").get(count++)).isEqualTo(String.valueOf(obj)); + assertThat(out).isSameAs(mockDataOutput); + } + }; + + sessionAttributes.setAttribute("attrOne", "testOne"); + sessionAttributes.setAttribute("attrTwo", "testTwo"); + + sessionAttributes.toData(mockDataOutput); + + verify(mockDataOutput, times(1)).writeInt(eq(2)); + verify(mockDataOutput, times(1)).writeUTF(eq("attrOne")); + verify(mockDataOutput, times(1)).writeUTF(eq("attrTwo")); + } + + @Test + public void sessionAttributesFromData() throws Exception { + final DataInput mockDataInput = mock(DataInput.class); + + when(mockDataInput.readInt()).thenReturn(2); + when(mockDataInput.readUTF()).thenReturn("attrOne").thenReturn("attrTwo"); + + GemFireSessionAttributes sessionAttributes = new GemFireSessionAttributes() { + private int count = 0; + @Override @SuppressWarnings("unchecked") + T readObject(DataInput in) throws ClassNotFoundException, IOException { + assertThat(in).isSameAs(mockDataInput); + return (T) Arrays.asList("testOne", "testTwo").get(count++); + } + }; + + assertThat(sessionAttributes.getAttributeNames().isEmpty()).isTrue(); + + sessionAttributes.fromData(mockDataInput); + + assertThat(sessionAttributes.getAttributeNames().size()).isEqualTo(2); + assertThat(sessionAttributes.getAttributeNames().containsAll(asSet("attrOne", "attrTwo"))).isTrue(); + assertThat(String.valueOf(sessionAttributes.getAttribute("attrOne"))).isEqualTo("testOne"); + assertThat(String.valueOf(sessionAttributes.getAttribute("attrTwo"))).isEqualTo("testTwo"); + + verify(mockDataInput, times(1)).readInt(); + verify(mockDataInput, times(2)).readUTF(); + } + + @Test + public void sessionAttributesHasDeltaIsFalse() { + assertThat(new GemFireSessionAttributes().hasDelta()).isFalse(); + } + + @Test + public void sessionAttributesHasDeltaIsTrue() { + GemFireSessionAttributes sessionAttributes = new GemFireSessionAttributes(); + + assertThat(sessionAttributes.hasDelta()).isFalse(); + + sessionAttributes.setAttribute("attrOne", "testOne"); + + assertThat(String.valueOf(sessionAttributes.getAttribute("attrOne"))).isEqualTo("testOne"); + assertThat(sessionAttributes.hasDelta()).isTrue(); + } + + @Test + public void sessionAttributesToDelta() throws Exception { + final DataOutput mockDataOutput = mock(DataOutput.class); + + GemFireSessionAttributes sessionAttributes = new GemFireSessionAttributes() { + private int count = 0; + @Override void writeObject(Object obj, DataOutput out) throws IOException { + assertThat(Arrays.asList("testOne", "testTwo", "testThree").get(count++)).isEqualTo(String.valueOf(obj)); + assertThat(out).isSameAs(mockDataOutput); + } + }; + + sessionAttributes.setAttribute("attrOne", "testOne"); + sessionAttributes.setAttribute("attrTwo", "testTwo"); + + assertThat(sessionAttributes.hasDelta()).isTrue(); + + sessionAttributes.toDelta(mockDataOutput); + + assertThat(sessionAttributes.hasDelta()).isFalse(); + + verify(mockDataOutput, times(1)).writeInt(eq(2)); + verify(mockDataOutput, times(1)).writeUTF("attrOne"); + verify(mockDataOutput, times(1)).writeUTF("attrTwo"); + reset(mockDataOutput); + + sessionAttributes.setAttribute("attrOne", "testOne"); + + assertThat(sessionAttributes.hasDelta()).isFalse(); + + sessionAttributes.toDelta(mockDataOutput); + + verify(mockDataOutput, times(1)).writeInt(eq(0)); + verify(mockDataOutput, never()).writeUTF(any(String.class)); + reset(mockDataOutput); + + sessionAttributes.setAttribute("attrTwo", "testThree"); + + assertThat(sessionAttributes.hasDelta()).isTrue(); + + sessionAttributes.toDelta(mockDataOutput); + + verify(mockDataOutput, times(1)).writeInt(eq(1)); + verify(mockDataOutput, times(1)).writeUTF(eq("attrTwo")); + } + + @Test + public void sessionAttributesFromDelta() throws Exception { + final DataInput mockDataInput = mock(DataInput.class); + + when(mockDataInput.readInt()).thenReturn(2); + when(mockDataInput.readUTF()).thenReturn("attrOne").thenReturn("attrTwo"); + + GemFireSessionAttributes sessionAttributes = new GemFireSessionAttributes() { + private int count = 0; + @Override @SuppressWarnings("unchecked") + T readObject(DataInput in) throws ClassNotFoundException, IOException { + assertThat(in).isSameAs(mockDataInput); + return (T) Arrays.asList("testOne", "testTwo", "testThree").get(count++); + } + }; + + sessionAttributes.setAttribute("attrOne", "one"); + sessionAttributes.setAttribute("attrTwo", "two"); + + assertThat(sessionAttributes.getAttributeNames().size()).isEqualTo(2); + assertThat(sessionAttributes.getAttributeNames().containsAll(asSet("attrOne", "attrTwo"))).isTrue(); + assertThat(String.valueOf(sessionAttributes.getAttribute("attrOne"))).isEqualTo("one"); + assertThat(String.valueOf(sessionAttributes.getAttribute("attrTwo"))).isEqualTo("two"); + assertThat(sessionAttributes.hasDelta()).isTrue(); + + sessionAttributes.fromDelta(mockDataInput); + + assertThat(sessionAttributes.getAttributeNames().size()).isEqualTo(2); + assertThat(sessionAttributes.getAttributeNames().containsAll(asSet("attrOne", "attrTwo"))).isTrue(); + assertThat(String.valueOf(sessionAttributes.getAttribute("attrOne"))).isEqualTo("testOne"); + assertThat(String.valueOf(sessionAttributes.getAttribute("attrTwo"))).isEqualTo("testTwo"); + assertThat(sessionAttributes.hasDelta()).isFalse(); + + verify(mockDataInput, times(1)).readInt(); + verify(mockDataInput, times(2)).readUTF(); + reset(mockDataInput); + + when(mockDataInput.readInt()).thenReturn(1); + when(mockDataInput.readUTF()).thenReturn("attrTwo"); + + sessionAttributes.setAttribute("attrOne", "one"); + sessionAttributes.setAttribute("attrTwo", "two"); + + assertThat(sessionAttributes.getAttributeNames().size()).isEqualTo(2); + assertThat(sessionAttributes.getAttributeNames().containsAll(asSet("attrOne", "attrTwo"))).isTrue(); + assertThat(String.valueOf(sessionAttributes.getAttribute("attrOne"))).isEqualTo("one"); + assertThat(String.valueOf(sessionAttributes.getAttribute("attrTwo"))).isEqualTo("two"); + assertThat(sessionAttributes.hasDelta()).isTrue(); + + sessionAttributes.fromDelta(mockDataInput); + + assertThat(sessionAttributes.getAttributeNames().size()).isEqualTo(2); + assertThat(sessionAttributes.getAttributeNames().containsAll(asSet("attrOne", "attrTwo"))).isTrue(); + assertThat(String.valueOf(sessionAttributes.getAttribute("attrOne"))).isEqualTo("one"); + assertThat(String.valueOf(sessionAttributes.getAttribute("attrTwo"))).isEqualTo("testThree"); + assertThat(sessionAttributes.hasDelta()).isTrue(); + + verify(mockDataInput, times(1)).readInt(); + verify(mockDataInput, times(1)).readUTF(); + } + + @Test + public void sessionWithAttributesAreThreadSafe() throws Throwable { + TestFramework.runOnce(new ThreadSafeSessionTest()); + } + + @SuppressWarnings("unused") + protected static final class ThreadSafeSessionTest extends MultithreadedTestCase { + + private final long beforeOrAtCreationTime = System.currentTimeMillis(); + + private GemFireSession session; + + private volatile long expectedCreationTime; + + @Override + public void initialize() { + session = new GemFireSession("1"); + + assertThat(session).isNotNull(); + assertThat(session.getId()).isEqualTo("1"); + assertThat(session.getCreationTime()).isGreaterThanOrEqualTo(beforeOrAtCreationTime); + assertThat(session.getLastAccessedTime()).isEqualTo(session.getCreationTime()); + assertThat(session.getMaxInactiveIntervalInSeconds()).isEqualTo(0); + assertThat(session.getPrincipalName()).isNull(); + assertThat(session.getAttributeNames().isEmpty()).isTrue(); + + expectedCreationTime = session.getCreationTime(); + + session.setLastAccessedTime(0l); + session.setMaxInactiveIntervalInSeconds(60); + session.setPrincipalName("jblum"); + } + + public void thread1() { + assertTick(0); + + Thread.currentThread().setName("HTTP Request Processing Thread 1"); + + assertThat(session).isNotNull(); + assertThat(session.getId()).isEqualTo("1"); + assertThat(session.getCreationTime()).isEqualTo(expectedCreationTime); + assertThat(session.getLastAccessedTime()).isEqualTo(0l); + assertThat(session.getMaxInactiveIntervalInSeconds()).isEqualTo(60); + assertThat(session.getPrincipalName()).isEqualTo("jblum"); + assertThat(session.getAttributeNames().size()).isEqualTo(1); + assertThat(String.valueOf(session.getAttribute(Session.PRINCIPAL_NAME_ATTRIBUTE_NAME))).isEqualTo("jblum"); + + session.setAttribute("tennis", "ping"); + session.setAttribute("junk", "test"); + session.setLastAccessedTime(1l); + session.setMaxInactiveIntervalInSeconds(120); + session.setPrincipalName("rwinch"); + + waitForTick(2); + + assertThat(session).isNotNull(); + assertThat(session.getId()).isEqualTo("1"); + assertThat(session.getCreationTime()).isEqualTo(expectedCreationTime); + assertThat(session.getLastAccessedTime()).isEqualTo(2l); + assertThat(session.getMaxInactiveIntervalInSeconds()).isEqualTo(180); + assertThat(session.getPrincipalName()).isEqualTo("ogierke"); + assertThat(session.getAttributeNames().size()).isEqualTo(3); + assertThat(session.getAttributeNames().containsAll(asSet("tennis", "greeting"))).isTrue(); + assertThat(session.getAttributeNames().contains("junk")).isFalse(); + assertThat(session.getAttribute("junk")).isNull(); + assertThat(String.valueOf(session.getAttribute("tennis"))).isEqualTo("pong"); + assertThat(String.valueOf(session.getAttribute("greeting"))).isEqualTo("hello"); + } + + public void thread2() { + assertTick(0); + + Thread.currentThread().setName("HTTP Request Processing Thread 2"); + + waitForTick(1); + assertTick(1); + + assertThat(session).isNotNull(); + assertThat(session.getId()).isEqualTo("1"); + assertThat(session.getCreationTime()).isEqualTo(expectedCreationTime); + assertThat(session.getLastAccessedTime()).isEqualTo(1l); + assertThat(session.getMaxInactiveIntervalInSeconds()).isEqualTo(120); + assertThat(session.getPrincipalName()).isEqualTo("rwinch"); + assertThat(session.getAttributeNames().size()).isEqualTo(3); + assertThat(session.getAttributeNames().containsAll(asSet("tennis", "junk"))).isTrue(); + assertThat(String.valueOf(session.getAttribute("junk"))).isEqualTo("test"); + assertThat(String.valueOf(session.getAttribute("tennis"))).isEqualTo("ping"); + + session.setAttribute("tennis", "pong"); + session.setAttribute("greeting", "hello"); + session.removeAttribute("junk"); + session.setLastAccessedTime(2l); + session.setMaxInactiveIntervalInSeconds(180); + session.setPrincipalName("ogierke"); + } + + @Override + public void finish() { + session = null; + } + } + + protected static class TestGemFireOperationsSessionRepository extends AbstractGemFireOperationsSessionRepository { + + protected TestGemFireOperationsSessionRepository(GemfireOperations gemfireOperations) { + super(gemfireOperations); + } + + public Map findByPrincipalName(String principalName) { + throw new UnsupportedOperationException("not implemented"); + } + + public ExpiringSession createSession() { + throw new UnsupportedOperationException("not implemented"); + } + + public ExpiringSession getSession(String id) { + throw new UnsupportedOperationException("not implemented"); + } + + public void save(ExpiringSession session) { + throw new UnsupportedOperationException("not implemented"); + } + + public void delete(String id) { + throw new UnsupportedOperationException("not implemented"); + } + } + +} diff --git a/spring-session/src/test/java/org/springframework/session/data/gemfire/GemFireOperationsSessionRepositoryTest.java b/spring-session/src/test/java/org/springframework/session/data/gemfire/GemFireOperationsSessionRepositoryTest.java new file mode 100644 index 00000000..3b682f40 --- /dev/null +++ b/spring-session/src/test/java/org/springframework/session/data/gemfire/GemFireOperationsSessionRepositoryTest.java @@ -0,0 +1,368 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * 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. + */ + +package org.springframework.session.data.gemfire; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Matchers.isA; +import static org.mockito.Matchers.same; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.springframework.session.data.gemfire.GemFireOperationsSessionRepository.GemFireSession; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.data.gemfire.GemfireAccessor; +import org.springframework.data.gemfire.GemfireOperations; +import org.springframework.session.ExpiringSession; +import org.springframework.session.events.AbstractSessionEvent; +import org.springframework.session.events.SessionDeletedEvent; + +import com.gemstone.gemfire.cache.AttributesMutator; +import com.gemstone.gemfire.cache.Region; +import com.gemstone.gemfire.cache.query.SelectResults; + +/** + * The GemFireOperationsSessionRepositoryTest class is a test suite of test cases testing the contract and functionality + * of the GemFireOperationsSessionRepository class. + * + * @author John Blum + * @see org.junit.Test + * @see org.junit.runner.RunWith + * @see org.mockito.Mock + * @see org.mockito.Mockito + * @see org.mockito.runners.MockitoJUnitRunner + * @see org.springframework.session.data.gemfire.GemFireOperationsSessionRepository + * @since 1.0.0 + */ +@RunWith(MockitoJUnitRunner.class) +public class GemFireOperationsSessionRepositoryTest { + + protected static final int MAX_INACTIVE_INTERVAL_IN_SECONDS = 600; + + @Mock + private ApplicationEventPublisher mockApplicationEventPublisher; + + @Mock + private AttributesMutator mockAttributesMutator; + + @Mock + private Region mockRegion; + + @Mock + private GemfireOperationsAccessor mockTemplate; + + private GemFireOperationsSessionRepository sessionRepository; + + @Before + public void setup() throws Exception { + when(mockRegion.getAttributesMutator()).thenReturn(mockAttributesMutator); + when(mockRegion.getFullPath()).thenReturn("/Example"); + when(mockTemplate.getRegion()).thenReturn(mockRegion); + + sessionRepository = new GemFireOperationsSessionRepository(mockTemplate); + sessionRepository.setApplicationEventPublisher(mockApplicationEventPublisher); + sessionRepository.setMaxInactiveIntervalInSeconds(MAX_INACTIVE_INTERVAL_IN_SECONDS); + sessionRepository.afterPropertiesSet(); + + assertThat(sessionRepository.getApplicationEventPublisher()).isSameAs(mockApplicationEventPublisher); + assertThat(sessionRepository.getFullyQualifiedRegionName()).isEqualTo("/Example"); + assertThat(sessionRepository.getMaxInactiveIntervalInSeconds()).isEqualTo(MAX_INACTIVE_INTERVAL_IN_SECONDS); + } + + @After + public void tearDown() { + verify(mockAttributesMutator, times(1)).addCacheListener(same(sessionRepository)); + verify(mockRegion, times(1)).getFullPath(); + verify(mockTemplate, times(1)).getRegion(); + } + + @Test + @SuppressWarnings("unchecked") + public void findByPrincipalNameFindsMatchingSessions() throws Exception { + ExpiringSession mockSessionOne = mock(ExpiringSession.class, "MockSessionOne"); + ExpiringSession mockSessionTwo = mock(ExpiringSession.class, "MockSessionTwo"); + ExpiringSession mockSessionThree = mock(ExpiringSession.class, "MockSessionThree"); + + when(mockSessionOne.getId()).thenReturn("1"); + when(mockSessionTwo.getId()).thenReturn("2"); + when(mockSessionThree.getId()).thenReturn("3"); + + SelectResults mockSelectResults = mock(SelectResults.class); + + when(mockSelectResults.asList()).thenReturn(Arrays.asList(mockSessionOne, mockSessionTwo, mockSessionThree)); + + String principalName = "jblum"; + + String expectedOql = String.format(GemFireOperationsSessionRepository.FIND_SESSIONS_BY_PRINCIPAL_NAME_QUERY, + sessionRepository.getFullyQualifiedRegionName()); + + when(mockTemplate.find(eq(expectedOql), eq(principalName))).thenReturn(mockSelectResults); + + Map sessions = sessionRepository.findByPrincipalName(principalName); + + assertThat(sessions).isNotNull(); + assertThat(sessions.size()).isEqualTo(3); + assertThat(sessions.get("1")).isEqualTo(mockSessionOne); + assertThat(sessions.get("2")).isEqualTo(mockSessionTwo); + assertThat(sessions.get("3")).isEqualTo(mockSessionThree); + + verify(mockTemplate, times(1)).find(eq(expectedOql), eq(principalName)); + verify(mockSelectResults, times(1)).asList(); + verify(mockSessionOne, times(1)).getId(); + verify(mockSessionTwo, times(1)).getId(); + verify(mockSessionThree, times(1)).getId(); + } + + @Test + @SuppressWarnings("unchecked") + public void findByPrincipalNameReturnsNoMatchingSessions() { + SelectResults mockSelectResults = mock(SelectResults.class); + + when(mockSelectResults.asList()).thenReturn(Collections.emptyList()); + + String principalName = "jblum"; + + String expectedOql = String.format(GemFireOperationsSessionRepository.FIND_SESSIONS_BY_PRINCIPAL_NAME_QUERY, + sessionRepository.getFullyQualifiedRegionName()); + + when(mockTemplate.find(eq(expectedOql), eq(principalName))).thenReturn(mockSelectResults); + + Map sessions = sessionRepository.findByPrincipalName(principalName); + + assertThat(sessions).isNotNull(); + assertThat(sessions.isEmpty()).isTrue(); + + verify(mockTemplate, times(1)).find(eq(expectedOql), eq(principalName)); + verify(mockSelectResults, times(1)).asList(); + } + + @Test + public void createProperlyInitializedSession() { + final long beforeOrAtCreationTime = System.currentTimeMillis(); + + ExpiringSession session = sessionRepository.createSession(); + + assertThat(session).isInstanceOf(GemFireSession.class); + assertThat(session.getId()).isNotNull(); + assertThat(session.getAttributeNames().isEmpty()).isTrue(); + assertThat(session.getCreationTime()).isGreaterThanOrEqualTo(beforeOrAtCreationTime); + assertThat(session.getLastAccessedTime()).isGreaterThanOrEqualTo(beforeOrAtCreationTime); + assertThat(session.getMaxInactiveIntervalInSeconds()).isEqualTo(MAX_INACTIVE_INTERVAL_IN_SECONDS); + } + + @Test + public void getSessionDeletesMatchingExpiredSessionById() { + final String expectedSessionId = "1"; + + final ExpiringSession mockSession = mock(ExpiringSession.class); + + when(mockSession.isExpired()).thenReturn(true); + when(mockSession.getId()).thenReturn(expectedSessionId); + when(mockTemplate.get(eq(expectedSessionId))).thenReturn(mockSession); + when(mockTemplate.remove(eq(expectedSessionId))).thenReturn(mockSession); + + doAnswer(new Answer() { + public Void answer(final InvocationOnMock invocation) throws Throwable { + ApplicationEvent applicationEvent = invocation.getArgumentAt(0, ApplicationEvent.class); + + assertThat(applicationEvent).isInstanceOf(SessionDeletedEvent.class); + + AbstractSessionEvent sessionEvent = (AbstractSessionEvent) applicationEvent; + + assertThat(sessionEvent.getSource()).isSameAs(sessionRepository); + assertThat(sessionEvent.getSession()).isSameAs(mockSession); + assertThat(sessionEvent.getSessionId()).isEqualTo(expectedSessionId); + + return null; + } + }).when(mockApplicationEventPublisher).publishEvent(any(ApplicationEvent.class)); + + assertThat(sessionRepository.getSession(expectedSessionId)).isNull(); + + verify(mockTemplate, times(1)).get(eq(expectedSessionId)); + verify(mockTemplate, times(1)).remove(eq(expectedSessionId)); + verify(mockSession, times(1)).isExpired(); + verify(mockSession, times(2)).getId(); + verify(mockApplicationEventPublisher, times(1)).publishEvent(isA(SessionDeletedEvent.class)); + } + + @Test + public void getSessionFindsMatchingNonExpiredSessionById() { + final String expectedId = "1"; + + final long expectedCreationTime = System.currentTimeMillis(); + final long currentLastAccessedTime = (expectedCreationTime + TimeUnit.MINUTES.toMillis(5)); + + ExpiringSession mockSession = mock(ExpiringSession.class); + + when(mockSession.isExpired()).thenReturn(false); + when(mockSession.getId()).thenReturn(expectedId); + when(mockSession.getCreationTime()).thenReturn(expectedCreationTime); + when(mockSession.getLastAccessedTime()).thenReturn(currentLastAccessedTime); + when(mockSession.getAttributeNames()).thenReturn(Collections.singleton("attrOne")); + when(mockSession.getAttribute(eq("attrOne"))).thenReturn("test"); + when(mockTemplate.get(eq(expectedId))).thenReturn(mockSession); + + ExpiringSession actualSession = sessionRepository.getSession(expectedId); + + assertThat(actualSession).isNotSameAs(mockSession); + assertThat(actualSession.getId()).isEqualTo(expectedId); + assertThat(actualSession.getCreationTime()).isEqualTo(expectedCreationTime); + assertThat(actualSession.getLastAccessedTime()).isNotEqualTo(currentLastAccessedTime); + assertThat(actualSession.getLastAccessedTime()).isGreaterThanOrEqualTo(expectedCreationTime); + assertThat(actualSession.getAttributeNames()).isEqualTo(Collections.singleton("attrOne")); + assertThat(String.valueOf(actualSession.getAttribute("attrOne"))).isEqualTo("test"); + + verify(mockTemplate, times(1)).get(eq(expectedId)); + verify(mockSession, times(1)).isExpired(); + verify(mockSession, times(1)).getId(); + verify(mockSession, times(1)).getCreationTime(); + verify(mockSession, times(1)).getLastAccessedTime(); + verify(mockSession, times(1)).getAttributeNames(); + verify(mockSession, times(1)).getAttribute(eq("attrOne")); + } + + @Test + public void getSessionReturnsNull() { + when(mockTemplate.get(anyString())).thenReturn(null); + assertThat(sessionRepository.getSession("1")).isNull(); + } + + @Test + public void saveStoresSession() { + final String expectedSessionId = "1"; + + final long expectedCreationTime = System.currentTimeMillis(); + final long expectedLastAccessTime = (expectedCreationTime + TimeUnit.MINUTES.toMillis(5)); + + ExpiringSession mockSession = mock(ExpiringSession.class); + + when(mockSession.getId()).thenReturn(expectedSessionId); + when(mockSession.getCreationTime()).thenReturn(expectedCreationTime); + when(mockSession.getLastAccessedTime()).thenReturn(expectedLastAccessTime); + when(mockSession.getMaxInactiveIntervalInSeconds()).thenReturn(MAX_INACTIVE_INTERVAL_IN_SECONDS); + when(mockSession.getAttributeNames()).thenReturn(Collections.emptySet()); + + when(mockTemplate.put(eq(expectedSessionId), isA(GemFireSession.class))) + .thenAnswer(new Answer() { + public ExpiringSession answer(final InvocationOnMock invocation) throws Throwable { + ExpiringSession session = invocation.getArgumentAt(1, ExpiringSession.class); + + assertThat(session).isNotNull(); + assertThat(session.getId()).isEqualTo(expectedSessionId); + assertThat(session.getCreationTime()).isEqualTo(expectedCreationTime); + assertThat(session.getLastAccessedTime()).isEqualTo(expectedLastAccessTime); + assertThat(session.getMaxInactiveIntervalInSeconds()).isEqualTo(MAX_INACTIVE_INTERVAL_IN_SECONDS); + assertThat(session.getAttributeNames().isEmpty()).isTrue(); + + return null; + } + }); + + sessionRepository.save(mockSession); + + verify(mockSession, times(2)).getId(); + verify(mockSession, times(1)).getCreationTime(); + verify(mockSession, times(1)).getLastAccessedTime(); + verify(mockSession, times(1)).getMaxInactiveIntervalInSeconds(); + verify(mockSession, times(1)).getAttributeNames(); + verify(mockTemplate, times(1)).put(eq(expectedSessionId), isA(GemFireSession.class)); + } + + @Test + public void deleteRemovesExistingSessionAndHandlesDelete() { + final String expectedSessionId = "1"; + + final ExpiringSession mockSession = mock(ExpiringSession.class); + + when(mockSession.getId()).thenReturn(expectedSessionId); + when(mockTemplate.remove(eq(expectedSessionId))).thenReturn(mockSession); + + doAnswer(new Answer() { + public Void answer(final InvocationOnMock invocation) throws Throwable { + ApplicationEvent applicationEvent = invocation.getArgumentAt(0, ApplicationEvent.class); + + assertThat(applicationEvent).isInstanceOf(SessionDeletedEvent.class); + + AbstractSessionEvent sessionEvent = (AbstractSessionEvent) applicationEvent; + + assertThat(sessionEvent.getSource()).isSameAs(sessionRepository); + assertThat(sessionEvent.getSession()).isSameAs(mockSession); + assertThat(sessionEvent.getSessionId()).isEqualTo(expectedSessionId); + + return null; + } + }).when(mockApplicationEventPublisher).publishEvent(isA(SessionDeletedEvent.class)); + + sessionRepository.delete(expectedSessionId); + + verify(mockSession, times(1)).getId(); + verify(mockTemplate, times(1)).remove(eq(expectedSessionId)); + verify(mockApplicationEventPublisher, times(1)).publishEvent(isA(SessionDeletedEvent.class)); + } + + @Test + public void deleteRemovesNonExistingSessionAndHandlesDelete() { + final String expectedSessionId = "1"; + + when(mockTemplate.remove(anyString())).thenReturn(null); + + doAnswer(new Answer() { + public Void answer(final InvocationOnMock invocation) throws Throwable { + ApplicationEvent applicationEvent = invocation.getArgumentAt(0, ApplicationEvent.class); + + assertThat(applicationEvent).isInstanceOf(SessionDeletedEvent.class); + + AbstractSessionEvent sessionEvent = (AbstractSessionEvent) applicationEvent; + + assertThat(sessionEvent.getSource()).isSameAs(sessionRepository); + assertThat(sessionEvent.getSession()).isNull(); + assertThat(sessionEvent.getSessionId()).isEqualTo(expectedSessionId); + + return null; + } + }).when(mockApplicationEventPublisher).publishEvent(isA(SessionDeletedEvent.class)); + + sessionRepository.delete(expectedSessionId); + + verify(mockTemplate, times(1)).remove(eq(expectedSessionId)); + verify(mockApplicationEventPublisher, times(1)).publishEvent(isA(SessionDeletedEvent.class)); + } + + protected abstract class GemfireOperationsAccessor extends GemfireAccessor implements GemfireOperations { + } + +} diff --git a/spring-session/src/test/java/org/springframework/session/data/gemfire/config/annotation/web/http/GemFireHttpSessionConfigurationTest.java b/spring-session/src/test/java/org/springframework/session/data/gemfire/config/annotation/web/http/GemFireHttpSessionConfigurationTest.java new file mode 100644 index 00000000..5b60907c --- /dev/null +++ b/spring-session/src/test/java/org/springframework/session/data/gemfire/config/annotation/web/http/GemFireHttpSessionConfigurationTest.java @@ -0,0 +1,255 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * 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. + */ + +package org.springframework.session.data.gemfire.config.annotation.web.http; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.data.gemfire.GemfireOperations; +import org.springframework.data.gemfire.GemfireTemplate; +import org.springframework.session.data.gemfire.GemFireOperationsSessionRepository; + +import com.gemstone.gemfire.cache.Cache; +import com.gemstone.gemfire.cache.GemFireCache; +import com.gemstone.gemfire.cache.Region; +import com.gemstone.gemfire.cache.RegionShortcut; +import com.gemstone.gemfire.cache.client.ClientCache; +import com.gemstone.gemfire.cache.client.ClientRegionShortcut; + +/** + * The GemFireHttpSessionConfigurationTest class is a test suite of test cases testing the contract and functionality + * of the {@link GemFireHttpSessionConfiguration} class. + * + * @author John Blum + * @see org.junit.Test + * @see org.mockito.Mockito + * @see org.springframework.session.data.gemfire.config.annotation.web.http.GemFireHttpSessionConfiguration + * @see com.gemstone.gemfire.cache.Cache + * @see com.gemstone.gemfire.cache.GemFireCache + * @see com.gemstone.gemfire.cache.Region + * @see com.gemstone.gemfire.cache.client.ClientCache + * @since 1.0.0 + */ +public class GemFireHttpSessionConfigurationTest { + + private GemFireHttpSessionConfiguration gemfireConfiguration; + + @Before + public void setup() { + gemfireConfiguration = new GemFireHttpSessionConfiguration(); + } + + @Test + public void setAndGetBeanClassLoader() { + assertThat(gemfireConfiguration.getBeanClassLoader()).isNull(); + + gemfireConfiguration.setBeanClassLoader(Thread.currentThread().getContextClassLoader()); + + assertThat(gemfireConfiguration.getBeanClassLoader()).isEqualTo(Thread.currentThread().getContextClassLoader()); + + gemfireConfiguration.setBeanClassLoader(null); + + assertThat(gemfireConfiguration.getBeanClassLoader()).isNull(); + } + + @Test + public void setAndGetClientRegionShortcut() { + assertThat(gemfireConfiguration.getClientRegionShortcut()).isEqualTo( + GemFireHttpSessionConfiguration.DEFAULT_CLIENT_REGION_SHORTCUT); + + gemfireConfiguration.setClientRegionShortcut(ClientRegionShortcut.CACHING_PROXY); + + assertThat(gemfireConfiguration.getClientRegionShortcut()).isEqualTo(ClientRegionShortcut.CACHING_PROXY); + + gemfireConfiguration.setClientRegionShortcut(null); + + assertThat(gemfireConfiguration.getClientRegionShortcut()).isEqualTo( + GemFireHttpSessionConfiguration.DEFAULT_CLIENT_REGION_SHORTCUT); + } + + @Test + public void setAndGetMaxInactiveIntervalInSeconds() { + assertThat(gemfireConfiguration.getMaxInactiveIntervalInSeconds()).isEqualTo( + GemFireHttpSessionConfiguration.DEFAULT_MAX_INACTIVE_INTERVAL_IN_SECONDS); + + gemfireConfiguration.setMaxInactiveIntervalInSeconds(300); + + assertThat(gemfireConfiguration.getMaxInactiveIntervalInSeconds()).isEqualTo(300); + + gemfireConfiguration.setMaxInactiveIntervalInSeconds(Integer.MAX_VALUE); + + assertThat(gemfireConfiguration.getMaxInactiveIntervalInSeconds()).isEqualTo(Integer.MAX_VALUE); + + gemfireConfiguration.setMaxInactiveIntervalInSeconds(-1); + + assertThat(gemfireConfiguration.getMaxInactiveIntervalInSeconds()).isEqualTo(-1); + + gemfireConfiguration.setMaxInactiveIntervalInSeconds(Integer.MIN_VALUE); + + assertThat(gemfireConfiguration.getMaxInactiveIntervalInSeconds()).isEqualTo(Integer.MIN_VALUE); + } + + @Test + public void setAndGetServerRegionShortcut() { + assertThat(gemfireConfiguration.getServerRegionShortcut()).isEqualTo( + GemFireHttpSessionConfiguration.DEFAULT_SERVER_REGION_SHORTCUT); + + gemfireConfiguration.setServerRegionShortcut(RegionShortcut.REPLICATE_PERSISTENT); + + assertThat(gemfireConfiguration.getServerRegionShortcut()).isEqualTo(RegionShortcut.REPLICATE_PERSISTENT); + + gemfireConfiguration.setServerRegionShortcut(null); + + assertThat(gemfireConfiguration.getServerRegionShortcut()).isEqualTo( + GemFireHttpSessionConfiguration.DEFAULT_SERVER_REGION_SHORTCUT); + } + + @Test + public void setAndGetSpringSessionGemFireRegionName() { + assertThat(gemfireConfiguration.getSpringSessionGemFireRegionName()).isEqualTo( + GemFireHttpSessionConfiguration.DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME); + + gemfireConfiguration.setSpringSessionGemFireRegionName("test"); + + assertThat(gemfireConfiguration.getSpringSessionGemFireRegionName()).isEqualTo("test"); + + gemfireConfiguration.setSpringSessionGemFireRegionName(" "); + + assertThat(gemfireConfiguration.getSpringSessionGemFireRegionName()).isEqualTo( + GemFireHttpSessionConfiguration.DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME); + + gemfireConfiguration.setSpringSessionGemFireRegionName(""); + + assertThat(gemfireConfiguration.getSpringSessionGemFireRegionName()).isEqualTo( + GemFireHttpSessionConfiguration.DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME); + + gemfireConfiguration.setSpringSessionGemFireRegionName(null); + + assertThat(gemfireConfiguration.getSpringSessionGemFireRegionName()).isEqualTo( + GemFireHttpSessionConfiguration.DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME); + } + + @Test + public void setImportMetadata() { + AnnotationMetadata mockAnnotationMetadata = mock(AnnotationMetadata.class, "testSetImportMetadata"); + + Map annotationAttributes = new HashMap(4); + + annotationAttributes.put("clientRegionShortcut", ClientRegionShortcut.CACHING_PROXY); + annotationAttributes.put("maxInactiveIntervalInSeconds", 600); + annotationAttributes.put("serverRegionShortcut", RegionShortcut.REPLICATE); + annotationAttributes.put("regionName", "TEST"); + + when(mockAnnotationMetadata.getAnnotationAttributes(eq(EnableGemFireHttpSession.class.getName()))) + .thenReturn(annotationAttributes); + + gemfireConfiguration.setImportMetadata(mockAnnotationMetadata); + + assertThat(gemfireConfiguration.getClientRegionShortcut()).isEqualTo(ClientRegionShortcut.CACHING_PROXY); + assertThat(gemfireConfiguration.getMaxInactiveIntervalInSeconds()).isEqualTo(600); + assertThat(gemfireConfiguration.getServerRegionShortcut()).isEqualTo(RegionShortcut.REPLICATE); + assertThat(gemfireConfiguration.getSpringSessionGemFireRegionName()).isEqualTo("TEST"); + + verify(mockAnnotationMetadata, times(1)).getAnnotationAttributes(eq(EnableGemFireHttpSession.class.getName())); + } + + @Test + public void createAndInitializeSpringSessionRepositoryBean() { + GemfireOperations mockGemfireOperations = mock(GemfireOperations.class, + "testCreateAndInitializeSpringSessionRepositoryBean"); + + gemfireConfiguration.setMaxInactiveIntervalInSeconds(120); + + GemFireOperationsSessionRepository sessionRepository = gemfireConfiguration.sessionRepository( + mockGemfireOperations); + + assertThat(sessionRepository).isNotNull(); + assertThat(sessionRepository.getTemplate()).isSameAs(mockGemfireOperations); + assertThat(sessionRepository.getMaxInactiveIntervalInSeconds()).isEqualTo(120); + } + + @Test + @SuppressWarnings("unchecked") + public void createAndInitializeSpringSessionGemFireRegionTemplate() { + GemFireCache mockGemFireCache = mock(GemFireCache.class); + Region mockRegion = mock(Region.class); + + when(mockGemFireCache.getRegion(eq("Example"))).thenReturn(mockRegion); + + gemfireConfiguration.setSpringSessionGemFireRegionName("Example"); + + GemfireTemplate template = gemfireConfiguration.sessionRegionTemplate(mockGemFireCache); + + assertThat(gemfireConfiguration.getSpringSessionGemFireRegionName()).isEqualTo("Example"); + assertThat(template).isNotNull(); + assertThat(template.getRegion()).isSameAs(mockRegion); + + verify(mockGemFireCache, times(1)).getRegion(eq("Example")); + } + + @Test + public void expirationIsAllowed() { + Cache mockCache = mock(Cache.class, "testExpirationIsAllowed.MockCache"); + ClientCache mockClientCache = mock(ClientCache.class, "testExpirationIsAllowed.MockClientCache"); + + gemfireConfiguration.setClientRegionShortcut(ClientRegionShortcut.PROXY); + gemfireConfiguration.setServerRegionShortcut(RegionShortcut.REPLICATE); + + assertThat(gemfireConfiguration.isExpirationAllowed(mockCache)).isTrue(); + + gemfireConfiguration.setServerRegionShortcut(RegionShortcut.PARTITION_REDUNDANT_PERSISTENT_OVERFLOW); + + assertThat(gemfireConfiguration.isExpirationAllowed(mockCache)).isTrue(); + + gemfireConfiguration.setClientRegionShortcut(ClientRegionShortcut.CACHING_PROXY); + gemfireConfiguration.setServerRegionShortcut(RegionShortcut.PARTITION_PROXY); + + assertThat(gemfireConfiguration.isExpirationAllowed(mockClientCache)).isTrue(); + + gemfireConfiguration.setClientRegionShortcut(ClientRegionShortcut.LOCAL_PERSISTENT_OVERFLOW); + gemfireConfiguration.setServerRegionShortcut(RegionShortcut.REPLICATE_PROXY); + + assertThat(gemfireConfiguration.isExpirationAllowed(mockClientCache)).isTrue(); + } + + @Test + public void expirationIsNotAllowed() { + Cache mockCache = mock(Cache.class, "testExpirationIsAllowed.MockCache"); + ClientCache mockClientCache = mock(ClientCache.class, "testExpirationIsAllowed.MockClientCache"); + + gemfireConfiguration.setClientRegionShortcut(ClientRegionShortcut.PROXY); + gemfireConfiguration.setServerRegionShortcut(RegionShortcut.PARTITION); + + assertThat(gemfireConfiguration.isExpirationAllowed(mockClientCache)).isFalse(); + + gemfireConfiguration.setClientRegionShortcut(ClientRegionShortcut.LOCAL); + gemfireConfiguration.setServerRegionShortcut(RegionShortcut.PARTITION_PROXY); + + assertThat(gemfireConfiguration.isExpirationAllowed(mockCache)).isFalse(); + } + +} diff --git a/spring-session/src/test/java/org/springframework/session/data/gemfire/config/annotation/web/http/support/GemFireCacheTypeAwareRegionFactoryBeanTest.java b/spring-session/src/test/java/org/springframework/session/data/gemfire/config/annotation/web/http/support/GemFireCacheTypeAwareRegionFactoryBeanTest.java new file mode 100644 index 00000000..f5091338 --- /dev/null +++ b/spring-session/src/test/java/org/springframework/session/data/gemfire/config/annotation/web/http/support/GemFireCacheTypeAwareRegionFactoryBeanTest.java @@ -0,0 +1,245 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * 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. + */ + +package org.springframework.session.data.gemfire.config.annotation.web.http.support; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.springframework.data.gemfire.client.Interest; + +import com.gemstone.gemfire.cache.Cache; +import com.gemstone.gemfire.cache.GemFireCache; +import com.gemstone.gemfire.cache.InterestResultPolicy; +import com.gemstone.gemfire.cache.Region; +import com.gemstone.gemfire.cache.RegionAttributes; +import com.gemstone.gemfire.cache.RegionShortcut; +import com.gemstone.gemfire.cache.client.ClientCache; +import com.gemstone.gemfire.cache.client.ClientRegionShortcut; + +/** + * The GemFireCacheTypeAwareRegionFactoryBeanTest class is a test suite of test cases testing the contract + * and functionality of the GemFireCacheTypeAwareRegionFactoryBean class. + * + * @author John Blum + * @see org.junit.Rule + * @see org.junit.Test + * @see org.mockito.Mockito + * @see org.springframework.session.data.gemfire.config.annotation.web.http.support.GemFireCacheTypeAwareRegionFactoryBean + * @see com.gemstone.gemfire.cache.Cache + * @see com.gemstone.gemfire.cache.GemFireCache + * @see com.gemstone.gemfire.cache.InterestResultPolicy + * @see com.gemstone.gemfire.cache.Region + * @see com.gemstone.gemfire.cache.RegionAttributes + * @see com.gemstone.gemfire.cache.RegionShortcut + * @see com.gemstone.gemfire.cache.client.ClientCache + * @see com.gemstone.gemfire.cache.client.ClientRegionShortcut + * @since 1.0.0 + */ +public class GemFireCacheTypeAwareRegionFactoryBeanTest { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private GemFireCacheTypeAwareRegionFactoryBean regionFactoryBean; + + @Before + public void setup() { + regionFactoryBean = new GemFireCacheTypeAwareRegionFactoryBean(); + } + + @Test + public void afterPropertiesSetCreatesClientRegionForClientCache() throws Exception { + final ClientCache mockClientCache = mock(ClientCache.class); + + final Region mockClientRegion = mock(Region.class, "MockClientRegion"); + final Region mockServerRegion = mock(Region.class, "MockServerRegion"); + + regionFactoryBean = new GemFireCacheTypeAwareRegionFactoryBean() { + @Override protected Region newClientRegion(GemFireCache gemfireCache) throws Exception { + assertThat(gemfireCache).isSameAs(mockClientCache); + return mockClientRegion; + } + + @Override protected Region newServerRegion(final GemFireCache gemfireCache) throws Exception { + assertThat(gemfireCache).isSameAs(mockClientCache); + return mockServerRegion; + } + }; + + regionFactoryBean.setGemfireCache(mockClientCache); + regionFactoryBean.afterPropertiesSet(); + + assertThat(regionFactoryBean.getGemfireCache()).isSameAs(mockClientCache); + assertThat(regionFactoryBean.getObject()).isEqualTo(mockClientRegion); + } + + @Test + public void afterPropertiesSetCreatesServerRegionForPeerCache() throws Exception { + final Cache mockCache = mock(Cache.class); + + final Region mockClientRegion = mock(Region.class, "MockClientRegion"); + final Region mockServerRegion = mock(Region.class, "MockServerRegion"); + + regionFactoryBean = new GemFireCacheTypeAwareRegionFactoryBean() { + @Override protected Region newClientRegion(GemFireCache gemfireCache) throws Exception { + assertThat(gemfireCache).isSameAs(mockCache); + return mockClientRegion; + } + + @Override protected Region newServerRegion(final GemFireCache gemfireCache) throws Exception { + assertThat(gemfireCache).isSameAs(mockCache); + return mockServerRegion; + } + }; + + regionFactoryBean.setGemfireCache(mockCache); + regionFactoryBean.afterPropertiesSet(); + + assertThat(regionFactoryBean.getGemfireCache()).isSameAs(mockCache); + assertThat(regionFactoryBean.getObject()).isEqualTo(mockServerRegion); + } + + @Test + public void allKeysInterestRegistration() { + Interest[] interests = regionFactoryBean.registerInterests(true); + + assertThat(interests).isNotNull(); + assertThat(interests.length).isEqualTo(1); + assertThat(interests[0].isDurable()).isFalse(); + assertThat(interests[0].getKey().toString()).isEqualTo("ALL_KEYS"); + assertThat(interests[0].getPolicy()).isEqualTo(InterestResultPolicy.KEYS); + assertThat(interests[0].isReceiveValues()).isTrue(); + } + + @Test + public void emptyInterestsRegistration() { + Interest[] interests = regionFactoryBean.registerInterests(false); + + assertThat(interests).isNotNull(); + assertThat(interests.length).isEqualTo(0); + } + + @Test + @SuppressWarnings("unchecked") + public void getObjectTypeBeforeInitializationIsRegionClass() { + assertThat((Class) regionFactoryBean.getObjectType()).isEqualTo(Region.class); + } + + @Test + public void isSingletonIsTrue() { + assertThat(regionFactoryBean.isSingleton()).isTrue(); + } + + @Test + public void setAndGetClientRegionShortcut() { + assertThat(regionFactoryBean.getClientRegionShortcut()).isEqualTo( + GemFireCacheTypeAwareRegionFactoryBean.DEFAULT_CLIENT_REGION_SHORTCUT); + + regionFactoryBean.setClientRegionShortcut(ClientRegionShortcut.LOCAL_PERSISTENT); + + assertThat(regionFactoryBean.getClientRegionShortcut()).isEqualTo(ClientRegionShortcut.LOCAL_PERSISTENT); + + regionFactoryBean.setClientRegionShortcut(null); + + assertThat(regionFactoryBean.getClientRegionShortcut()).isEqualTo( + GemFireCacheTypeAwareRegionFactoryBean.DEFAULT_CLIENT_REGION_SHORTCUT); + } + + @Test + public void setAndGetGemfireCache() { + Cache mockCache = mock(Cache.class); + + regionFactoryBean.setGemfireCache(mockCache); + + assertThat(regionFactoryBean.getGemfireCache()).isEqualTo(mockCache); + } + + @Test + public void setGemfireCacheToNullThrowsIllegalArgumentException() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("The GemFireCache reference must not be null"); + regionFactoryBean.setGemfireCache(null); + } + + @Test + public void getGemfireCacheWhenNullThrowsIllegalStateException() { + expectedException.expect(IllegalStateException.class); + expectedException.expectMessage("A reference to a GemFireCache was not properly configured"); + regionFactoryBean.getGemfireCache(); + } + + @Test + @SuppressWarnings("unchecked") + public void setAndGetRegionAttributes() { + RegionAttributes mockRegionAttributes = mock(RegionAttributes.class); + + assertThat(regionFactoryBean.getRegionAttributes()).isNull(); + + regionFactoryBean.setRegionAttributes(mockRegionAttributes); + + assertThat(regionFactoryBean.getRegionAttributes()).isSameAs(mockRegionAttributes); + + regionFactoryBean.setRegionAttributes(null); + + assertThat(regionFactoryBean.getRegionAttributes()).isNull(); + } + + @Test + public void setAndGetRegionName() { + assertThat(regionFactoryBean.getRegionName()).isEqualTo( + GemFireCacheTypeAwareRegionFactoryBean.DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME); + + regionFactoryBean.setRegionName("Example"); + + assertThat(regionFactoryBean.getRegionName()).isEqualTo("Example"); + + regionFactoryBean.setRegionName(" "); + + assertThat(regionFactoryBean.getRegionName()).isEqualTo( + GemFireCacheTypeAwareRegionFactoryBean.DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME); + + regionFactoryBean.setRegionName(""); + + assertThat(regionFactoryBean.getRegionName()).isEqualTo( + GemFireCacheTypeAwareRegionFactoryBean.DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME); + + regionFactoryBean.setRegionName(null); + + assertThat(regionFactoryBean.getRegionName()).isEqualTo( + GemFireCacheTypeAwareRegionFactoryBean.DEFAULT_SPRING_SESSION_GEMFIRE_REGION_NAME); + } + + @Test + public void setAndGetServerRegionShortcut() { + assertThat(regionFactoryBean.getServerRegionShortcut()).isEqualTo( + GemFireCacheTypeAwareRegionFactoryBean.DEFAULT_SERVER_REGION_SHORTCUT); + + regionFactoryBean.setServerRegionShortcut(RegionShortcut.LOCAL_PERSISTENT); + + assertThat(regionFactoryBean.getServerRegionShortcut()).isEqualTo(RegionShortcut.LOCAL_PERSISTENT); + + regionFactoryBean.setServerRegionShortcut(null); + + assertThat(regionFactoryBean.getServerRegionShortcut()).isEqualTo( + GemFireCacheTypeAwareRegionFactoryBean.DEFAULT_SERVER_REGION_SHORTCUT); + } + +} diff --git a/spring-session/src/test/java/org/springframework/session/data/gemfire/support/GemFireUtilsTest.java b/spring-session/src/test/java/org/springframework/session/data/gemfire/support/GemFireUtilsTest.java new file mode 100644 index 00000000..4615c00e --- /dev/null +++ b/spring-session/src/test/java/org/springframework/session/data/gemfire/support/GemFireUtilsTest.java @@ -0,0 +1,172 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * 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. + */ + +package org.springframework.session.data.gemfire.support; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import java.io.Closeable; +import java.io.IOException; + +import org.junit.Test; + +import com.gemstone.gemfire.cache.Cache; +import com.gemstone.gemfire.cache.GemFireCache; +import com.gemstone.gemfire.cache.RegionShortcut; +import com.gemstone.gemfire.cache.client.ClientCache; +import com.gemstone.gemfire.cache.client.ClientRegionShortcut; + +/** + * The GemFireUtilsTest class is a test suite of test cases testing the contract and functionality of the GemFireUtils + * utility class. + * + * @author John Blum + * @see org.junit.Test + * @see org.mockito.Mockito + * @see org.springframework.session.data.gemfire.support.GemFireUtils + * @since 1.0.0 + */ +public class GemFireUtilsTest { + + @Test + public void closeNonNullCloseableSuccessfullyReturnsTrue() throws IOException { + Closeable mockCloseable = mock(Closeable.class); + assertThat(GemFireUtils.close(mockCloseable)).isTrue(); + verify(mockCloseable, times(1)).close(); + } + + @Test + public void closeNonNullCloseableObjectThrowingIOExceptionReturnsFalse() throws IOException { + Closeable mockCloseable = mock(Closeable.class); + doThrow(new IOException("test")).when(mockCloseable).close(); + assertThat(GemFireUtils.close(mockCloseable)).isFalse(); + verify(mockCloseable, times(1)).close(); + } + + @Test + public void closeNullCloseableObjectReturnsFalse() { + assertThat(GemFireUtils.close(null)).isFalse(); + } + + @Test + public void clientCacheIsClient() { + assertThat(GemFireUtils.isClient(mock(ClientCache.class))).isTrue(); + } + + @Test + public void genericCacheIsNotClient() { + assertThat(GemFireUtils.isClient(mock(GemFireCache.class))).isFalse(); + } + + @Test + public void peerCacheIsNotClient() { + assertThat(GemFireUtils.isClient(mock(Cache.class))).isFalse(); + } + + @Test + public void peerCacheIsPeer() { + assertThat(GemFireUtils.isPeer(mock(Cache.class))).isTrue(); + } + + @Test + public void genericCacheIsNotPeer() { + assertThat(GemFireUtils.isPeer(mock(GemFireCache.class))).isFalse(); + } + + @Test + public void clientCacheIsNotPeer() { + assertThat(GemFireUtils.isPeer(mock(ClientCache.class))).isFalse(); + } + + @Test + public void clientRegionShortcutIsLocal() { + assertThat(GemFireUtils.isLocal(ClientRegionShortcut.LOCAL)).isTrue(); + assertThat(GemFireUtils.isLocal(ClientRegionShortcut.LOCAL_HEAP_LRU)).isTrue(); + assertThat(GemFireUtils.isLocal(ClientRegionShortcut.LOCAL_OVERFLOW)).isTrue(); + assertThat(GemFireUtils.isLocal(ClientRegionShortcut.LOCAL_PERSISTENT)).isTrue(); + assertThat(GemFireUtils.isLocal(ClientRegionShortcut.LOCAL_PERSISTENT_OVERFLOW)).isTrue(); + } + + @Test + public void clientRegionShortcutIsNotLocal() { + assertThat(GemFireUtils.isLocal(ClientRegionShortcut.CACHING_PROXY)).isFalse(); + assertThat(GemFireUtils.isLocal(ClientRegionShortcut.CACHING_PROXY_HEAP_LRU)).isFalse(); + assertThat(GemFireUtils.isLocal(ClientRegionShortcut.CACHING_PROXY_OVERFLOW)).isFalse(); + assertThat(GemFireUtils.isLocal(ClientRegionShortcut.PROXY)).isFalse(); + } + + @Test + public void clientRegionShortcutIsProxy() { + assertThat(GemFireUtils.isProxy(ClientRegionShortcut.PROXY)).isTrue(); + } + + @Test + public void clientRegionShortcutIsNotProxy() { + assertThat(GemFireUtils.isProxy(ClientRegionShortcut.CACHING_PROXY)).isFalse(); + assertThat(GemFireUtils.isProxy(ClientRegionShortcut.CACHING_PROXY_HEAP_LRU)).isFalse(); + assertThat(GemFireUtils.isProxy(ClientRegionShortcut.CACHING_PROXY_OVERFLOW)).isFalse(); + assertThat(GemFireUtils.isProxy(ClientRegionShortcut.LOCAL)).isFalse(); + assertThat(GemFireUtils.isProxy(ClientRegionShortcut.LOCAL_HEAP_LRU)).isFalse(); + assertThat(GemFireUtils.isProxy(ClientRegionShortcut.LOCAL_OVERFLOW)).isFalse(); + assertThat(GemFireUtils.isProxy(ClientRegionShortcut.LOCAL_PERSISTENT)).isFalse(); + assertThat(GemFireUtils.isProxy(ClientRegionShortcut.LOCAL_PERSISTENT_OVERFLOW)).isFalse(); + } + + @Test + public void regionShortcutIsProxy() { + assertThat(GemFireUtils.isProxy(RegionShortcut.PARTITION_PROXY)).isTrue(); + assertThat(GemFireUtils.isProxy(RegionShortcut.PARTITION_PROXY_REDUNDANT)).isTrue(); + assertThat(GemFireUtils.isProxy(RegionShortcut.REPLICATE_PROXY)).isTrue(); + } + + @Test + public void regionShortcutIsNotProxy() { + assertThat(GemFireUtils.isProxy(RegionShortcut.LOCAL)).isFalse(); + assertThat(GemFireUtils.isProxy(RegionShortcut.LOCAL_HEAP_LRU)).isFalse(); + assertThat(GemFireUtils.isProxy(RegionShortcut.LOCAL_OVERFLOW)).isFalse(); + assertThat(GemFireUtils.isProxy(RegionShortcut.LOCAL_PERSISTENT)).isFalse(); + assertThat(GemFireUtils.isProxy(RegionShortcut.LOCAL_PERSISTENT_OVERFLOW)).isFalse(); + assertThat(GemFireUtils.isProxy(RegionShortcut.REPLICATE)).isFalse(); + assertThat(GemFireUtils.isProxy(RegionShortcut.REPLICATE_HEAP_LRU)).isFalse(); + assertThat(GemFireUtils.isProxy(RegionShortcut.REPLICATE_OVERFLOW)).isFalse(); + assertThat(GemFireUtils.isProxy(RegionShortcut.REPLICATE_PERSISTENT)).isFalse(); + assertThat(GemFireUtils.isProxy(RegionShortcut.REPLICATE_PERSISTENT_OVERFLOW)).isFalse(); + assertThat(GemFireUtils.isProxy(RegionShortcut.PARTITION)).isFalse(); + assertThat(GemFireUtils.isProxy(RegionShortcut.PARTITION_HEAP_LRU)).isFalse(); + assertThat(GemFireUtils.isProxy(RegionShortcut.PARTITION_OVERFLOW)).isFalse(); + assertThat(GemFireUtils.isProxy(RegionShortcut.PARTITION_PERSISTENT)).isFalse(); + assertThat(GemFireUtils.isProxy(RegionShortcut.PARTITION_PERSISTENT_OVERFLOW)).isFalse(); + assertThat(GemFireUtils.isProxy(RegionShortcut.PARTITION_REDUNDANT)).isFalse(); + assertThat(GemFireUtils.isProxy(RegionShortcut.PARTITION_REDUNDANT_HEAP_LRU)).isFalse(); + assertThat(GemFireUtils.isProxy(RegionShortcut.PARTITION_REDUNDANT_OVERFLOW)).isFalse(); + assertThat(GemFireUtils.isProxy(RegionShortcut.PARTITION_REDUNDANT_PERSISTENT)).isFalse(); + assertThat(GemFireUtils.isProxy(RegionShortcut.PARTITION_REDUNDANT_PERSISTENT_OVERFLOW)).isFalse(); + } + + @Test + public void toRegionPath() { + assertThat(GemFireUtils.toRegionPath("A")).isEqualTo("/A"); + assertThat(GemFireUtils.toRegionPath("Example")).isEqualTo("/Example"); + assertThat(GemFireUtils.toRegionPath("/Example")).isEqualTo("//Example"); + assertThat(GemFireUtils.toRegionPath("/")).isEqualTo("//"); + assertThat(GemFireUtils.toRegionPath("")).isEqualTo("/"); + } + +} diff --git a/spring-session/src/test/resources/org/springframework/session/data/gemfire/config/annotation/web/http/GemFireHttpSessionConfigurationXmlTests-context.xml b/spring-session/src/test/resources/org/springframework/session/data/gemfire/config/annotation/web/http/GemFireHttpSessionConfigurationXmlTests-context.xml new file mode 100644 index 00000000..7602d2df --- /dev/null +++ b/spring-session/src/test/resources/org/springframework/session/data/gemfire/config/annotation/web/http/GemFireHttpSessionConfigurationXmlTests-context.xml @@ -0,0 +1,25 @@ + + + + + GemFireHttpSessionConfigurationXmlTests + 0 + warning + + + + + + +