diff --git a/.gitignore b/.gitignore index e4a10566a..1f2ba65b5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,17 +1,14 @@ -# Eclipse project files -.classpath -.project -.settings/ - - -# Intellij project files -*.iml -.idea/ - -# Others -target/ -logs/ -/examples/showcase/bin/yuicompressor-2.4.*.jar -/support/jmeter/*.jtl -/support/jmeter/*.log -/support/local-script/ +# Eclipse project files +.classpath +.project +.settings/ + + +# Intellij project files +*.iml +.idea/ + +# Others +target/ +logs/ +modules/utils副本 diff --git a/.travis.yml b/.travis.yml index 9a68a274f..3ae97daed 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,13 @@ language: java +jdk: + - oraclejdk8 + - openjdk7 +install: true script: -- mvn test -- mvn test -Pfunctional-test -Dselenium.driver=firefox -env: MAVEN_OPTS="-XX:MaxPermSize=128m" -addons: - firefox: "22.0" -before_install: - - "export DISPLAY=:99.0" - - "sh -e /etc/init.d/xvfb start" + - "cd modules;mvn clean test" + - "mvn clean test" + - "mvn clean package -Dmaven.test.skip=true" + +branches: + only: + - master diff --git a/README.md b/README.md new file mode 100644 index 000000000..a7df6c24c --- /dev/null +++ b/README.md @@ -0,0 +1,31 @@ +### 最新开源: [VJTools-唯品会Java核心项目](https://github.com/vipshop/vjtools) + +# SpringSide + +[![Build Status](https://api.travis-ci.org/springside/springside4.png?branch=master)](https://travis-ci.org/springside/springside4/) + +SpringSide是以Spring Framework为核心的,Pragmatic风格的JavaEE应用参考示例,是JavaEE世界中的主流技术选型,最佳实践的总结与演示。 + + 1. Utils - 性能与易用性兼顾的Java基础库,综合各门各派的大成之作(近期重点). + 2. BootApi - 基于Spring Boot的Web Service应用, 可以用于SOA服务,或Ajax页面的后台. + 3. BootWeb - 基于Spring Boot的Web应用, 典型的增删改查管理(未开始). + 4. Showcase - 更多的示例. + + +## 主要用例 + +全部示例以一个P2P图书馆展开,P2P图书馆避免了中央式图书馆所需的场地和图书管理员,大家把图书登记在应用里互相借阅。 + +## 快速开始 (JDK7.0+) + +1. 运行根目录下的quick-start.sh 或 quick-start.bat + * 将modules安装到本地maven仓库 + * 以开发模式启动BootApi应用 + +2. 访问 http://localhost:8080/,按上面的提示体验。 + + +------------------------------- +Offical Site: http://springside.io(域名过期) + +Document: https://github.com/springside/springside4/wiki diff --git a/README.txt b/README.txt deleted file mode 100644 index 8d8eba085..000000000 --- a/README.txt +++ /dev/null @@ -1,12 +0,0 @@ - SpringSide is a Spring Framework based JavaEE application reference architecture. - - It shows the mainstream technologies and pragmatic practice in JavaEE world. - - It has 2 major examples: - - 1. Quickstart -- a minimal CRUD Todo-List web application. - 2. Showcase -- demonstrate many other interesting technologies. - -------------------------------- -Offical Site: http://www.springside.org.cn -Document: https://github.com/springside/springside4/wiki \ No newline at end of file diff --git a/examples/boot-api/pom.xml b/examples/boot-api/pom.xml new file mode 100644 index 000000000..fba3fea7f --- /dev/null +++ b/examples/boot-api/pom.xml @@ -0,0 +1,136 @@ + + + 4.0.0 + + io.springside.examples + boot-api + 5.0.0-SNAPSHOT + war + Springside :: Examples :: SpringBoot WebService + + + org.springframework.boot + spring-boot-starter-parent + 1.4.3.RELEASE + + + + 5.0.0-SNAPSHOT + 3.5 + 20.0 + 1.5.0 + + 1.7 + + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-actuator + + + + + + io.springside + springside-utils + ${springside.version} + + + + io.springside + springside-core + ${springside.version} + + + + + com.h2database + h2 + + + + org.flywaydb + flyway-core + + + + + org.apache.commons + commons-lang3 + ${commons-lang3.version} + + + com.google.guava + guava + ${guava.version} + + + ma.glasnost.orika + orika-core + ${orika.version} + + + commons-codec + commons-codec + + + + + org.jolokia + jolokia-core + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.assertj + assertj-core + test + + + + io.springside + springside-utils + ${springside.version} + tests + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.flywaydb + flyway-maven-plugin + ${flyway.version} + + org.h2.Driver + jdbc:h2:file:~/.h2/bootapi;AUTO_SERVER=TRUE;DB_CLOSE_DELAY=-1; + sa + + + + + + diff --git a/examples/boot-api/refresh-db.sh b/examples/boot-api/refresh-db.sh new file mode 100755 index 000000000..d6de8b1ea --- /dev/null +++ b/examples/boot-api/refresh-db.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +echo "cleanup the production/qa db,just for demo" + +mvn flyway:clean \ No newline at end of file diff --git a/examples/boot-api/src/main/java/org/springside/examples/bootapi/BootApiApplication.java b/examples/boot-api/src/main/java/org/springside/examples/bootapi/BootApiApplication.java new file mode 100644 index 000000000..d2c121246 --- /dev/null +++ b/examples/boot-api/src/main/java/org/springside/examples/bootapi/BootApiApplication.java @@ -0,0 +1,23 @@ +package org.springside.examples.bootapi; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +// SpringBoot 应用标识 +@SpringBootApplication +public class BootApiApplication { + + /** + * 启动嵌入式的Tomcat并初始化Spring环境. + * + * 无 applicationContext.xml 和 web.xml, 靠下述方式进行配置: + * + * 1. 扫描当前package下的class设置自动注入的Bean
+ * 2. 也支持用@Bean标注的类配置Bean
+ * 3. 根据classpath中的三方包Class及集中的application.properties条件配置三方包,如线程池
+ * 4. 也支持用@Configuration标注的类配置三方包. + */ + public static void main(String[] args) throws Exception { + SpringApplication.run(BootApiApplication.class, args); + } +} diff --git a/examples/boot-api/src/main/java/org/springside/examples/bootapi/api/AccountEndPoint.java b/examples/boot-api/src/main/java/org/springside/examples/bootapi/api/AccountEndPoint.java new file mode 100644 index 000000000..f7726663e --- /dev/null +++ b/examples/boot-api/src/main/java/org/springside/examples/bootapi/api/AccountEndPoint.java @@ -0,0 +1,54 @@ +package org.springside.examples.bootapi.api; + +import java.util.Collections; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springside.examples.bootapi.service.AccountService; +import org.springside.examples.bootapi.service.exception.ErrorCode; +import org.springside.examples.bootapi.service.exception.ServiceException; +import org.springside.modules.web.MediaTypes; + +// Spring Restful MVC Controller的标识, 直接输出内容,不调用template引擎. +@RestController +public class AccountEndPoint { + + private static Logger logger = LoggerFactory.getLogger(AccountEndPoint.class); + + @Autowired + private AccountService accountService; + + @RequestMapping(value = "/api/accounts/login", produces = MediaTypes.JSON_UTF_8) + public Map login(@RequestParam("email") String email, @RequestParam("password") String password) { + + if (StringUtils.isEmpty(email) || StringUtils.isEmpty(password)) { + throw new ServiceException("User or password empty", ErrorCode.BAD_REQUEST); + } + + String token = accountService.login(email, password); + + return Collections.singletonMap("token", token); + } + + @RequestMapping(value = "/api/accounts/logout") + public void logout(@RequestParam(value = "token", required = false) String token) { + accountService.logout(token); + } + + @RequestMapping(value = "/api/accounts/register") + public void register(@RequestParam("email") String email, + @RequestParam(value = "name", required = false) String name, @RequestParam("password") String password) { + + if (StringUtils.isEmpty(email) || StringUtils.isEmpty(name) || StringUtils.isEmpty(password)) { + throw new ServiceException("User or name or password empty", ErrorCode.BAD_REQUEST); + } + + accountService.register(email, name, password); + } +} diff --git a/examples/boot-api/src/main/java/org/springside/examples/bootapi/api/BookEndpoint.java b/examples/boot-api/src/main/java/org/springside/examples/bootapi/api/BookEndpoint.java new file mode 100644 index 000000000..af3ad8d6b --- /dev/null +++ b/examples/boot-api/src/main/java/org/springside/examples/bootapi/api/BookEndpoint.java @@ -0,0 +1,147 @@ +package org.springside.examples.bootapi.api; + +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Pageable; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springside.examples.bootapi.domain.Account; +import org.springside.examples.bootapi.domain.Book; +import org.springside.examples.bootapi.dto.BookDto; +import org.springside.examples.bootapi.service.AccountService; +import org.springside.examples.bootapi.service.BookAdminService; +import org.springside.examples.bootapi.service.BookBorrowService; +import org.springside.examples.bootapi.service.exception.ErrorCode; +import org.springside.examples.bootapi.service.exception.ServiceException; +import org.springside.modules.utils.mapper.BeanMapper; +import org.springside.modules.web.MediaTypes; + +// Spring Restful MVC Controller的标识, 直接输出内容,不调用template引擎. +@RestController +public class BookEndpoint { + + private static Logger logger = LoggerFactory.getLogger(BookEndpoint.class); + + @Autowired + private AccountService accountService; + + @Autowired + private BookAdminService adminService; + + @Autowired + private BookBorrowService borrowService; + + @RequestMapping(value = "/api/books", produces = MediaTypes.JSON_UTF_8) + public List listAllBook(Pageable pageable) { + Iterable books = adminService.findAll(pageable); + + return BeanMapper.mapList(books, BookDto.class); + } + + @RequestMapping(value = "/api/books/{id}", produces = MediaTypes.JSON_UTF_8) + public BookDto listOneBook(@PathVariable("id") Long id) { + Book book = adminService.findOne(id); + + return BeanMapper.map(book, BookDto.class); + } + + @RequestMapping(value = "/api/books", method = RequestMethod.POST, consumes = MediaTypes.JSON_UTF_8) + public void createBook(@RequestBody BookDto bookDto, + @RequestParam(value = "token", required = false) String token) { + checkToken(token); + // 使用Header中的Token,查找登录用户 + Account currentUser = accountService.getLoginUser(token); + + // 使用BeanMapper, 将与外部交互的BookDto对象复制为应用内部的Book对象 + Book book = BeanMapper.map(bookDto, Book.class); + + // 保存Book对象 + adminService.saveBook(book, currentUser); + } + + @RequestMapping(value = "/api/books/{id}/modify", method = RequestMethod.POST, consumes = MediaTypes.JSON_UTF_8) + public void modifyBook(@RequestBody BookDto bookDto, + @RequestParam(value = "token", required = false) String token) { + checkToken(token); + Account currentUser = accountService.getLoginUser(token); + Book book = BeanMapper.map(bookDto, Book.class); + adminService.modifyBook(book, currentUser.id); + } + + @RequestMapping(value = "/api/books/{id}/delete") + public void deleteBook(@PathVariable("id") Long id, @RequestParam(value = "token", required = false) String token) { + checkToken(token); + Account currentUser = accountService.getLoginUser(token); + adminService.deleteBook(id, currentUser.id); + } + + @RequestMapping(value = "/api/books/{id}/request") + public void applyBorrowRequest(@PathVariable("id") Long id, + @RequestParam(value = "token", required = false) String token) { + checkToken(token); + Account currentUser = accountService.getLoginUser(token); + borrowService.applyBorrowRequest(id, currentUser); + } + + @RequestMapping(value = "/api/books/{id}/cancel") + public void cancelBorrowRequest(@PathVariable("id") Long id, + @RequestParam(value = "token", required = false) String token) { + checkToken(token); + Account currentUser = accountService.getLoginUser(token); + borrowService.cancelBorrowRequest(id, currentUser); + } + + @RequestMapping(value = "/api/books/{id}/confirm") + public void markBookBorrowed(@PathVariable("id") Long id, + @RequestParam(value = "token", required = false) String token) { + checkToken(token); + Account currentUser = accountService.getLoginUser(token); + borrowService.markBookBorrowed(id, currentUser); + } + + @RequestMapping(value = "/api/books/{id}/reject") + public void rejectBorrowRequest(@PathVariable("id") Long id, + @RequestParam(value = "token", required = false) String token) { + checkToken(token); + Account currentUser = accountService.getLoginUser(token); + borrowService.rejectBorrowRequest(id, currentUser); + } + + @RequestMapping(value = "/api/books/{id}/return") + public void markBookReturned(@PathVariable("id") Long id, + @RequestParam(value = "token", required = false) String token) { + checkToken(token); + Account currentUser = accountService.getLoginUser(token); + borrowService.markBookReturned(id, currentUser); + } + + @RequestMapping(value = "/api/mybook", produces = MediaTypes.JSON_UTF_8) + public List listMyBook(@RequestParam(value = "token", required = false) String token, Pageable pageable) { + checkToken(token); + Account currentUser = accountService.getLoginUser(token); + List books = adminService.listMyBook(currentUser.id, pageable); + return BeanMapper.mapList(books, BookDto.class); + } + + @RequestMapping(value = "/api/myborrowedbook", produces = MediaTypes.JSON_UTF_8) + public List listMyBorrowedBook(@RequestParam(value = "token", required = false) String token, + Pageable pageable) { + checkToken(token); + Account currentUser = accountService.getLoginUser(token); + List books = borrowService.listMyBorrowedBook(currentUser.id, pageable); + return BeanMapper.mapList(books, BookDto.class); + } + + private void checkToken(String token) { + if (token == null) { + throw new ServiceException("No token in request", ErrorCode.NO_TOKEN); + } + } +} diff --git a/examples/boot-api/src/main/java/org/springside/examples/bootapi/api/support/CustomExceptionHandler.java b/examples/boot-api/src/main/java/org/springside/examples/bootapi/api/support/CustomExceptionHandler.java new file mode 100644 index 000000000..cdddf8b06 --- /dev/null +++ b/examples/boot-api/src/main/java/org/springside/examples/bootapi/api/support/CustomExceptionHandler.java @@ -0,0 +1,84 @@ +package org.springside.examples.bootapi.api.support; + +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.context.request.WebRequest; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; +import org.springside.examples.bootapi.service.exception.ServiceException; +import org.springside.modules.utils.mapper.JsonMapper; +import org.springside.modules.web.MediaTypes; + +import com.google.common.collect.Maps; + +@ControllerAdvice(annotations = { RestController.class }) +public class CustomExceptionHandler extends ResponseEntityExceptionHandler { + + private Logger logger = LoggerFactory.getLogger(CustomExceptionHandler.class); + + private JsonMapper jsonMapper = new JsonMapper(); + + @ExceptionHandler(value = { ServiceException.class }) + public final ResponseEntity handleServiceException(ServiceException ex, HttpServletRequest request) { + // 注入servletRequest,用于出错时打印请求URL与来源地址 + logError(ex, request); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.parseMediaType(MediaTypes.JSON_UTF_8)); + ErrorResult result = new ErrorResult(ex.errorCode.code, ex.getMessage()); + return new ResponseEntity(result, headers, HttpStatus.valueOf(ex.errorCode.httpStatus)); + } + + @ExceptionHandler(value = { Exception.class }) + public final ResponseEntity handleGeneralException(Exception ex, HttpServletRequest request) { + logError(ex, request); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.parseMediaType(MediaTypes.JSON_UTF_8)); + ErrorResult result = new ErrorResult(HttpStatus.INTERNAL_SERVER_ERROR.value(), + HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase()); + return new ResponseEntity(result, headers, HttpStatus.INTERNAL_SERVER_ERROR); + } + + /** + * 重载ResponseEntityExceptionHandler的方法,加入日志 + */ + @Override + protected ResponseEntity handleExceptionInternal(Exception ex, Object body, HttpHeaders headers, + HttpStatus status, WebRequest request) { + + logError(ex); + + if (HttpStatus.INTERNAL_SERVER_ERROR.equals(status)) { + request.setAttribute("javax.servlet.error.exception", ex, WebRequest.SCOPE_REQUEST); + } + + return new ResponseEntity(body, headers, status); + } + + public void logError(Exception ex) { + Map map = Maps.newHashMap(); + map.put("message", ex.getMessage()); + logger.error(jsonMapper.toJson(map), ex); + } + + public void logError(Exception ex, HttpServletRequest request) { + Map map = Maps.newHashMap(); + map.put("message", ex.getMessage()); + map.put("from", request.getRemoteAddr()); + String queryString = request.getQueryString(); + map.put("path", queryString != null ? (request.getRequestURI() + "?" + queryString) : request.getRequestURI()); + + logger.error(jsonMapper.toJson(map), ex); + } +} diff --git a/examples/boot-api/src/main/java/org/springside/examples/bootapi/api/support/ErrorPageController.java b/examples/boot-api/src/main/java/org/springside/examples/bootapi/api/support/ErrorPageController.java new file mode 100644 index 000000000..971845518 --- /dev/null +++ b/examples/boot-api/src/main/java/org/springside/examples/bootapi/api/support/ErrorPageController.java @@ -0,0 +1,66 @@ +package org.springside.examples.bootapi.api.support; + +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.web.DefaultErrorAttributes; +import org.springframework.boot.autoconfigure.web.ErrorAttributes; +import org.springframework.boot.autoconfigure.web.ErrorController; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.ServletRequestAttributes; +import org.springside.modules.utils.mapper.JsonMapper; +import org.springside.modules.web.MediaTypes; + +/** + * 重载替换Spring Boot默认的BasicErrorController, 增加日志并让错误返回方式统一. + * + * @author calvin + */ +@Controller +public class ErrorPageController implements ErrorController { + + private static Logger logger = LoggerFactory.getLogger(ErrorPageController.class); + + @Value("${error.path:/error}") + private String errorPath; + + private JsonMapper jsonMapper = new JsonMapper(); + + private ErrorAttributes errorAttributes = new DefaultErrorAttributes(); + + @RequestMapping(value = "${error.path:/error}", produces = MediaTypes.JSON_UTF_8) + @ResponseBody + public ErrorResult handle(HttpServletRequest request) { + Map attributes = getErrorAttributes(request); + + ErrorResult result = new ErrorResult(); + result.code = (int) attributes.get("status"); + result.message = (String) attributes.get("error"); + + logError(attributes, request); + + return result; + } + + private Map getErrorAttributes(HttpServletRequest request) { + RequestAttributes requestAttributes = new ServletRequestAttributes(request); + return this.errorAttributes.getErrorAttributes(requestAttributes, false); + } + + private void logError(Map attributes, HttpServletRequest request) { + attributes.put("from", request.getRemoteAddr()); + logger.error(jsonMapper.toJson(attributes)); + } + + @Override + public String getErrorPath() { + return this.errorPath; + } +} diff --git a/examples/boot-api/src/main/java/org/springside/examples/bootapi/api/support/ErrorResult.java b/examples/boot-api/src/main/java/org/springside/examples/bootapi/api/support/ErrorResult.java new file mode 100644 index 000000000..3d00077ff --- /dev/null +++ b/examples/boot-api/src/main/java/org/springside/examples/bootapi/api/support/ErrorResult.java @@ -0,0 +1,15 @@ +package org.springside.examples.bootapi.api.support; + +public class ErrorResult { + + public int code; + public String message; + + public ErrorResult() { + } + + public ErrorResult(int code, String message) { + this.code = code; + this.message = message; + } +} \ No newline at end of file diff --git a/examples/boot-api/src/main/java/org/springside/examples/bootapi/config/H2ConsoleConfiguration.java b/examples/boot-api/src/main/java/org/springside/examples/bootapi/config/H2ConsoleConfiguration.java new file mode 100644 index 000000000..120d89d6f --- /dev/null +++ b/examples/boot-api/src/main/java/org/springside/examples/bootapi/config/H2ConsoleConfiguration.java @@ -0,0 +1,32 @@ +package org.springside.examples.bootapi.config; + +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletRegistration; + +import org.springframework.boot.context.embedded.ServletContextInitializer; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; + +/** + * 在非生产环境里,初始化H2Console管理嵌入式H2. + * + * @author calvin + */ +@Configuration +@Profile(Profiles.NOT_PRODUCTION) +public class H2ConsoleConfiguration implements ServletContextInitializer { + + @Override + public void onStartup(ServletContext servletContext) throws ServletException { + initH2Console(servletContext); + } + + private void initH2Console(ServletContext servletContext) { + ServletRegistration.Dynamic h2ConsoleServlet = servletContext.addServlet("H2Console", + new org.h2.server.web.WebServlet()); + h2ConsoleServlet.addMapping("/h2/*"); + h2ConsoleServlet.setInitParameter("-properties", "src/main/resources"); + h2ConsoleServlet.setLoadOnStartup(1); + } +} diff --git a/examples/boot-api/src/main/java/org/springside/examples/bootapi/config/Profiles.java b/examples/boot-api/src/main/java/org/springside/examples/bootapi/config/Profiles.java new file mode 100644 index 000000000..d45fa0a25 --- /dev/null +++ b/examples/boot-api/src/main/java/org/springside/examples/bootapi/config/Profiles.java @@ -0,0 +1,15 @@ +package org.springside.examples.bootapi.config; + +/** + * 定义项目中使用的Profiles + * + * @author calvin + */ +public class Profiles { + + public static final String PRODUCTION = "prod"; + public static final String NOT_PRODUCTION = "!prod"; + + public static final String QA = "qa"; + public static final String DEVELOPMENT = "dev"; +} diff --git a/examples/boot-api/src/main/java/org/springside/examples/bootapi/domain/Account.java b/examples/boot-api/src/main/java/org/springside/examples/bootapi/domain/Account.java new file mode 100644 index 000000000..b8e8d72fb --- /dev/null +++ b/examples/boot-api/src/main/java/org/springside/examples/bootapi/domain/Account.java @@ -0,0 +1,35 @@ +package org.springside.examples.bootapi.domain; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +import org.apache.commons.lang3.builder.ToStringBuilder; + +// JPA实体类的标识 +@Entity +public class Account { + + // JPA 主键标识, 策略为由数据库生成主键 + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + public Long id; + + public String email; + public String name; + public String hashPassword; + + public Account() { + + } + + public Account(Long id) { + this.id = id; + } + + @Override + public String toString() { + return ToStringBuilder.reflectionToString(this); + } +} diff --git a/examples/boot-api/src/main/java/org/springside/examples/bootapi/domain/Book.java b/examples/boot-api/src/main/java/org/springside/examples/bootapi/domain/Book.java new file mode 100644 index 000000000..18d5a10ae --- /dev/null +++ b/examples/boot-api/src/main/java/org/springside/examples/bootapi/domain/Book.java @@ -0,0 +1,58 @@ +package org.springside.examples.bootapi.domain; + +import java.util.Date; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; + +import org.apache.commons.lang3.builder.ToStringBuilder; + +// JPA实体类的标识 +@Entity +public class Book { + + public static final String STATUS_IDLE = "idle"; + public static final String STATUS_REQUEST = "request"; + public static final String STATUS_OUT = "out"; + + // JPA 主键标识, 策略为由数据库生成主键 + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + public Long id; + + public String doubanId; + + public String title; + + public String url; + + public String status; + + @ManyToOne + @JoinColumn(name = "owner_id") + public Account owner; + + public Date onboardDate; + + @ManyToOne + @JoinColumn(name = "borrower_id") + public Account borrower; + + public Date borrowDate; + + public Book() { + } + + public Book(Long id) { + this.id = id; + } + + @Override + public String toString() { + return ToStringBuilder.reflectionToString(this); + } +} diff --git a/examples/boot-api/src/main/java/org/springside/examples/bootapi/domain/Message.java b/examples/boot-api/src/main/java/org/springside/examples/bootapi/domain/Message.java new file mode 100644 index 000000000..0b83a1698 --- /dev/null +++ b/examples/boot-api/src/main/java/org/springside/examples/bootapi/domain/Message.java @@ -0,0 +1,45 @@ +package org.springside.examples.bootapi.domain; + +import java.util.Date; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; + +import org.apache.commons.lang3.builder.ToStringBuilder; + +// JPA实体类的标识 +@Entity +public class Message { + + // JPA 主键标识, 策略为由数据库生成主键 + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + public Long id; + + @ManyToOne + @JoinColumn(name = "receiver_id") + public Account receiver; + + public String message; + + public Date receiveDate; + + public Message() { + + } + + public Message(Account receiver, String message, Date receiveDate) { + this.receiver = receiver; + this.message = message; + this.receiveDate = receiveDate; + } + + @Override + public String toString() { + return ToStringBuilder.reflectionToString(this); + } +} diff --git a/examples/boot-api/src/main/java/org/springside/examples/bootapi/dto/AccountDto.java b/examples/boot-api/src/main/java/org/springside/examples/bootapi/dto/AccountDto.java new file mode 100644 index 000000000..e757e3060 --- /dev/null +++ b/examples/boot-api/src/main/java/org/springside/examples/bootapi/dto/AccountDto.java @@ -0,0 +1,14 @@ +package org.springside.examples.bootapi.dto; + +import org.apache.commons.lang3.builder.ToStringBuilder; + +public class AccountDto { + public Long id; + public String email; + public String name; + + @Override + public String toString() { + return ToStringBuilder.reflectionToString(this); + } +} diff --git a/examples/boot-api/src/main/java/org/springside/examples/bootapi/dto/BookDto.java b/examples/boot-api/src/main/java/org/springside/examples/bootapi/dto/BookDto.java new file mode 100644 index 000000000..075271a82 --- /dev/null +++ b/examples/boot-api/src/main/java/org/springside/examples/bootapi/dto/BookDto.java @@ -0,0 +1,31 @@ +package org.springside.examples.bootapi.dto; + +import java.util.Date; + +import org.apache.commons.lang3.builder.ToStringBuilder; + +import com.fasterxml.jackson.annotation.JsonFormat; + +public class BookDto { + + public Long id; + public String bookId; + public String title; + public String url; + public String status; + + public AccountDto owner; + + @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+08:00") + public Date onboardDate; + + public AccountDto borrower; + + @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+08:00") + public Date borrowDate; + + @Override + public String toString() { + return ToStringBuilder.reflectionToString(this); + } +} diff --git a/examples/boot-api/src/main/java/org/springside/examples/bootapi/repository/AccountDao.java b/examples/boot-api/src/main/java/org/springside/examples/bootapi/repository/AccountDao.java new file mode 100644 index 000000000..f9ce20586 --- /dev/null +++ b/examples/boot-api/src/main/java/org/springside/examples/bootapi/repository/AccountDao.java @@ -0,0 +1,16 @@ +package org.springside.examples.bootapi.repository; + +import org.springframework.data.repository.CrudRepository; +import org.springside.examples.bootapi.domain.Account; + +/** + * 基于Spring Data JPA的Dao接口, 自动根据接口生成实现. + * + * CrudRepository默认有针对实体对象的CRUD方法. + * + * Spring Data JPA 还会解释新增方法名生成新方法的实现. + */ +public interface AccountDao extends CrudRepository { + + Account findByEmail(String email); +} diff --git a/examples/boot-api/src/main/java/org/springside/examples/bootapi/repository/BookDao.java b/examples/boot-api/src/main/java/org/springside/examples/bootapi/repository/BookDao.java new file mode 100644 index 000000000..c8e194c00 --- /dev/null +++ b/examples/boot-api/src/main/java/org/springside/examples/bootapi/repository/BookDao.java @@ -0,0 +1,21 @@ +package org.springside.examples.bootapi.repository; + +import java.util.List; + +import org.springframework.data.domain.Pageable; +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springside.examples.bootapi.domain.Book; + +/** + * 基于Spring Data JPA的Dao接口, 自动根据接口生成实现. + * + * PagingAndSortingRepository默认有针对实体对象的CRUD与分页查询函数. + * + * Spring Data JPA 还会解释新增方法名生成新方法的实现. + */ +public interface BookDao extends PagingAndSortingRepository { + + List findByOwnerId(Long ownerId, Pageable pageable); + + List findByBorrowerId(Long borrowerId, Pageable pageable); +} diff --git a/examples/boot-api/src/main/java/org/springside/examples/bootapi/repository/MessageDao.java b/examples/boot-api/src/main/java/org/springside/examples/bootapi/repository/MessageDao.java new file mode 100644 index 000000000..5e2f148a7 --- /dev/null +++ b/examples/boot-api/src/main/java/org/springside/examples/bootapi/repository/MessageDao.java @@ -0,0 +1,12 @@ +package org.springside.examples.bootapi.repository; + +import org.springframework.data.repository.CrudRepository; +import org.springside.examples.bootapi.domain.Message; + +/** + * 基于Spring Data JPA的Dao接口, 自动根据接口生成实现. + * + * CrudRepository默认有针对实体对象的CRUD方法. + */ +public interface MessageDao extends CrudRepository { +} diff --git a/examples/boot-api/src/main/java/org/springside/examples/bootapi/service/AccountService.java b/examples/boot-api/src/main/java/org/springside/examples/bootapi/service/AccountService.java new file mode 100644 index 000000000..23adb5f8c --- /dev/null +++ b/examples/boot-api/src/main/java/org/springside/examples/bootapi/service/AccountService.java @@ -0,0 +1,108 @@ +package org.springside.examples.bootapi.service; + +import java.util.concurrent.TimeUnit; + +import javax.annotation.PostConstruct; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.actuate.metrics.CounterService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springside.examples.bootapi.domain.Account; +import org.springside.examples.bootapi.repository.AccountDao; +import org.springside.examples.bootapi.service.exception.ErrorCode; +import org.springside.examples.bootapi.service.exception.ServiceException; +import org.springside.modules.utils.text.HashUtil; +import org.springside.modules.utils.misc.IdGenerator; +import org.springside.modules.utils.text.EncodeUtil; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; + +// Spring Bean的标识. +@Service +public class AccountService { + + private static Logger logger = LoggerFactory.getLogger(AccountService.class); + + @Autowired + private AccountDao accountDao; + + // 注入配置值 + @Value("${app.loginTimeoutSecs:600}") + private int loginTimeoutSecs; + + // codehale metrics + @Autowired + private CounterService counterService; + + // guava cache + private Cache loginUsers; + + @PostConstruct + public void init() { + loginUsers = CacheBuilder.newBuilder().maximumSize(1000).expireAfterAccess(loginTimeoutSecs, TimeUnit.SECONDS) + .build(); + } + + @Transactional(readOnly = true) + public String login(String email, String password) { + Account account = accountDao.findByEmail(email); + + if (account == null) { + throw new ServiceException("User not exist", ErrorCode.UNAUTHORIZED); + } + + if (!account.hashPassword.equals(hashPassword(password))) { + throw new ServiceException("Password wrong", ErrorCode.UNAUTHORIZED); + } + + String token = IdGenerator.uuid2(); + loginUsers.put(token, account); + counterService.increment("loginUser"); + return token; + } + + public void logout(String token) { + Account account = loginUsers.getIfPresent(token); + if (account == null) { + logger.warn("logout an alreay logout token:" + token); + } else { + loginUsers.invalidate(token); + counterService.decrement("loginUser"); + } + } + + public Account getLoginUser(String token) { + + Account account = loginUsers.getIfPresent(token); + + if (account == null) { + throw new ServiceException("User doesn't login", ErrorCode.UNAUTHORIZED); + } + + return account; + } + + @Transactional + public void register(String email, String name, String password) { + + if (StringUtils.isBlank(email) || StringUtils.isBlank(password)) { + throw new ServiceException("Invalid parameter", ErrorCode.BAD_REQUEST); + } + + Account account = new Account(); + account.email = email; + account.name = name; + account.hashPassword = hashPassword(password); + accountDao.save(account); + } + + protected static String hashPassword(String password) { + return EncodeUtil.encodeBase64(HashUtil.sha1(password)); + } +} diff --git a/examples/boot-api/src/main/java/org/springside/examples/bootapi/service/BookAdminService.java b/examples/boot-api/src/main/java/org/springside/examples/bootapi/service/BookAdminService.java new file mode 100644 index 000000000..8178e1a45 --- /dev/null +++ b/examples/boot-api/src/main/java/org/springside/examples/bootapi/service/BookAdminService.java @@ -0,0 +1,89 @@ +package org.springside.examples.bootapi.service; + +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springside.examples.bootapi.domain.Account; +import org.springside.examples.bootapi.domain.Book; +import org.springside.examples.bootapi.repository.BookDao; +import org.springside.examples.bootapi.service.exception.ErrorCode; +import org.springside.examples.bootapi.service.exception.ServiceException; +import org.springside.modules.utils.time.ClockUtil; + + +// Spring Bean的标识. +@Service +public class BookAdminService { + + private static Logger logger = LoggerFactory.getLogger(BookBorrowService.class); + + @Autowired + private BookDao bookDao; + + + @Transactional(readOnly = true) + public Iterable findAll(Pageable pageable) { + return bookDao.findAll(pageable); + } + + @Transactional(readOnly = true) + public Book findOne(Long id) { + return bookDao.findOne(id); + } + + @Transactional(readOnly = true) + public List listMyBook(Long ownerId, Pageable pageable) { + return bookDao.findByOwnerId(ownerId, pageable); + } + + @Transactional + public void saveBook(Book book, Account owner) { + + book.owner = owner; + book.status = Book.STATUS_IDLE; + book.onboardDate = ClockUtil.currentDate(); + + bookDao.save(book); + } + + @Transactional + public void modifyBook(Book book, Long currentAccountId) { + if (!currentAccountId.equals(book.owner.id)) { + logger.error("user:" + currentAccountId + " try to modified a book:" + book.id + " which is not him"); + throw new ServiceException("User can't modify others book", ErrorCode.BOOK_OWNERSHIP_WRONG); + } + + Book orginalBook = bookDao.findOne(book.id); + + if (orginalBook == null) { + logger.error("user:" + currentAccountId + " try to modified a book:" + book.id + " which is not exist"); + throw new ServiceException("The Book is not exist", ErrorCode.BAD_REQUEST); + } + + orginalBook.title = book.title; + orginalBook.url = book.url; + bookDao.save(orginalBook); + } + + @Transactional + public void deleteBook(Long id, Long currentAccountId) { + Book book = bookDao.findOne(id); + + if (book == null) { + logger.error("user:" + currentAccountId + " try to delete a book:" + id + " which is not exist"); + throw new ServiceException("The Book is not exist", ErrorCode.BAD_REQUEST); + } + + if (!currentAccountId.equals(book.owner.id)) { + logger.error("user:" + currentAccountId + " try to delete a book:" + book.id + " which is not him"); + throw new ServiceException("User can't delete others book", ErrorCode.BOOK_OWNERSHIP_WRONG); + } + + bookDao.delete(id); + } +} diff --git a/examples/boot-api/src/main/java/org/springside/examples/bootapi/service/BookBorrowService.java b/examples/boot-api/src/main/java/org/springside/examples/bootapi/service/BookBorrowService.java new file mode 100644 index 000000000..9a09dbae6 --- /dev/null +++ b/examples/boot-api/src/main/java/org/springside/examples/bootapi/service/BookBorrowService.java @@ -0,0 +1,166 @@ +package org.springside.examples.bootapi.service; + +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springside.examples.bootapi.domain.Account; +import org.springside.examples.bootapi.domain.Book; +import org.springside.examples.bootapi.domain.Message; +import org.springside.examples.bootapi.repository.BookDao; +import org.springside.examples.bootapi.repository.MessageDao; +import org.springside.examples.bootapi.service.exception.ErrorCode; +import org.springside.examples.bootapi.service.exception.ServiceException; +import org.springside.modules.utils.time.ClockUtil; + +// Spring Bean的标识. +@Service +public class BookBorrowService { + + private static Logger logger = LoggerFactory.getLogger(BookBorrowService.class); + + @Autowired + protected BookDao bookDao; + + @Autowired + protected MessageDao messageDao; + + @Transactional + public void applyBorrowRequest(Long id, Account borrower) { + Book book = bookDao.findOne(id); + + if (!book.status.equals(Book.STATUS_IDLE)) { + logger.error("User request the book not idle, user id:" + borrower.id + ",book id:" + id + ",status:" + + book.status); + throw new ServiceException("The book is not idle", ErrorCode.BOOK_STATUS_WRONG); + } + + if (borrower.id.equals(book.owner.id)) { + logger.error("User borrow the book himself, user id:" + borrower.id + ",book id:" + id); + throw new ServiceException("User shouldn't borrower the book which is himeself", + ErrorCode.BOOK_OWNERSHIP_WRONG); + } + + book.status = Book.STATUS_REQUEST; + book.borrower = borrower; + bookDao.save(book); + + Message message = new Message(book.owner, + String.format("Apply book <%s> request by %s", book.title, borrower.name), ClockUtil.currentDate()); + + messageDao.save(message); + } + + @Transactional + public void cancelBorrowRequest(Long id, Account borrower) { + Book book = bookDao.findOne(id); + + if (!book.status.equals(Book.STATUS_REQUEST)) { + logger.error("User cancel the book not reqesting, user id:" + borrower.id + ",book id:" + id + ",status:" + + book.status); + throw new ServiceException("The book is not requesting", ErrorCode.BOOK_STATUS_WRONG); + } + + if (!borrower.id.equals(book.borrower.id)) { + logger.error("User cancel the book not request by him, user id:" + borrower.id + ",book id:" + id + + ",borrower id" + book.borrower.id); + throw new ServiceException("User can't cancel other ones request", ErrorCode.BOOK_OWNERSHIP_WRONG); + } + + book.status = Book.STATUS_IDLE; + book.borrower = null; + bookDao.save(book); + + Message message = new Message(book.owner, + String.format("Cancel book <%s> request by %s", book.title, borrower.name), ClockUtil.currentDate()); + + messageDao.save(message); + } + + @Transactional + public void markBookBorrowed(Long id, Account owner) { + Book book = bookDao.findOne(id); + + if (!book.status.equals(Book.STATUS_REQUEST)) { + logger.error("User confirm the book not reqesting, user id:" + owner.id + ",book id:" + id + ",status:" + + book.status); + throw new ServiceException("The book is not requesting", ErrorCode.BOOK_STATUS_WRONG); + } + + if (!owner.id.equals(book.owner.id)) { + logger.error("User confirm the book not himself, user id:" + owner.id + ",book id:" + id + ",owner id" + + book.owner.id); + throw new ServiceException("User can't cofirm others book", ErrorCode.BOOK_OWNERSHIP_WRONG); + } + + book.status = Book.STATUS_OUT; + book.borrowDate = ClockUtil.currentDate(); + bookDao.save(book); + + Message message = new Message(book.borrower, + String.format("Confirm book <%s> request by %s", book.title, owner.name), ClockUtil.currentDate()); + messageDao.save(message); + } + + @Transactional + public void rejectBorrowRequest(Long id, Account owner) { + Book book = bookDao.findOne(id); + + if (!book.status.equals(Book.STATUS_REQUEST)) { + logger.error("User reject the book not reqesting, user id:" + owner.id + ",book id:" + id + ",status:" + + book.status); + throw new ServiceException("The book is not requesting", ErrorCode.BOOK_STATUS_WRONG); + } + + if (!owner.id.equals(book.owner.id)) { + + logger.error("User reject the book not himself, user id:" + owner.id + ",book id:" + id + ",owener id" + + book.owner.id); + throw new ServiceException("User can't reject others book", ErrorCode.BOOK_OWNERSHIP_WRONG); + } + + book.status = Book.STATUS_IDLE; + book.borrowDate = null; + book.borrower = null; + bookDao.save(book); + + Message message = new Message(book.borrower, + String.format("Reject book <%s> request by %s", book.title, owner.name), ClockUtil.currentDate()); + messageDao.save(message); + } + + @Transactional + public void markBookReturned(Long id, Account owner) { + Book book = bookDao.findOne(id); + + if (!book.status.equals(Book.STATUS_OUT)) { + logger.error( + "User return the book not out, user id:" + owner.id + ",book id:" + id + ",status:" + book.status); + throw new ServiceException("The book is not borrowing", ErrorCode.BOOK_STATUS_WRONG); + } + + if (!owner.id.equals(book.owner.id)) { + logger.error("User return the book not himself, user id:" + owner.id + ",book id:" + id + ",owner id" + + book.owner.id); + throw new ServiceException("User can't make others book returned", ErrorCode.BOOK_OWNERSHIP_WRONG); + } + + book.status = Book.STATUS_IDLE; + book.borrowDate = null; + book.borrower = null; + bookDao.save(book); + + Message message = new Message(book.borrower, + String.format("Mark book <%s> returned by %s", book.title, owner.name), ClockUtil.currentDate()); + messageDao.save(message); + } + + @Transactional(readOnly = true) + public List listMyBorrowedBook(Long borrowerId, Pageable pageable) { + return bookDao.findByBorrowerId(borrowerId, pageable); + } +} diff --git a/examples/boot-api/src/main/java/org/springside/examples/bootapi/service/exception/ErrorCode.java b/examples/boot-api/src/main/java/org/springside/examples/bootapi/service/exception/ErrorCode.java new file mode 100644 index 000000000..3f25a0c59 --- /dev/null +++ b/examples/boot-api/src/main/java/org/springside/examples/bootapi/service/exception/ErrorCode.java @@ -0,0 +1,17 @@ +package org.springside.examples.bootapi.service.exception; + +public enum ErrorCode { + + BAD_REQUEST(400, 400), UNAUTHORIZED(401, 401), FORBIDDEN(403, 403), INTERNAL_SERVER_ERROR(500, 500), + + BOOK_STATUS_WRONG(1100, 400), BOOK_OWNERSHIP_WRONG(1101, 403), NO_TOKEN(1102, 401); + + public int code; + public int httpStatus; + + ErrorCode(int code, int httpStatus) { + this.code = code; + this.httpStatus = httpStatus; + } + +} diff --git a/examples/boot-api/src/main/java/org/springside/examples/bootapi/service/exception/ServiceException.java b/examples/boot-api/src/main/java/org/springside/examples/bootapi/service/exception/ServiceException.java new file mode 100644 index 000000000..34969e4eb --- /dev/null +++ b/examples/boot-api/src/main/java/org/springside/examples/bootapi/service/exception/ServiceException.java @@ -0,0 +1,13 @@ +package org.springside.examples.bootapi.service.exception; + +public class ServiceException extends RuntimeException { + + private static final long serialVersionUID = -8634700792767837033L; + + public ErrorCode errorCode; + + public ServiceException(String message, ErrorCode errorCode) { + super(message); + this.errorCode = errorCode; + } +} diff --git a/examples/boot-api/src/main/resources/.h2.server.properties b/examples/boot-api/src/main/resources/.h2.server.properties new file mode 100644 index 000000000..3eb9427c8 --- /dev/null +++ b/examples/boot-api/src/main/resources/.h2.server.properties @@ -0,0 +1,6 @@ +#H2 Server Properties +#Sat Nov 07 10:12:13 CST 2015 +0=TestDB(Embedded)|org.h2.Driver|jdbc\:h2\:mem\:testdb|sa +webAllowOthers=false +webPort=8090 +webSSL=false diff --git a/examples/boot-api/src/main/resources/application-prod.properties b/examples/boot-api/src/main/resources/application-prod.properties new file mode 100644 index 000000000..6729a6434 --- /dev/null +++ b/examples/boot-api/src/main/resources/application-prod.properties @@ -0,0 +1,31 @@ +#set H2 in file mode as the production DB +spring.jpa.database=H2 +spring.datasource.driverClassName=org.h2.Driver +spring.datasource.url=jdbc:h2:file:~/.h2/bootapi;AUTO_SERVER=TRUE;DB_CLOSE_DELAY=-1; +spring.datasource.username=sa +spring.datasource.password= + +#disable automatic initialize for embedded H2 +spring.jpa.hibernate.ddl-auto=none +spring.datasource.initialize=false +flyway.enabled=true + +#connection pool settings +spring.datasource.initial-size=10 +spring.datasource.max-active=100 +spring.datasource.min-idle=8 +spring.datasource.max-idle=8 +#spring.datasource.max-wait= +#spring.datasource.time-between-eviction-runs-millis= +#spring.datasource.min-evictable-idle-time-millis= + + +# logging settings +logging.file=/var/log/springside/boot-api.log +#logging.level.org.hibernate=WARN + +# optional tomcat settings +#server.contextPath=/ by default +#server.tomcat.maxThreads=200 by default +#server.tomcat.compression=on(off by default) +#server.tomcat.compressableMimeTypes=application/json,application/xml (text/html, text/xml, and text/plain by default) \ No newline at end of file diff --git a/examples/boot-api/src/main/resources/application.properties b/examples/boot-api/src/main/resources/application.properties new file mode 100644 index 000000000..953a2779b --- /dev/null +++ b/examples/boot-api/src/main/resources/application.properties @@ -0,0 +1,21 @@ +# server settings +server.port=8080 +management.port=7002 + +# application settings +app.loginTimeoutSecs=600 + +# db init settings +spring.jpa.hibernate.ddl-auto=validate +spring.jpa.show-sql=false +spring.datasource.initialize=true +spring.datasource.sqlScriptEncoding=UTF-8 +flyway.enabled=false + +# other settings +spring.main.show-banner=false +spring.jackson.serialization.INDENT_OUTPUT=true + +# /info endpoint +info.app.name=Spring Boot WebService Example +info.app.version=${project.version} \ No newline at end of file diff --git a/examples/boot-api/src/main/resources/data.sql b/examples/boot-api/src/main/resources/data.sql new file mode 100644 index 000000000..96202b3cd --- /dev/null +++ b/examples/boot-api/src/main/resources/data.sql @@ -0,0 +1,8 @@ + +insert into book (id, douban_id, title, url, description, owner_id,status,onboard_date) values(1,'25984046', 'Big Data日知录', 'http://book.douban.com/subject/25984046/','', 1,'idle','2015-01-01'); +insert into book (id, douban_id, title, url, description, owner_id,status,onboard_date) values(2,'25900156', 'Redis设计与实现', 'http://book.douban.com/subject/25900156/','', 1,'idle','2015-01-02'); +insert into book (id, douban_id, title, url, description, owner_id,status,onboard_date) values(3,'25741352', 'DSL实战', 'http://book.douban.com/subject/25741352/','', 2,'idle','2015-01-03'); + +insert into account (id,email,name,hash_password) values(1,'calvin.xiao@springside.io','Calvin','+2MunThvGcEfdYIFlT4NQQHt6z4='); +insert into account (id,email,name,hash_password) values(2,'david.wang@springside.io','David','+2MunThvGcEfdYIFlT4NQQHt6z4='); + diff --git a/examples/boot-api/src/main/resources/db/migration/V1.0__init.sql b/examples/boot-api/src/main/resources/db/migration/V1.0__init.sql new file mode 100644 index 000000000..be4371b92 --- /dev/null +++ b/examples/boot-api/src/main/resources/db/migration/V1.0__init.sql @@ -0,0 +1,29 @@ +create table book ( + id bigint generated by default as identity, + douban_id varchar(64) not null, + title varchar(128) not null, + url varchar(255), + description varchar(255), + owner_id bigint not null, + onboard_date timestamp, + status varchar(32) not null, + borrower_id bigint null, + borrow_date timestamp, + primary key (id) +); + +create table account ( + id bigint generated by default as identity, + name varchar(64) not null, + email varchar(128), + hash_password varchar(255), + primary key (id) +); + + +insert into book (id, douban_id, title, url, description, owner_id,status,onboard_date) values(1,'25984046', 'Big Data日知录', 'http://book.douban.com/subject/25984046/','', 1,'idle','2015-01-01'); +insert into book (id, douban_id, title, url, description, owner_id,status,onboard_date) values(2,'25900156', 'Redis设计与实现', 'http://book.douban.com/subject/25900156/','', 1,'idle','2015-01-02'); +insert into book (id, douban_id, title, url, description, owner_id,status,onboard_date) values(3,'25741352', 'DSL实战', 'http://book.douban.com/subject/25741352/','', 2,'idle','2015-01-03'); + +insert into account (id,email,name,hash_password) values(1,'calvin.xiao@springside.io','Calvin','+2MunThvGcEfdYIFlT4NQQHt6z4='); +insert into account (id,email,name,hash_password) values(2,'david.wang@springside.io','David','+2MunThvGcEfdYIFlT4NQQHt6z4='); \ No newline at end of file diff --git a/examples/boot-api/src/main/resources/db/migration/V1.1__add_table_message.sql b/examples/boot-api/src/main/resources/db/migration/V1.1__add_table_message.sql new file mode 100644 index 000000000..6e994d391 --- /dev/null +++ b/examples/boot-api/src/main/resources/db/migration/V1.1__add_table_message.sql @@ -0,0 +1,7 @@ +create table message ( + id bigint generated by default as identity, + receiver_id bigint null, + message varchar(256), + receive_date timestamp, + primary key (id) +); \ No newline at end of file diff --git a/examples/boot-api/src/main/resources/schema.sql b/examples/boot-api/src/main/resources/schema.sql new file mode 100644 index 000000000..823dd1e02 --- /dev/null +++ b/examples/boot-api/src/main/resources/schema.sql @@ -0,0 +1,33 @@ +drop table if exists book; +drop table if exists account; +drop table if exists message; + +create table book ( + id bigint generated by default as identity, + douban_id varchar(64) not null, + title varchar(128) not null, + url varchar(255), + description varchar(255), + owner_id bigint not null, + onboard_date timestamp, + status varchar(32) not null, + borrower_id bigint null, + borrow_date timestamp, + primary key (id) +); + +create table account ( + id bigint generated by default as identity, + name varchar(64) not null, + email varchar(128), + hash_password varchar(255), + primary key (id) +); + +create table message ( + id bigint generated by default as identity, + receiver_id bigint null, + message varchar(256), + receive_date timestamp, + primary key (id) +); \ No newline at end of file diff --git a/examples/boot-api/src/main/webapp/index.html b/examples/boot-api/src/main/webapp/index.html new file mode 100644 index 000000000..4cfbdbae5 --- /dev/null +++ b/examples/boot-api/src/main/webapp/index.html @@ -0,0 +1,69 @@ + + + +Spring Boot Web Service示例 + + + +

1. 顺序访问P2P图书馆 API:

+ +step1: + + +step2: + + + +step3: + + + +step4: + + + +other flow: + + + + +

2. 访问下列应用管理Endpoint:

+ + + +

3. JMX通过Restful的jolokia访问:

+ + + +

4. 在开发Profile下的H2 Console,用于查看内存中数据库的内容:

+ + + \ No newline at end of file diff --git a/examples/boot-api/src/test/java/org/springside/examples/bootapi/functional/BaseFunctionalTest.java b/examples/boot-api/src/test/java/org/springside/examples/bootapi/functional/BaseFunctionalTest.java new file mode 100644 index 000000000..6f4ce06a4 --- /dev/null +++ b/examples/boot-api/src/test/java/org/springside/examples/bootapi/functional/BaseFunctionalTest.java @@ -0,0 +1,31 @@ +package org.springside.examples.bootapi.functional; + +import org.junit.Rule; +import org.junit.rules.TestRule; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.boot.test.WebIntegrationTest; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springside.examples.bootapi.BootApiApplication; +import org.springside.modules.test.rule.TestProgress; + +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = BootApiApplication.class) +// 定义为Web集成测试,并使用随机端口号 +@WebIntegrationTest("server.port=0") +// 定义每执行完一个Test文件刷新一次Spring Application Context,避免Case间的数据影响. +// 但Test文件内多个测试方法间的影响仍需注意 +@DirtiesContext +public abstract class BaseFunctionalTest { + + // 注入启动server后的实际端口号 + @Value("${local.server.port}") + protected int port; + + // 在Console里打印Case的开始与结束,更容易分清Console里的日志归属于哪个Case. + @Rule + public TestRule testProgress = new TestProgress(); + +} diff --git a/examples/boot-api/src/test/java/org/springside/examples/bootapi/functional/BookEndpointTest.java b/examples/boot-api/src/test/java/org/springside/examples/bootapi/functional/BookEndpointTest.java new file mode 100644 index 000000000..fe8fe81ff --- /dev/null +++ b/examples/boot-api/src/test/java/org/springside/examples/bootapi/functional/BookEndpointTest.java @@ -0,0 +1,199 @@ +/* + * Copyright 2012-2014 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.springside.examples.bootapi.functional; + +import static org.assertj.core.api.Assertions.*; + +import java.util.ArrayList; +import java.util.Map; + +import org.junit.Before; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runners.MethodSorters; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.TestRestTemplate; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.client.RestTemplate; +import org.springside.examples.bootapi.api.support.ErrorResult; +import org.springside.examples.bootapi.domain.Book; +import org.springside.examples.bootapi.dto.BookDto; +import org.springside.examples.bootapi.repository.BookDao; +import org.springside.examples.bootapi.service.exception.ErrorCode; +import org.springside.modules.test.data.RandomData; +import org.springside.modules.utils.mapper.JsonMapper; + +import com.google.common.collect.Maps; + +// 测试方法的执行顺序在不同JVM里是不固定的,此处设为按方法名排序,避免方法间数据影响的不确定性 +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class BookEndpointTest extends BaseFunctionalTest { + + // 注入Spring Context中的BookDao,实现白盒查询数据库实际情况 + @Autowired + private BookDao bookDao; + + private RestTemplate restTemplate; + private JsonMapper jsonMapper = new JsonMapper(); + + private String resourceUrl; + private String loginUrl; + private String logoutUrl; + + @Before + public void setup() { + // TestRestTemplate与RestTemplate, 服务端返回非200返回码时,不会抛异常. + restTemplate = new TestRestTemplate(); + resourceUrl = "http://localhost:" + port + "/api/books"; + loginUrl = "http://localhost:" + port + "/api/accounts/login"; + logoutUrl = "http://localhost:" + port + "/api/accounts/logout"; + } + + @Test + public void listBook() { + BookList tasks = restTemplate.getForObject(resourceUrl, BookList.class); + assertThat(tasks).hasSize(3); + BookDto firstBook = tasks.get(0); + + assertThat(firstBook.title).isEqualTo("Big Data日知录"); + assertThat(firstBook.owner.name).isEqualTo("Calvin"); + + BookDto book = restTemplate.getForObject(resourceUrl + "/{id}", BookDto.class, 1L); + assertThat(book.title).isEqualTo("Big Data日知录"); + assertThat(book.owner.name).isEqualTo("Calvin"); + } + + @Test + public void applyRequest() { + String token = login("calvin.xiao@springside.io"); + + ResponseEntity response = restTemplate.getForEntity(resourceUrl + "/{id}/request?token={token}", + String.class, 3L, token); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + + // 查询数据库状态 + Book book = bookDao.findOne(3L); + assertThat(book.borrower.id).isEqualTo(1L); + assertThat(book.status).isEqualTo(Book.STATUS_REQUEST); + + // 回退操作 + response = restTemplate.getForEntity(resourceUrl + "/{id}/cancel?token={token}", String.class, 3L, token); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + + logout(token); + } + + @Test + public void applyRequestWithError() { + // 未设置token + ResponseEntity response = restTemplate.getForEntity(resourceUrl + "/{id}/request", String.class, 1L); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED); + ErrorResult errorResult = jsonMapper.fromJson(response.getBody(), ErrorResult.class); + assertThat(errorResult.code).isEqualTo(ErrorCode.NO_TOKEN.code); + + Book book = bookDao.findOne(1L); + assertThat(book.borrower).isNull(); + + // 设置错误token + response = restTemplate.getForEntity(resourceUrl + "/{id}/request?token={token}", String.class, 1L, "abc"); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED); + errorResult = jsonMapper.fromJson(response.getBody(), ErrorResult.class); + assertThat(errorResult.code).isEqualTo(ErrorCode.UNAUTHORIZED.code); + + book = bookDao.findOne(1L); + assertThat(book.borrower).isNull(); + + // 自己借自己的书 + String token = login("calvin.xiao@springside.io"); + + response = restTemplate.getForEntity(resourceUrl + "/{id}/request?token={token}", String.class, 1L, token); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN); + errorResult = jsonMapper.fromJson(response.getBody(), ErrorResult.class); + assertThat(errorResult.code).isEqualTo(ErrorCode.BOOK_OWNERSHIP_WRONG.code); + + book = bookDao.findOne(1L); + assertThat(book.borrower).isNull(); + + logout(token); + + // 借一本已被申请借出的书 + token = login("calvin.xiao@springside.io"); + + response = restTemplate.getForEntity(resourceUrl + "/{id}/request?token={token}", String.class, 3L, token); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + + response = restTemplate.getForEntity(resourceUrl + "/{id}/request?token={token}", String.class, 3L, token); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); + errorResult = jsonMapper.fromJson(response.getBody(), ErrorResult.class); + assertThat(errorResult.code).isEqualTo(ErrorCode.BOOK_STATUS_WRONG.code); + + // 回退操作 + response = restTemplate.getForEntity(resourceUrl + "/{id}/cancel?token={token}", String.class, 3L, token); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + + logout(token); + } + + @Test + public void fullBorrowProcess() { + // 发起请求 + String token = login("david.wang@springside.io"); + + ResponseEntity response = restTemplate.getForEntity(resourceUrl + "/{id}/request?token={token}", + String.class, 1L, token); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + + logout(token); + + // 确认借出 + token = login("calvin.xiao@springside.io"); + + response = restTemplate.getForEntity(resourceUrl + "/{id}/confirm?token={token}", String.class, 1L, token); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + + // 确认归还 + response = restTemplate.getForEntity(resourceUrl + "/{id}/return?token={token}", String.class, 1L, token); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + + logout(token); + } + + private String login(String user) { + Map map = Maps.newHashMap(); + map.put("email", user); + map.put("password", "springside"); + + ResponseEntity response = restTemplate.getForEntity(loginUrl + "?email={email}&password={password}", + Map.class, map); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + return (String) response.getBody().get("token"); + } + + public void logout(String token) { + restTemplate.getForEntity(logoutUrl + "?token={token}", String.class, token); + } + + private static BookDto randomBook() { + BookDto book = new BookDto(); + book.title = RandomData.randomName("Book"); + + return book; + } + + // ArrayList在RestTemplate转换时不好表示,创建一个类来表达它是最简单的。 + private static class BookList extends ArrayList { + } + +} diff --git a/examples/boot-api/src/test/java/org/springside/examples/bootapi/repository/BookDaoTest.java b/examples/boot-api/src/test/java/org/springside/examples/bootapi/repository/BookDaoTest.java new file mode 100644 index 000000000..2f3d08416 --- /dev/null +++ b/examples/boot-api/src/test/java/org/springside/examples/bootapi/repository/BookDaoTest.java @@ -0,0 +1,37 @@ +package org.springside.examples.bootapi.repository; + +import static org.assertj.core.api.Assertions.*; + +import java.util.List; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.data.domain.PageRequest; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springside.examples.bootapi.BootApiApplication; +import org.springside.examples.bootapi.domain.Book; + +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = BootApiApplication.class) +@DirtiesContext +public class BookDaoTest { + + @Autowired + private BookDao bookDao; + + @Test + public void findByOwnerId() { + List books = bookDao.findByOwnerId(1L, new PageRequest(0, 10)); + assertThat(books).hasSize(2); + assertThat(books.get(0).title).isEqualTo("Big Data日知录"); + } + + @Test + public void findByBorrowerId() { + List books = bookDao.findByBorrowerId(1L, new PageRequest(0, 10)); + assertThat(books).hasSize(0); + } +} diff --git a/examples/boot-api/src/test/java/org/springside/examples/bootapi/service/AccountServiceTest.java b/examples/boot-api/src/test/java/org/springside/examples/bootapi/service/AccountServiceTest.java new file mode 100644 index 000000000..a751e533c --- /dev/null +++ b/examples/boot-api/src/test/java/org/springside/examples/bootapi/service/AccountServiceTest.java @@ -0,0 +1,12 @@ +package org.springside.examples.bootapi.service; + +import org.junit.Test; + +public class AccountServiceTest { + + @Test + public void hash() throws Exception { + System.out.println("hashPassword:" + AccountService.hashPassword("springside")); + } + +} diff --git a/examples/boot-api/src/test/java/org/springside/examples/bootapi/service/BookBorrowServiceTest.java b/examples/boot-api/src/test/java/org/springside/examples/bootapi/service/BookBorrowServiceTest.java new file mode 100644 index 000000000..13c7def8d --- /dev/null +++ b/examples/boot-api/src/test/java/org/springside/examples/bootapi/service/BookBorrowServiceTest.java @@ -0,0 +1,87 @@ +package org.springside.examples.bootapi.service; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; +import org.springside.examples.bootapi.domain.Account; +import org.springside.examples.bootapi.domain.Book; +import org.springside.examples.bootapi.domain.Message; +import org.springside.examples.bootapi.repository.BookDao; +import org.springside.examples.bootapi.repository.MessageDao; +import org.springside.modules.test.log.LogbackListAppender; + +public class BookBorrowServiceTest { + + private BookBorrowService service; + + private BookDao mockBookDao; + + private MessageDao mockMessageDao; + + private LogbackListAppender appender; + + @Before + public void setup() { + service = new BookBorrowService(); + mockBookDao = Mockito.mock(BookDao.class); + mockMessageDao = Mockito.mock(MessageDao.class); + service.bookDao = mockBookDao; + service.messageDao = mockMessageDao; + + appender = new LogbackListAppender(); + appender.addToLogger(BookBorrowService.class); + } + + public void tearDown() { + appender.removeFromLogger(BookBorrowService.class); + } + + @Test + public void applyBorrowRequest() { + + Book book = new Book(1L); + book.status = Book.STATUS_IDLE; + book.owner = new Account(1L); + + Mockito.when(mockBookDao.findOne(1L)).thenReturn(book); + + service.applyBorrowRequest(1L, new Account(3L)); + + Mockito.verify(mockBookDao).save(Mockito.any(Book.class)); + Mockito.verify(mockMessageDao).save(Mockito.any(Message.class)); + } + + @Test + public void applyBorrowRequestWithError() { + + // 自己借自己的书 + Book book = new Book(1L); + book.status = Book.STATUS_IDLE; + book.owner = new Account(1L); + + Mockito.when(mockBookDao.findOne(1L)).thenReturn(book); + try { + service.applyBorrowRequest(1L, new Account(1L)); + fail("should fail here"); + } catch (Exception e) { + assertThat(e).hasMessageContaining("User shouldn't borrower the book which is himeself"); + assertThat(appender.getLastMessage()).contains("user id:1,book id:1"); + } + // 保证BookDao没被调用 + Mockito.verify(mockBookDao, Mockito.never()).save(Mockito.any(Book.class)); + + // 借已借出的书 + book.status = Book.STATUS_REQUEST; + + try { + service.applyBorrowRequest(1L, new Account(3L)); + fail("should fail here"); + } catch (Exception e) { + assertThat(e).hasMessageContaining("The book is not idle"); + assertThat(appender.getLastMessage()).contains("user id:3,book id:1,status:request"); + } + Mockito.verify(mockBookDao, Mockito.never()).save(Mockito.any(Book.class)); + } +} diff --git a/examples/boot-api/start.sh b/examples/boot-api/start.sh new file mode 100755 index 000000000..a2e608500 --- /dev/null +++ b/examples/boot-api/start.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +echo "start applicaiton in production mode" + +java -Xmx1024m -XX:MaxPermSize=128M -Djava.security.egd=file:/dev/./urandom -jar target/boot-api-5.0.0-SNAPSHOT.war --spring.profiles.active=prod + +#set the server port +#java -Xmx1024m -jar target/boot-api-5.0.0-SNAPSHOT.war --server.port=9090 + +#set the properties file location +#java -Xmx1024m -jar target/boot-api-5.0.0-SNAPSHOT.war --spring.config.location=/var/myapp/conf \ No newline at end of file diff --git a/examples/boot-showcase/README.txt b/examples/boot-showcase/README.txt new file mode 100644 index 000000000..107ca5e4b --- /dev/null +++ b/examples/boot-showcase/README.txt @@ -0,0 +1,12 @@ +What is more? + +定义: + +* 不把Spring boot的pom.xml作为parent +* 自定义Logback输出格式 +* 禁止无用的SpringBoot Endpoint +* 使用JavaSimon, 演示自定义 AOP切面与Servlet映射 + +功能: + +* email \ No newline at end of file diff --git a/examples/boot-showcase/pom.xml b/examples/boot-showcase/pom.xml new file mode 100644 index 000000000..14ef5fbce --- /dev/null +++ b/examples/boot-showcase/pom.xml @@ -0,0 +1,162 @@ + + + 4.0.0 + + io.springside.examples + boot-showcase + 5.0.0-SNAPSHOT + war + Springside :: Example :: SpringBoot WebService Advanced Demo + + + 5.0.0-SNAPSHOT + 1.4.3.RELEASE + 3.5 + 3.5.2 + + 1.7 + UTF-8 + ${java.version} + ${java.version} + + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.springframework.boot + spring-boot-starter-mail + + + + + io.springside + springside-utils + ${springside.version} + + + + io.springside + springside-core + ${springside.version} + + + + + javax.servlet + javax.servlet-api + + + + + com.h2database + h2 + runtime + + + + + org.apache.commons + commons-lang3 + ${commons-lang3.version} + + + + + org.jolokia + jolokia-core + + + + + org.javasimon + javasimon-spring + ${javasimon.version} + + + org.javasimon + javasimon-console-embed + ${javasimon.version} + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.assertj + assertj-core + test + + + + io.springside + springside-utils + ${springside.version} + tests + test + + + + + + + src/main/resources + true + + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot.version} + + + + repackage + + + + + + org.apache.maven.plugins + maven-war-plugin + 2.4 + + false + + + + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + + diff --git a/examples/boot-showcase/src/main/java/org/springside/examples/showcase/BootShowcaseApplication.java b/examples/boot-showcase/src/main/java/org/springside/examples/showcase/BootShowcaseApplication.java new file mode 100644 index 000000000..1af23353e --- /dev/null +++ b/examples/boot-showcase/src/main/java/org/springside/examples/showcase/BootShowcaseApplication.java @@ -0,0 +1,12 @@ +package org.springside.examples.showcase; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class BootShowcaseApplication { + + public static void main(String[] args) throws Exception { + SpringApplication.run(BootShowcaseApplication.class, args); + } +} diff --git a/examples/boot-showcase/src/main/java/org/springside/examples/showcase/config/JavaSimonConfig.java b/examples/boot-showcase/src/main/java/org/springside/examples/showcase/config/JavaSimonConfig.java new file mode 100644 index 000000000..5809656f2 --- /dev/null +++ b/examples/boot-showcase/src/main/java/org/springside/examples/showcase/config/JavaSimonConfig.java @@ -0,0 +1,35 @@ +package org.springside.examples.showcase.config; + +import org.javasimon.console.SimonConsoleServlet; +import org.javasimon.spring.MonitoredMeasuringPointcut; +import org.javasimon.spring.MonitoringInterceptor; +import org.springframework.aop.support.DefaultPointcutAdvisor; +import org.springframework.boot.context.embedded.ServletRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * 演示配置能力,包括AOP配置与Servlet配置 + */ +@Configuration +public class JavaSimonConfig { + + // 定义AOP, 对标注了@Monitored的方法进行监控 + @Bean(name = "monitoringAdvisor") + public DefaultPointcutAdvisor monitoringAdvisor() { + DefaultPointcutAdvisor monitoringAdvisor = new DefaultPointcutAdvisor(); + monitoringAdvisor.setAdvice(new MonitoringInterceptor()); + monitoringAdvisor.setPointcut(new MonitoredMeasuringPointcut()); + return monitoringAdvisor; + } + + // 定义Servlet URL Mapping + @Bean + public ServletRegistrationBean dispatcherRegistration() { + ServletRegistrationBean registration = new ServletRegistrationBean(new SimonConsoleServlet()); + registration.addInitParameter("url-prefix", "/javasimon"); + registration.addUrlMappings("/javasimon/*"); + return registration; + } + +} diff --git a/examples/boot-showcase/src/main/java/org/springside/examples/showcase/mail/MailService.java b/examples/boot-showcase/src/main/java/org/springside/examples/showcase/mail/MailService.java new file mode 100644 index 000000000..7acead617 --- /dev/null +++ b/examples/boot-showcase/src/main/java/org/springside/examples/showcase/mail/MailService.java @@ -0,0 +1,22 @@ +package org.springside.examples.showcase.mail; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.mail.SimpleMailMessage; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.stereotype.Component; + +@Component +public class MailService { + + @Autowired + private JavaMailSender mailSender; + + public void sendMail(String to, String from, String subject, String content) { + SimpleMailMessage mail = new SimpleMailMessage(); + mail.setFrom(from); + mail.setTo(to); + mail.setSubject(subject); + mail.setText(content); + mailSender.send(mail); + } +} diff --git a/examples/boot-showcase/src/main/java/org/springside/examples/showcase/web/HelloController.java b/examples/boot-showcase/src/main/java/org/springside/examples/showcase/web/HelloController.java new file mode 100644 index 000000000..b6bc04df7 --- /dev/null +++ b/examples/boot-showcase/src/main/java/org/springside/examples/showcase/web/HelloController.java @@ -0,0 +1,18 @@ +package org.springside.examples.showcase.web; + +import org.javasimon.aop.Monitored; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springside.modules.web.MediaTypes; + +@RestController +public class HelloController { + + @RequestMapping(value = "/hello", produces = MediaTypes.TEXT_PLAIN) + @Monitored + public String hello(@RequestParam("name") String name) { + return "hello " + name; + } + +} diff --git a/examples/boot-showcase/src/main/resources/application-prod.properties b/examples/boot-showcase/src/main/resources/application-prod.properties new file mode 100644 index 000000000..ff0b2832d --- /dev/null +++ b/examples/boot-showcase/src/main/resources/application-prod.properties @@ -0,0 +1,32 @@ +spring.jpa.database=H2 + +spring.datasource.driverClassName=org.h2.Driver +spring.datasource.url=jdbc:h2:file:~/.h2/bootmore;AUTO_SERVER=TRUE;DB_CLOSE_DELAY=-1; +spring.datasource.username=sa +spring.datasource.password= + +spring.datasource.initialize=false +spring.jpa.hibernate.ddl-auto=none + +spring.datasource.initial-size=10 +spring.datasource.max-active=100 +spring.datasource.min-idle=8 +spring.datasource.max-idle=8 +#spring.datasource.max-wait= +#spring.datasource.time-between-eviction-runs-millis= +#spring.datasource.min-evictable-idle-time-millis= + + +# logging +logging.file=/var/log/springside/boot-showcase.log +#logging.level.org.hibernate=WARN + +# tomcat settings +#server.contextPath=/ by default +#server.tomcat.maxThreads=200 by default +#server.tomcat.compression=on(off by default) +#server.tomcat.compressableMimeTypes=application/json,application/xml (text/html, text/xml, and text/plain by default) +#server.address +#server.sessiontimeout + + diff --git a/examples/boot-showcase/src/main/resources/application.properties b/examples/boot-showcase/src/main/resources/application.properties new file mode 100644 index 000000000..825fcd413 --- /dev/null +++ b/examples/boot-showcase/src/main/resources/application.properties @@ -0,0 +1,32 @@ +# server +server.port=8080 +management.port=7002 + +# db init settings +spring.jpa.hibernate.ddl-auto=validate +spring.jpa.show-sql=false +spring.datasource.initialize=true +spring.datasource.sqlScriptEncoding=UTF-8 + +# other +spring.main.show-banner=false +http.mappers.json-pretty-print=true +http.mappers.json-sort-keys=true + +# disable useless endpoints +endpoints.autoconfig.enabled=false +endpoints.beans.enabled=false +endpoints.configprops.enabled=false +endpoints.mappings.enabled=false +endpoints.trace.enabled=false +#endpoints.shutdown.enabled=true + +# mail setting +spring.mail.host=smtp.163.com +spring.mail.username=springside@163.com +spring.mail.password=springside123 +spring.mail.properties.mail.smtp.auth=true + +# /info endpoint +info.app.name=Advanced Spring Boot Showcase Example +info.app.version=${project.version} diff --git a/examples/boot-showcase/src/main/resources/logback.xml b/examples/boot-showcase/src/main/resources/logback.xml new file mode 100644 index 000000000..d46ef389e --- /dev/null +++ b/examples/boot-showcase/src/main/resources/logback.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/examples/pom.xml b/examples/pom.xml index 699df835c..e5a848f30 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -1,20 +1,15 @@ - - - 4.0.0 - - org.springside - springside-parent - 4.1.1-SNAPSHOT - ../modules/parent/ - - org.springside.examples - springside-examples - Springside :: Example - pom - - - quickstart - showcase - + + + 4.0.0 + io.springside.examples + springside-examples + 5.0.0-SNAPSHOT + Springside :: Examples + pom + + + boot-api + boot-showcase + \ No newline at end of file diff --git a/examples/quickstart/bin/jetty.bat b/examples/quickstart/bin/jetty.bat deleted file mode 100644 index 306038fec..000000000 --- a/examples/quickstart/bin/jetty.bat +++ /dev/null @@ -1,11 +0,0 @@ -@echo off -echo [INFO] Use maven jetty-plugin run the project. - -cd %~dp0 -cd .. - -set MAVEN_OPTS=%MAVEN_OPTS% -XX:MaxPermSize=128m -call mvn jetty:run -Djetty.port=8080 - -cd bin -pause \ No newline at end of file diff --git a/examples/quickstart/bin/package.bat b/examples/quickstart/bin/package.bat deleted file mode 100644 index a9b5ddbae..000000000 --- a/examples/quickstart/bin/package.bat +++ /dev/null @@ -1,8 +0,0 @@ -@echo off -echo [INFO] Package the war in target dir. - -cd %~dp0 -cd .. -call mvn clean package -Dmaven.test.skip=true -cd bin -pause \ No newline at end of file diff --git a/examples/quickstart/bin/refresh-db.bat b/examples/quickstart/bin/refresh-db.bat deleted file mode 100644 index 6a63ae5de..000000000 --- a/examples/quickstart/bin/refresh-db.bat +++ /dev/null @@ -1,10 +0,0 @@ -@echo off -echo [INFO] Re-create the schema and provision the sample data. - -cd %~dp0 -cd .. - -call mvn antrun:run -Prefresh-db - -cd bin -pause \ No newline at end of file diff --git a/examples/quickstart/bin/smoke-test.bat b/examples/quickstart/bin/smoke-test.bat deleted file mode 100644 index 6b3ea76b8..000000000 --- a/examples/quickstart/bin/smoke-test.bat +++ /dev/null @@ -1,11 +0,0 @@ -@echo off -echo [INFO] run smoking functional test. - -cd %~dp0 -cd .. - -set MAVEN_OPTS=%MAVEN_OPTS% -XX:MaxPermSize=128m -call mvn clean test -Pfunctional-test,run-smoke - -cd bin -pause \ No newline at end of file diff --git a/examples/quickstart/pom.xml b/examples/quickstart/pom.xml deleted file mode 100644 index 57f199dbb..000000000 --- a/examples/quickstart/pom.xml +++ /dev/null @@ -1,635 +0,0 @@ - - - 4.0.0 - org.springside.examples - quickstart - 4.1.1-SNAPSHOT - war - Springside :: Example :: QuickStart - - - - 4.1.1-SNAPSHOT - 3.2.6.RELEASE - 4.2.8.Final - 1.4.3.RELEASE - 7.0.47 - 2.4.2 - 1.2.2 - 4.3.1.Final - 2.3.0 - 1.7.5 - 1.0.13 - 3.1 - 15.0 - 4.11 - 1.9.5 - 2.38.0 - 7.6.14.v20131031 - 1.3.174 - - - UTF-8 - 1.6 - - - com.h2database - h2 - ${h2.version} - - - - - 3.0.0 - - - - - - - - - - - - - org.springside - springside-core - ${springside.version} - - - - - org.aspectj - aspectjrt - 1.7.3 - - - org.aspectj - aspectjweaver - 1.7.3 - runtime - - - - - - - org.hibernate - hibernate-entitymanager - ${hibernate.version} - - - - - org.springframework.data - spring-data-jpa - ${spring-data-jpa.version} - - - junit - junit-dep - - - - - org.springframework - spring-orm - ${spring.version} - - - org.springframework - spring-tx - ${spring.version} - - - - - org.apache.tomcat - tomcat-jdbc - ${tomcat-jdbc.version} - runtime - - - - - ${jdbc.driver.groupId} - ${jdbc.driver.artifactId} - ${jdbc.driver.version} - runtime - - - - - - org.springframework - spring-webmvc - ${spring.version} - - - - opensymphony - sitemesh - ${sitemesh.version} - runtime - - - - javax.servlet - jstl - 1.2 - - - - javax.servlet - servlet-api - 2.5 - provided - - - - - - org.apache.shiro - shiro-spring - ${shiro.version} - - - org.apache.shiro - shiro-ehcache - ${shiro.version} - - - net.sf.ehcache - ehcache-core - 2.6.6 - - - commons-codec - commons-codec - 1.8 - - - - - - org.hibernate - hibernate-validator - ${hibernate-validator.version} - - - - - com.fasterxml.jackson.core - jackson-databind - ${jackson.version} - - - com.fasterxml.jackson.module - jackson-module-jaxb-annotations - ${jackson.version} - - - - - - org.slf4j - slf4j-api - ${slf4j.version} - - - - ch.qos.logback - logback-classic - ${logback.version} - runtime - - - - - org.slf4j - log4j-over-slf4j - ${slf4j.version} - runtime - - - - org.slf4j - jcl-over-slf4j - ${slf4j.version} - runtime - - - - org.slf4j - jul-to-slf4j - ${slf4j.version} - runtime - - - - com.googlecode.log4jdbc - log4jdbc - 1.2 - runtime - - - - - - org.apache.commons - commons-lang3 - ${commons-lang3.version} - - - com.google.guava - guava - ${guava.version} - - - - - - org.springside - springside-test - ${springside.version} - test - - - - junit - junit - ${junit.version} - test - - - - org.mockito - mockito-core - ${mockito.version} - test - - - - org.springframework - spring-test - ${spring.version} - test - - - - - org.seleniumhq.selenium - selenium-java - ${selenium.version} - test - - - org.seleniumhq.selenium - selenium-htmlunit-driver - - - org.seleniumhq.selenium - selenium-android-driver - - - org.seleniumhq.selenium - selenium-iphone-driver - - - org.seleniumhq.selenium - selenium-safari-driver - - - commons-logging - commons-logging - - - - - org.seleniumhq.selenium - selenium-remote-driver - ${selenium.version} - test - - - cglib - cglib-nodep - - - commons-logging - commons-logging - - - - - - - - - org.eclipse.jetty.aggregate - jetty-webapp - ${jetty.version} - test - - - org.eclipse.jetty - jetty-jsp - ${jetty.version} - test - - - - - - - - org.springframework - spring-core - ${spring.version} - - - commons-logging - commons-logging - - - - - org.springframework - spring-beans - ${spring.version} - - - org.springframework - spring-context - ${spring.version} - - - org.springframework - spring-aop - ${spring.version} - - - commons-logging - commons-logging - - - - - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.1 - - ${jdk.version} - ${jdk.version} - true - - - - - - org.apache.maven.plugins - maven-war-plugin - 2.4 - - ${project.artifactId} - - - - - - org.apache.maven.plugins - maven-surefire-plugin - 2.16 - - - **/*Test.java - - -Xmx256M - - - - org.apache.maven.surefire - surefire-junit47 - 2.16 - - - - - - - org.codehaus.mojo - build-helper-maven-plugin - 1.8 - - - add-functional-source - generate-sources - - add-test-source - - - - src/test/functional - - - - - - - - - org.codehaus.mojo - cobertura-maven-plugin - 2.6 - - - - **/entity/**/*.class - **/*Controller.class - - - - - - - - org.apache.maven.plugins - maven-enforcer-plugin - 1.3.1 - - - enforce-banned-dependencies - - enforce - - - - - 3.0.3 - - - 1.6 - - - true - - commons-logging - aspectj:aspectj* - org.springframework - - - org.springframework:*:3.2.* - - - - true - - - - - - - - org.mortbay.jetty - jetty-maven-plugin - ${jetty.version} - - - - spring.profiles.active - development - - - true - - - /${project.artifactId} - - - - - - - org.apache.maven.plugins - maven-resource-plugin - 2.5 - - - - - org.apache.maven.plugins - maven-install-plugin - 2.5.1 - - - - - org.apache.maven.plugins - maven-antrun-plugin - 1.7 - - - - - - - - functional-test - - - - org.apache.maven.plugins - maven-surefire-plugin - - -Xmx256M -XX:MaxPermSize=128m - - **/*FT.java - - - false - - - ${selenium.driver} - - - - - - - - - - run-smoke - - - - org.apache.maven.plugins - maven-surefire-plugin - - org.springside.modules.test.category.Smoke - - - - - - - - - refresh-db - - - - org.apache.maven.plugins - maven-antrun-plugin - - - - - - - - - - - - - - - - - diff --git a/examples/quickstart/src/main/java/org/springside/examples/quickstart/entity/IdEntity.java b/examples/quickstart/src/main/java/org/springside/examples/quickstart/entity/IdEntity.java deleted file mode 100644 index 116b4f404..000000000 --- a/examples/quickstart/src/main/java/org/springside/examples/quickstart/entity/IdEntity.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.springside.examples.quickstart.entity; - -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.MappedSuperclass; - -/** - * 统一定义id的entity基类. - * - * 基类统一定义id的属性名称、数据类型、列名映射及生成策略. - * Oracle需要每个Entity独立定义id的SEQUCENCE时,不继承于本类而改为实现一个Idable的接口。 - * - * @author calvin - */ -// JPA 基类的标识 -@MappedSuperclass -public abstract class IdEntity { - - protected Long id; - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } -} diff --git a/examples/quickstart/src/main/java/org/springside/examples/quickstart/entity/Task.java b/examples/quickstart/src/main/java/org/springside/examples/quickstart/entity/Task.java deleted file mode 100644 index b809c6371..000000000 --- a/examples/quickstart/src/main/java/org/springside/examples/quickstart/entity/Task.java +++ /dev/null @@ -1,47 +0,0 @@ -package org.springside.examples.quickstart.entity; - -import javax.persistence.Entity; -import javax.persistence.JoinColumn; -import javax.persistence.ManyToOne; -import javax.persistence.Table; - -import org.hibernate.validator.constraints.NotBlank; - -//JPA标识 -@Entity -@Table(name = "ss_task") -public class Task extends IdEntity { - - private String title; - private String description; - private User user; - - // JSR303 BeanValidator的校验规则 - @NotBlank - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - // JPA 基于USER_ID列的多对一关系定义 - @ManyToOne - @JoinColumn(name = "user_id") - public User getUser() { - return user; - } - - public void setUser(User user) { - this.user = user; - } -} diff --git a/examples/quickstart/src/main/java/org/springside/examples/quickstart/entity/User.java b/examples/quickstart/src/main/java/org/springside/examples/quickstart/entity/User.java deleted file mode 100644 index ef367bac3..000000000 --- a/examples/quickstart/src/main/java/org/springside/examples/quickstart/entity/User.java +++ /dev/null @@ -1,110 +0,0 @@ -package org.springside.examples.quickstart.entity; - -import java.util.Date; -import java.util.List; - -import javax.persistence.Entity; -import javax.persistence.Table; -import javax.persistence.Transient; - -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.builder.ToStringBuilder; -import org.hibernate.validator.constraints.NotBlank; - -import com.fasterxml.jackson.annotation.JsonFormat; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.google.common.collect.ImmutableList; - -@Entity -@Table(name = "ss_user") -public class User extends IdEntity { - private String loginName; - private String name; - private String plainPassword; - private String password; - private String salt; - private String roles; - private Date registerDate; - - public User() { - } - - public User(Long id) { - this.id = id; - } - - @NotBlank - public String getLoginName() { - return loginName; - } - - public void setLoginName(String loginName) { - this.loginName = loginName; - } - - @NotBlank - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - // 不持久化到数据库,也不显示在Restful接口的属性. - @Transient - @JsonIgnore - public String getPlainPassword() { - return plainPassword; - } - - public void setPlainPassword(String plainPassword) { - this.plainPassword = plainPassword; - } - - public String getPassword() { - return password; - } - - public void setPassword(String password) { - this.password = password; - } - - public String getSalt() { - return salt; - } - - public void setSalt(String salt) { - this.salt = salt; - } - - public String getRoles() { - return roles; - } - - public void setRoles(String roles) { - this.roles = roles; - } - - @Transient - @JsonIgnore - public List getRoleList() { - // 角色列表在数据库中实际以逗号分隔字符串存储,因此返回不能修改的List. - return ImmutableList.copyOf(StringUtils.split(roles, ",")); - } - - // 设定JSON序列化时的日期格式 - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+08:00") - public Date getRegisterDate() { - return registerDate; - } - - public void setRegisterDate(Date registerDate) { - this.registerDate = registerDate; - } - - @Override - public String toString() { - return ToStringBuilder.reflectionToString(this); - } -} \ No newline at end of file diff --git a/examples/quickstart/src/main/java/org/springside/examples/quickstart/repository/TaskDao.java b/examples/quickstart/src/main/java/org/springside/examples/quickstart/repository/TaskDao.java deleted file mode 100644 index e1569c901..000000000 --- a/examples/quickstart/src/main/java/org/springside/examples/quickstart/repository/TaskDao.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.springside.examples.quickstart.repository; - -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.repository.JpaSpecificationExecutor; -import org.springframework.data.jpa.repository.Modifying; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.PagingAndSortingRepository; -import org.springside.examples.quickstart.entity.Task; - -public interface TaskDao extends PagingAndSortingRepository, JpaSpecificationExecutor { - - Page findByUserId(Long id, Pageable pageRequest); - - @Modifying - @Query("delete from Task task where task.user.id=?1") - void deleteByUserId(Long id); -} diff --git a/examples/quickstart/src/main/java/org/springside/examples/quickstart/repository/UserDao.java b/examples/quickstart/src/main/java/org/springside/examples/quickstart/repository/UserDao.java deleted file mode 100644 index 02f690a83..000000000 --- a/examples/quickstart/src/main/java/org/springside/examples/quickstart/repository/UserDao.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.springside.examples.quickstart.repository; - -import org.springframework.data.repository.PagingAndSortingRepository; -import org.springside.examples.quickstart.entity.User; - -public interface UserDao extends PagingAndSortingRepository { - User findByLoginName(String loginName); -} diff --git a/examples/quickstart/src/main/java/org/springside/examples/quickstart/rest/RestException.java b/examples/quickstart/src/main/java/org/springside/examples/quickstart/rest/RestException.java deleted file mode 100644 index 906cbbdb3..000000000 --- a/examples/quickstart/src/main/java/org/springside/examples/quickstart/rest/RestException.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.springside.examples.quickstart.rest; - -import org.springframework.http.HttpStatus; - -/** - * 专用于Restful Service的异常. - * - * @author calvin - */ -public class RestException extends RuntimeException { - - public HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR; - - public RestException() { - } - - public RestException(HttpStatus status) { - this.status = status; - } - - public RestException(String message) { - super(message); - } - - public RestException(HttpStatus status, String message) { - super(message); - this.status = status; - } -} diff --git a/examples/quickstart/src/main/java/org/springside/examples/quickstart/rest/RestExceptionHandler.java b/examples/quickstart/src/main/java/org/springside/examples/quickstart/rest/RestExceptionHandler.java deleted file mode 100644 index d7f00bf37..000000000 --- a/examples/quickstart/src/main/java/org/springside/examples/quickstart/rest/RestExceptionHandler.java +++ /dev/null @@ -1,50 +0,0 @@ -package org.springside.examples.quickstart.rest; - -import java.util.Map; - -import javax.validation.ConstraintViolationException; - -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.ControllerAdvice; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.context.request.WebRequest; -import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; -import org.springside.modules.beanvalidator.BeanValidators; -import org.springside.modules.mapper.JsonMapper; -import org.springside.modules.web.MediaTypes; - -/** - * 自定义ExceptionHandler,专门处理Restful异常. - * - * @author calvin - */ -// 会被Spring-MVC自动扫描,但又不属于Controller的annotation。 -@ControllerAdvice -public class RestExceptionHandler extends ResponseEntityExceptionHandler { - private JsonMapper jsonMapper = new JsonMapper(); - - /** - * 处理RestException. - */ - @ExceptionHandler(value = { RestException.class }) - public final ResponseEntity handleException(RestException ex, WebRequest request) { - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.parseMediaType(MediaTypes.TEXT_PLAIN_UTF_8)); - return handleExceptionInternal(ex, ex.getMessage(), headers, ex.status, request); - } - - /** - * 处理JSR311 Validation异常. - */ - @ExceptionHandler(value = { ConstraintViolationException.class }) - public final ResponseEntity handleException(ConstraintViolationException ex, WebRequest request) { - Map errors = BeanValidators.extractPropertyAndMessage(ex.getConstraintViolations()); - String body = jsonMapper.toJson(errors); - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.parseMediaType(MediaTypes.TEXT_PLAIN_UTF_8)); - return handleExceptionInternal(ex, body, headers, HttpStatus.BAD_REQUEST, request); - } -} diff --git a/examples/quickstart/src/main/java/org/springside/examples/quickstart/rest/TaskRestController.java b/examples/quickstart/src/main/java/org/springside/examples/quickstart/rest/TaskRestController.java deleted file mode 100644 index 9ad8a32e9..000000000 --- a/examples/quickstart/src/main/java/org/springside/examples/quickstart/rest/TaskRestController.java +++ /dev/null @@ -1,96 +0,0 @@ -package org.springside.examples.quickstart.rest; - -import java.net.URI; -import java.util.List; - -import javax.validation.Validator; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.bind.annotation.ResponseStatus; -import org.springframework.web.util.UriComponentsBuilder; -import org.springside.examples.quickstart.entity.Task; -import org.springside.examples.quickstart.service.task.TaskService; -import org.springside.modules.beanvalidator.BeanValidators; -import org.springside.modules.web.MediaTypes; - -/** - * Task的Restful API的Controller. - * - * @author calvin - */ -@Controller -@RequestMapping(value = "/api/v1/task") -public class TaskRestController { - - private static Logger logger = LoggerFactory.getLogger(TaskRestController.class); - - @Autowired - private TaskService taskService; - - @Autowired - private Validator validator; - - @RequestMapping(method = RequestMethod.GET, produces = MediaTypes.JSON_UTF_8) - @ResponseBody - public List list() { - return taskService.getAllTask(); - } - - @RequestMapping(value = "/{id}", method = RequestMethod.GET, produces = MediaTypes.JSON_UTF_8) - @ResponseBody - public Task get(@PathVariable("id") Long id) { - Task task = taskService.getTask(id); - if (task == null) { - String message = "任务不存在(id:" + id + ")"; - logger.warn(message); - throw new RestException(HttpStatus.NOT_FOUND, message); - } - return task; - } - - @RequestMapping(method = RequestMethod.POST, consumes = MediaTypes.JSON) - @ResponseBody - public ResponseEntity create(@RequestBody Task task, UriComponentsBuilder uriBuilder) { - // 调用JSR303 Bean Validator进行校验, 异常将由RestExceptionHandler统一处理. - BeanValidators.validateWithException(validator, task); - - // 保存任务 - taskService.saveTask(task); - - // 按照Restful风格约定,创建指向新任务的url, 也可以直接返回id或对象. - Long id = task.getId(); - URI uri = uriBuilder.path("/api/v1/task/" + id).build().toUri(); - HttpHeaders headers = new HttpHeaders(); - headers.setLocation(uri); - - return new ResponseEntity(headers, HttpStatus.CREATED); - } - - @RequestMapping(value = "/{id}", method = RequestMethod.PUT, consumes = MediaTypes.JSON) - public ResponseEntity update(@RequestBody Task task) { - // 调用JSR303 Bean Validator进行校验, 异常将由RestExceptionHandler统一处理. - BeanValidators.validateWithException(validator, task); - // 保存 - taskService.saveTask(task); - - // 按Restful约定,返回204状态码, 无内容. 也可以返回200状态码. - return new ResponseEntity(HttpStatus.NO_CONTENT); - } - - @RequestMapping(value = "/{id}", method = RequestMethod.DELETE) - @ResponseStatus(HttpStatus.NO_CONTENT) - public void delete(@PathVariable("id") Long id) { - taskService.deleteTask(id); - } -} diff --git a/examples/quickstart/src/main/java/org/springside/examples/quickstart/service/ServiceException.java b/examples/quickstart/src/main/java/org/springside/examples/quickstart/service/ServiceException.java deleted file mode 100644 index f852b73bc..000000000 --- a/examples/quickstart/src/main/java/org/springside/examples/quickstart/service/ServiceException.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.springside.examples.quickstart.service; - -/** - * Service层公用的Exception. - * - * 继承自RuntimeException, 从由Spring管理事务的函数中抛出时会触发事务回滚. - * - * @author calvin - */ -public class ServiceException extends RuntimeException { - - private static final long serialVersionUID = 3583566093089790852L; - - public ServiceException() { - super(); - } - - public ServiceException(String message) { - super(message); - } - - public ServiceException(Throwable cause) { - super(cause); - } - - public ServiceException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/examples/quickstart/src/main/java/org/springside/examples/quickstart/service/account/AccountService.java b/examples/quickstart/src/main/java/org/springside/examples/quickstart/service/account/AccountService.java deleted file mode 100644 index 6d1df0123..000000000 --- a/examples/quickstart/src/main/java/org/springside/examples/quickstart/service/account/AccountService.java +++ /dev/null @@ -1,117 +0,0 @@ -package org.springside.examples.quickstart.service.account; - -import java.util.List; - -import org.apache.commons.lang3.StringUtils; -import org.apache.shiro.SecurityUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; -import org.springframework.transaction.annotation.Transactional; -import org.springside.examples.quickstart.entity.User; -import org.springside.examples.quickstart.repository.TaskDao; -import org.springside.examples.quickstart.repository.UserDao; -import org.springside.examples.quickstart.service.ServiceException; -import org.springside.examples.quickstart.service.account.ShiroDbRealm.ShiroUser; -import org.springside.modules.security.utils.Digests; -import org.springside.modules.utils.Clock; -import org.springside.modules.utils.Encodes; - -/** - * 用户管理类. - * - * @author calvin - */ -// Spring Service Bean的标识. -@Component -@Transactional -public class AccountService { - - public static final String HASH_ALGORITHM = "SHA-1"; - public static final int HASH_INTERATIONS = 1024; - private static final int SALT_SIZE = 8; - - private static Logger logger = LoggerFactory.getLogger(AccountService.class); - - private UserDao userDao; - private TaskDao taskDao; - private Clock clock = Clock.DEFAULT; - - public List getAllUser() { - return (List) userDao.findAll(); - } - - public User getUser(Long id) { - return userDao.findOne(id); - } - - public User findUserByLoginName(String loginName) { - return userDao.findByLoginName(loginName); - } - - public void registerUser(User user) { - entryptPassword(user); - user.setRoles("user"); - user.setRegisterDate(clock.getCurrentDate()); - - userDao.save(user); - } - - public void updateUser(User user) { - if (StringUtils.isNotBlank(user.getPlainPassword())) { - entryptPassword(user); - } - userDao.save(user); - } - - public void deleteUser(Long id) { - if (isSupervisor(id)) { - logger.warn("操作员{}尝试删除超级管理员用户", getCurrentUserName()); - throw new ServiceException("不能删除超级管理员用户"); - } - userDao.delete(id); - taskDao.deleteByUserId(id); - - } - - /** - * 判断是否超级管理员. - */ - private boolean isSupervisor(Long id) { - return id == 1; - } - - /** - * 取出Shiro中的当前用户LoginName. - */ - private String getCurrentUserName() { - ShiroUser user = (ShiroUser) SecurityUtils.getSubject().getPrincipal(); - return user.loginName; - } - - /** - * 设定安全的密码,生成随机的salt并经过1024次 sha-1 hash - */ - private void entryptPassword(User user) { - byte[] salt = Digests.generateSalt(SALT_SIZE); - user.setSalt(Encodes.encodeHex(salt)); - - byte[] hashPassword = Digests.sha1(user.getPlainPassword().getBytes(), salt, HASH_INTERATIONS); - user.setPassword(Encodes.encodeHex(hashPassword)); - } - - @Autowired - public void setUserDao(UserDao userDao) { - this.userDao = userDao; - } - - @Autowired - public void setTaskDao(TaskDao taskDao) { - this.taskDao = taskDao; - } - - public void setClock(Clock clock) { - this.clock = clock; - } -} diff --git a/examples/quickstart/src/main/java/org/springside/examples/quickstart/service/account/ShiroDbRealm.java b/examples/quickstart/src/main/java/org/springside/examples/quickstart/service/account/ShiroDbRealm.java deleted file mode 100644 index f07cebe60..000000000 --- a/examples/quickstart/src/main/java/org/springside/examples/quickstart/service/account/ShiroDbRealm.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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.springside.examples.quickstart.service.account; - -import java.io.Serializable; - -import javax.annotation.PostConstruct; - -import org.apache.shiro.authc.AuthenticationException; -import org.apache.shiro.authc.AuthenticationInfo; -import org.apache.shiro.authc.AuthenticationToken; -import org.apache.shiro.authc.SimpleAuthenticationInfo; -import org.apache.shiro.authc.UsernamePasswordToken; -import org.apache.shiro.authc.credential.HashedCredentialsMatcher; -import org.apache.shiro.authz.AuthorizationInfo; -import org.apache.shiro.authz.SimpleAuthorizationInfo; -import org.apache.shiro.realm.AuthorizingRealm; -import org.apache.shiro.subject.PrincipalCollection; -import org.apache.shiro.util.ByteSource; -import org.springside.examples.quickstart.entity.User; -import org.springside.modules.utils.Encodes; - -import com.google.common.base.Objects; - -public class ShiroDbRealm extends AuthorizingRealm { - - protected AccountService accountService; - - /** - * 认证回调函数,登录时调用. - */ - @Override - protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException { - UsernamePasswordToken token = (UsernamePasswordToken) authcToken; - User user = accountService.findUserByLoginName(token.getUsername()); - if (user != null) { - byte[] salt = Encodes.decodeHex(user.getSalt()); - return new SimpleAuthenticationInfo(new ShiroUser(user.getId(), user.getLoginName(), user.getName()), - user.getPassword(), ByteSource.Util.bytes(salt), getName()); - } else { - return null; - } - } - - /** - * 授权查询回调函数, 进行鉴权但缓存中无用户的授权信息时调用. - */ - @Override - protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { - ShiroUser shiroUser = (ShiroUser) principals.getPrimaryPrincipal(); - User user = accountService.findUserByLoginName(shiroUser.loginName); - SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); - info.addRoles(user.getRoleList()); - return info; - } - - /** - * 设定Password校验的Hash算法与迭代次数. - */ - @PostConstruct - public void initCredentialsMatcher() { - HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(AccountService.HASH_ALGORITHM); - matcher.setHashIterations(AccountService.HASH_INTERATIONS); - - setCredentialsMatcher(matcher); - } - - public void setAccountService(AccountService accountService) { - this.accountService = accountService; - } - - /** - * 自定义Authentication对象,使得Subject除了携带用户的登录名外还可以携带更多信息. - */ - public static class ShiroUser implements Serializable { - private static final long serialVersionUID = -1373760761780840081L; - public Long id; - public String loginName; - public String name; - - public ShiroUser(Long id, String loginName, String name) { - this.id = id; - this.loginName = loginName; - this.name = name; - } - - public String getName() { - return name; - } - - /** - * 本函数输出将作为默认的输出. - */ - @Override - public String toString() { - return loginName; - } - - /** - * 重载hashCode,只计算loginName; - */ - @Override - public int hashCode() { - return Objects.hashCode(loginName); - } - - /** - * 重载equals,只计算loginName; - */ - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - ShiroUser other = (ShiroUser) obj; - if (loginName == null) { - if (other.loginName != null) { - return false; - } - } else if (!loginName.equals(other.loginName)) { - return false; - } - return true; - } - } -} diff --git a/examples/quickstart/src/main/java/org/springside/examples/quickstart/service/task/TaskService.java b/examples/quickstart/src/main/java/org/springside/examples/quickstart/service/task/TaskService.java deleted file mode 100644 index 56fa7ba24..000000000 --- a/examples/quickstart/src/main/java/org/springside/examples/quickstart/service/task/TaskService.java +++ /dev/null @@ -1,80 +0,0 @@ -package org.springside.examples.quickstart.service.task; - -import java.util.List; -import java.util.Map; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Sort; -import org.springframework.data.domain.Sort.Direction; -import org.springframework.data.jpa.domain.Specification; -import org.springframework.stereotype.Component; -import org.springframework.transaction.annotation.Transactional; -import org.springside.examples.quickstart.entity.Task; -import org.springside.examples.quickstart.repository.TaskDao; -import org.springside.modules.persistence.DynamicSpecifications; -import org.springside.modules.persistence.SearchFilter; -import org.springside.modules.persistence.SearchFilter.Operator; - -//Spring Bean的标识. -@Component -// 默认将类中的所有public函数纳入事务管理. -@Transactional -public class TaskService { - - private TaskDao taskDao; - - public Task getTask(Long id) { - return taskDao.findOne(id); - } - - public void saveTask(Task entity) { - taskDao.save(entity); - } - - public void deleteTask(Long id) { - taskDao.delete(id); - } - - public List getAllTask() { - return (List) taskDao.findAll(); - } - - public Page getUserTask(Long userId, Map searchParams, int pageNumber, int pageSize, - String sortType) { - PageRequest pageRequest = buildPageRequest(pageNumber, pageSize, sortType); - Specification spec = buildSpecification(userId, searchParams); - - return taskDao.findAll(spec, pageRequest); - } - - /** - * 创建分页请求. - */ - private PageRequest buildPageRequest(int pageNumber, int pagzSize, String sortType) { - Sort sort = null; - if ("auto".equals(sortType)) { - sort = new Sort(Direction.DESC, "id"); - } else if ("title".equals(sortType)) { - sort = new Sort(Direction.ASC, "title"); - } - - return new PageRequest(pageNumber - 1, pagzSize, sort); - } - - /** - * 创建动态查询条件组合. - */ - private Specification buildSpecification(Long userId, Map searchParams) { - Map filters = SearchFilter.parse(searchParams); - filters.put("user.id", new SearchFilter("user.id", Operator.EQ, userId)); - Specification spec = DynamicSpecifications.bySearchFilter(filters.values(), Task.class); - return spec; - } - - @Autowired - public void setTaskDao(TaskDao taskDao) { - this.taskDao = taskDao; - } -} diff --git a/examples/quickstart/src/main/java/org/springside/examples/quickstart/web/account/LoginController.java b/examples/quickstart/src/main/java/org/springside/examples/quickstart/web/account/LoginController.java deleted file mode 100644 index a2d219ae9..000000000 --- a/examples/quickstart/src/main/java/org/springside/examples/quickstart/web/account/LoginController.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.springside.examples.quickstart.web.account; - -import org.apache.shiro.web.filter.authc.FormAuthenticationFilter; -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RequestParam; - -/** - * LoginController负责打开登录页面(GET请求)和登录出错页面(POST请求), - * - * 真正登录的POST请求由Filter完成, - * - * @author calvin - */ -@Controller -@RequestMapping(value = "/login") -public class LoginController { - - @RequestMapping(method = RequestMethod.GET) - public String login() { - return "account/login"; - } - - @RequestMapping(method = RequestMethod.POST) - public String fail(@RequestParam(FormAuthenticationFilter.DEFAULT_USERNAME_PARAM) String userName, Model model) { - model.addAttribute(FormAuthenticationFilter.DEFAULT_USERNAME_PARAM, userName); - return "account/login"; - } - -} diff --git a/examples/quickstart/src/main/java/org/springside/examples/quickstart/web/account/ProfileController.java b/examples/quickstart/src/main/java/org/springside/examples/quickstart/web/account/ProfileController.java deleted file mode 100644 index 98c737d6e..000000000 --- a/examples/quickstart/src/main/java/org/springside/examples/quickstart/web/account/ProfileController.java +++ /dev/null @@ -1,69 +0,0 @@ -package org.springside.examples.quickstart.web.account; - -import javax.validation.Valid; - -import org.apache.shiro.SecurityUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.ModelAttribute; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RequestParam; -import org.springside.examples.quickstart.entity.User; -import org.springside.examples.quickstart.service.account.AccountService; -import org.springside.examples.quickstart.service.account.ShiroDbRealm.ShiroUser; - -/** - * 用户修改自己资料的Controller. - * - * @author calvin - */ -@Controller -@RequestMapping(value = "/profile") -public class ProfileController { - - @Autowired - private AccountService accountService; - - @RequestMapping(method = RequestMethod.GET) - public String updateForm(Model model) { - Long id = getCurrentUserId(); - model.addAttribute("user", accountService.getUser(id)); - return "account/profile"; - } - - @RequestMapping(method = RequestMethod.POST) - public String update(@Valid @ModelAttribute("user") User user) { - accountService.updateUser(user); - updateCurrentUserName(user.getName()); - return "redirect:/"; - } - - /** - * 所有RequestMapping方法调用前的Model准备方法, 实现Struts2 Preparable二次部分绑定的效果,先根据form的id从数据库查出User对象,再把Form提交的内容绑定到该对象上。 - * 因为仅update()方法的form中有id属性,因此仅在update时实际执行. - */ - @ModelAttribute - public void getUser(@RequestParam(value = "id", defaultValue = "-1") Long id, Model model) { - if (id != -1) { - model.addAttribute("user", accountService.getUser(id)); - } - } - - /** - * 取出Shiro中的当前用户Id. - */ - private Long getCurrentUserId() { - ShiroUser user = (ShiroUser) SecurityUtils.getSubject().getPrincipal(); - return user.id; - } - - /** - * 更新Shiro中当前用户的用户名. - */ - private void updateCurrentUserName(String userName) { - ShiroUser user = (ShiroUser) SecurityUtils.getSubject().getPrincipal(); - user.name = userName; - } -} diff --git a/examples/quickstart/src/main/java/org/springside/examples/quickstart/web/account/RegisterController.java b/examples/quickstart/src/main/java/org/springside/examples/quickstart/web/account/RegisterController.java deleted file mode 100644 index 8351a6a20..000000000 --- a/examples/quickstart/src/main/java/org/springside/examples/quickstart/web/account/RegisterController.java +++ /dev/null @@ -1,51 +0,0 @@ -package org.springside.examples.quickstart.web.account; - -import javax.validation.Valid; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.servlet.mvc.support.RedirectAttributes; -import org.springside.examples.quickstart.entity.User; -import org.springside.examples.quickstart.service.account.AccountService; - -/** - * 用户注册的Controller. - * - * @author calvin - */ -@Controller -@RequestMapping(value = "/register") -public class RegisterController { - - @Autowired - private AccountService accountService; - - @RequestMapping(method = RequestMethod.GET) - public String registerForm() { - return "account/register"; - } - - @RequestMapping(method = RequestMethod.POST) - public String register(@Valid User user, RedirectAttributes redirectAttributes) { - accountService.registerUser(user); - redirectAttributes.addFlashAttribute("username", user.getLoginName()); - return "redirect:/login"; - } - - /** - * Ajax请求校验loginName是否唯一。 - */ - @RequestMapping(value = "checkLoginName") - @ResponseBody - public String checkLoginName(@RequestParam("loginName") String loginName) { - if (accountService.findUserByLoginName(loginName) == null) { - return "true"; - } else { - return "false"; - } - } -} diff --git a/examples/quickstart/src/main/java/org/springside/examples/quickstart/web/account/UserAdminController.java b/examples/quickstart/src/main/java/org/springside/examples/quickstart/web/account/UserAdminController.java deleted file mode 100644 index dc883e62f..000000000 --- a/examples/quickstart/src/main/java/org/springside/examples/quickstart/web/account/UserAdminController.java +++ /dev/null @@ -1,70 +0,0 @@ -package org.springside.examples.quickstart.web.account; - -import java.util.List; - -import javax.validation.Valid; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.ModelAttribute; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.servlet.mvc.support.RedirectAttributes; -import org.springside.examples.quickstart.entity.User; -import org.springside.examples.quickstart.service.account.AccountService; - -/** - * 管理员管理用户的Controller. - * - * @author calvin - */ -@Controller -@RequestMapping(value = "/admin/user") -public class UserAdminController { - - @Autowired - private AccountService accountService; - - @RequestMapping(method = RequestMethod.GET) - public String list(Model model) { - List users = accountService.getAllUser(); - model.addAttribute("users", users); - - return "account/adminUserList"; - } - - @RequestMapping(value = "update/{id}", method = RequestMethod.GET) - public String updateForm(@PathVariable("id") Long id, Model model) { - model.addAttribute("user", accountService.getUser(id)); - return "account/adminUserForm"; - } - - @RequestMapping(value = "update", method = RequestMethod.POST) - public String update(@Valid @ModelAttribute("user") User user, RedirectAttributes redirectAttributes) { - accountService.updateUser(user); - redirectAttributes.addFlashAttribute("message", "更新用户" + user.getLoginName() + "成功"); - return "redirect:/admin/user"; - } - - @RequestMapping(value = "delete/{id}") - public String delete(@PathVariable("id") Long id, RedirectAttributes redirectAttributes) { - User user = accountService.getUser(id); - accountService.deleteUser(id); - redirectAttributes.addFlashAttribute("message", "删除用户" + user.getLoginName() + "成功"); - return "redirect:/admin/user"; - } - - /** - * 所有RequestMapping方法调用前的Model准备方法, 实现Struts2 Preparable二次部分绑定的效果,先根据form的id从数据库查出User对象,再把Form提交的内容绑定到该对象上。 - * 因为仅update()方法的form中有id属性,因此仅在update时实际执行. - */ - @ModelAttribute - public void getUser(@RequestParam(value = "id", defaultValue = "-1") Long id, Model model) { - if (id != -1) { - model.addAttribute("user", accountService.getUser(id)); - } - } -} diff --git a/examples/quickstart/src/main/java/org/springside/examples/quickstart/web/api/ApiListController.java b/examples/quickstart/src/main/java/org/springside/examples/quickstart/web/api/ApiListController.java deleted file mode 100644 index ba85aad87..000000000 --- a/examples/quickstart/src/main/java/org/springside/examples/quickstart/web/api/ApiListController.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.springside.examples.quickstart.web.api; - -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; - -@Controller -@RequestMapping(value = "/api") -public class ApiListController { - @RequestMapping(method = RequestMethod.GET) - public String list() { - return "api/list"; - } -} diff --git a/examples/quickstart/src/main/java/org/springside/examples/quickstart/web/task/TaskController.java b/examples/quickstart/src/main/java/org/springside/examples/quickstart/web/task/TaskController.java deleted file mode 100644 index 4630de57a..000000000 --- a/examples/quickstart/src/main/java/org/springside/examples/quickstart/web/task/TaskController.java +++ /dev/null @@ -1,129 +0,0 @@ -package org.springside.examples.quickstart.web.task; - -import java.util.Map; - -import javax.servlet.ServletRequest; -import javax.validation.Valid; - -import org.apache.shiro.SecurityUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Page; -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.ModelAttribute; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.servlet.mvc.support.RedirectAttributes; -import org.springside.examples.quickstart.entity.Task; -import org.springside.examples.quickstart.entity.User; -import org.springside.examples.quickstart.service.account.ShiroDbRealm.ShiroUser; -import org.springside.examples.quickstart.service.task.TaskService; -import org.springside.modules.web.Servlets; - -import com.google.common.collect.Maps; - -/** - * Task管理的Controller, 使用Restful风格的Urls: - * - * List page : GET /task/ - * Create page : GET /task/create - * Create action : POST /task/create - * Update page : GET /task/update/{id} - * Update action : POST /task/update - * Delete action : GET /task/delete/{id} - * - * @author calvin - */ -@Controller -@RequestMapping(value = "/task") -public class TaskController { - - private static final String PAGE_SIZE = "3"; - - private static Map sortTypes = Maps.newLinkedHashMap(); - static { - sortTypes.put("auto", "自动"); - sortTypes.put("title", "标题"); - } - - @Autowired - private TaskService taskService; - - @RequestMapping(method = RequestMethod.GET) - public String list(@RequestParam(value = "page", defaultValue = "1") int pageNumber, - @RequestParam(value = "page.size", defaultValue = PAGE_SIZE) int pageSize, - @RequestParam(value = "sortType", defaultValue = "auto") String sortType, Model model, - ServletRequest request) { - Map searchParams = Servlets.getParametersStartingWith(request, "search_"); - Long userId = getCurrentUserId(); - - Page tasks = taskService.getUserTask(userId, searchParams, pageNumber, pageSize, sortType); - - model.addAttribute("tasks", tasks); - model.addAttribute("sortType", sortType); - model.addAttribute("sortTypes", sortTypes); - // 将搜索条件编码成字符串,用于排序,分页的URL - model.addAttribute("searchParams", Servlets.encodeParameterStringWithPrefix(searchParams, "search_")); - - return "task/taskList"; - } - - @RequestMapping(value = "create", method = RequestMethod.GET) - public String createForm(Model model) { - model.addAttribute("task", new Task()); - model.addAttribute("action", "create"); - return "task/taskForm"; - } - - @RequestMapping(value = "create", method = RequestMethod.POST) - public String create(@Valid Task newTask, RedirectAttributes redirectAttributes) { - User user = new User(getCurrentUserId()); - newTask.setUser(user); - - taskService.saveTask(newTask); - redirectAttributes.addFlashAttribute("message", "创建任务成功"); - return "redirect:/task/"; - } - - @RequestMapping(value = "update/{id}", method = RequestMethod.GET) - public String updateForm(@PathVariable("id") Long id, Model model) { - model.addAttribute("task", taskService.getTask(id)); - model.addAttribute("action", "update"); - return "task/taskForm"; - } - - @RequestMapping(value = "update", method = RequestMethod.POST) - public String update(@Valid @ModelAttribute("task") Task task, RedirectAttributes redirectAttributes) { - taskService.saveTask(task); - redirectAttributes.addFlashAttribute("message", "更新任务成功"); - return "redirect:/task/"; - } - - @RequestMapping(value = "delete/{id}") - public String delete(@PathVariable("id") Long id, RedirectAttributes redirectAttributes) { - taskService.deleteTask(id); - redirectAttributes.addFlashAttribute("message", "删除任务成功"); - return "redirect:/task/"; - } - - /** - * 所有RequestMapping方法调用前的Model准备方法, 实现Struts2 Preparable二次部分绑定的效果,先根据form的id从数据库查出Task对象,再把Form提交的内容绑定到该对象上。 - * 因为仅update()方法的form中有id属性,因此仅在update时实际执行. - */ - @ModelAttribute - public void getTask(@RequestParam(value = "id", defaultValue = "-1") Long id, Model model) { - if (id != -1) { - model.addAttribute("task", taskService.getTask(id)); - } - } - - /** - * 取出Shiro中的当前用户Id. - */ - private Long getCurrentUserId() { - ShiroUser user = (ShiroUser) SecurityUtils.getSubject().getPrincipal(); - return user.id; - } -} diff --git a/examples/quickstart/src/main/resources/application.properties b/examples/quickstart/src/main/resources/application.properties deleted file mode 100644 index fad6d7b23..000000000 --- a/examples/quickstart/src/main/resources/application.properties +++ /dev/null @@ -1,25 +0,0 @@ -#h2 database settings -jdbc.driver=org.h2.Driver -jdbc.url=jdbc:h2:file:~/.h2/quickstart;AUTO_SERVER=TRUE;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE -jdbc.username=sa -jdbc.password= - -#log4jdbc driver -#jdbc.driver=net.sf.log4jdbc.DriverSpy -#jdbc.url=jdbc:log4jdbc:h2:file:~/.h2/quickstart;AUTO_SERVER=TRUE;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE - -#oracle database settings -#jdbc.driver=oracle.jdbc.driver.OracleDriver -#jdbc.url=jdbc:oracle:thin:@127.0.0.1:1521:XE -#jdbc.username=quickstart -#jdbc.password=quickstart - -#mysql database setting -#jdbc.driver=com.mysql.jdbc.Driver -#jdbc.url=jdbc:mysql://localhost/quickstart?useUnicode=true&characterEncoding=utf-8 -#jdbc.username=root -#jdbc.password= - -#connection pool settings -jdbc.pool.maxIdle=10 -jdbc.pool.maxActive=50 diff --git a/examples/quickstart/src/main/resources/applicationContext-shiro.xml b/examples/quickstart/src/main/resources/applicationContext-shiro.xml deleted file mode 100644 index 5d0da1e90..000000000 --- a/examples/quickstart/src/main/resources/applicationContext-shiro.xml +++ /dev/null @@ -1,44 +0,0 @@ - - - - Shiro安全配置 - - - - - - - - - - - - - - - - - - - - /login = authc - /logout = logout - /static/** = anon - /api/** = anon - /register/** = anon - /admin/** = roles[admin] - /** = user - - - - - - - - - - - - \ No newline at end of file diff --git a/examples/quickstart/src/main/resources/applicationContext.xml b/examples/quickstart/src/main/resources/applicationContext.xml deleted file mode 100644 index c6150a2fe..000000000 --- a/examples/quickstart/src/main/resources/applicationContext.xml +++ /dev/null @@ -1,142 +0,0 @@ - - - - Spring公共配置 - - - - - - - - - - - - - - - - org.hibernate.cfg.ImprovedNamingStrategy - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/examples/quickstart/src/main/resources/ehcache/ehcache-shiro.xml b/examples/quickstart/src/main/resources/ehcache/ehcache-shiro.xml deleted file mode 100644 index 48b5c7a7d..000000000 --- a/examples/quickstart/src/main/resources/ehcache/ehcache-shiro.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - \ No newline at end of file diff --git a/examples/quickstart/src/main/resources/log4jdbc.properties b/examples/quickstart/src/main/resources/log4jdbc.properties deleted file mode 100644 index 18fab6316..000000000 --- a/examples/quickstart/src/main/resources/log4jdbc.properties +++ /dev/null @@ -1,7 +0,0 @@ -#Optional properties -#log4jdbc.debug.stack.prefix= -#log4jdbc.sqltiming.warn.threshold= -#log4jdbc.dump.sql.select=false -#log4jdbc.dump.sql.insert=false -#log4jdbc.dump.sql.update=false -#log4jdbc.dump.sql.delete=false \ No newline at end of file diff --git a/examples/quickstart/src/main/resources/logback.xml b/examples/quickstart/src/main/resources/logback.xml deleted file mode 100644 index cab7095ac..000000000 --- a/examples/quickstart/src/main/resources/logback.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - - - - - logs/quickstart.log - - logs/quickstart.%d{yyyy-MM-dd}.log - - - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - - - - - - - - - - - - - - \ No newline at end of file diff --git a/examples/quickstart/src/main/resources/sql/h2/schema-with-oracle.sql b/examples/quickstart/src/main/resources/sql/h2/schema-with-oracle.sql deleted file mode 100644 index ec61ca1a8..000000000 --- a/examples/quickstart/src/main/resources/sql/h2/schema-with-oracle.sql +++ /dev/null @@ -1,25 +0,0 @@ -drop table if exists ss_task; -drop table if exists ss_user; - -create table ss_task ( - id bigint, - title varchar(128) not null, - description varchar(255), - user_id bigint not null, - primary key (id) -); - -create table ss_user ( - id bigint, - login_name varchar(64) not null unique, - name varchar(64) not null, - password varchar(255) not null, - salt varchar(64) not null, - roles varchar(255) not null, - register_date timestamp not null, - primary key (id) -); - - -create sequence ss_seq_task start with 100 increment by 20; -create sequence ss_seq_user start with 100 increment by 20; \ No newline at end of file diff --git a/examples/quickstart/src/main/resources/sql/h2/schema.sql b/examples/quickstart/src/main/resources/sql/h2/schema.sql deleted file mode 100644 index dc7381ed1..000000000 --- a/examples/quickstart/src/main/resources/sql/h2/schema.sql +++ /dev/null @@ -1,21 +0,0 @@ -drop table if exists ss_task; -drop table if exists ss_user; - -create table ss_task ( - id bigint generated by default as identity, - title varchar(128) not null, - description varchar(255), - user_id bigint not null, - primary key (id) -); - -create table ss_user ( - id bigint generated by default as identity, - login_name varchar(64) not null unique, - name varchar(64) not null, - password varchar(255) not null, - salt varchar(64) not null, - roles varchar(255) not null, - register_date timestamp not null, - primary key (id) -); \ No newline at end of file diff --git a/examples/quickstart/src/main/resources/sql/mysql/schema.sql b/examples/quickstart/src/main/resources/sql/mysql/schema.sql deleted file mode 100644 index 691abea27..000000000 --- a/examples/quickstart/src/main/resources/sql/mysql/schema.sql +++ /dev/null @@ -1,21 +0,0 @@ -drop table if exists ss_task; -drop table if exists ss_user; - -create table ss_task ( - id bigint auto_increment, - title varchar(128) not null, - description varchar(255), - user_id bigint not null, - primary key (id) -) engine=InnoDB; - -create table ss_user ( - id bigint auto_increment, - login_name varchar(64) not null unique, - name varchar(64) not null, - password varchar(255) not null, - salt varchar(64) not null, - roles varchar(255) not null, - register_date timestamp not null default 0, - primary key (id) -) engine=InnoDB; \ No newline at end of file diff --git a/examples/quickstart/src/main/resources/sql/oracle/schema.sql b/examples/quickstart/src/main/resources/sql/oracle/schema.sql deleted file mode 100644 index c7fa99234..000000000 --- a/examples/quickstart/src/main/resources/sql/oracle/schema.sql +++ /dev/null @@ -1,25 +0,0 @@ -drop table ss_task; -drop table ss_user; - -create table ss_task ( - id number(19,0), - title varchar2(128) not null, - description varchar2(255), - user_id bigint not null, - primary key (id) -); - -create table ss_user ( - id number(19,0), - login_name varchar2(64) not null unique, - name varchar2(64) not null, - password varchar2(255) not null, - salt varchar2(64) not null, - roles varchar2(255) not null, - regiser_date date not null, - primary key (id) -); - - -create sequence ss_seq_task start with 100 increment by 20; -create sequence ss_seq_user start with 100 increment by 20; \ No newline at end of file diff --git a/examples/quickstart/src/main/webapp/WEB-INF/decorators.xml b/examples/quickstart/src/main/webapp/WEB-INF/decorators.xml deleted file mode 100644 index 5f9f1051b..000000000 --- a/examples/quickstart/src/main/webapp/WEB-INF/decorators.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - /static/* - /api/* - - - - /* - - diff --git a/examples/quickstart/src/main/webapp/WEB-INF/layouts/default.jsp b/examples/quickstart/src/main/webapp/WEB-INF/layouts/default.jsp deleted file mode 100644 index 63a88b23e..000000000 --- a/examples/quickstart/src/main/webapp/WEB-INF/layouts/default.jsp +++ /dev/null @@ -1,38 +0,0 @@ -<%@ page contentType="text/html;charset=UTF-8"%> -<%@ taglib prefix="sitemesh" uri="http://www.opensymphony.com/sitemesh/decorator" %> -<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> - - - - - - -QuickStart示例:<sitemesh:title/> - - - - - - - - - - - - - - - - - - -
- <%@ include file="/WEB-INF/layouts/header.jsp"%> -
- -
- <%@ include file="/WEB-INF/layouts/footer.jsp"%> -
- - - \ No newline at end of file diff --git a/examples/quickstart/src/main/webapp/WEB-INF/layouts/footer.jsp b/examples/quickstart/src/main/webapp/WEB-INF/layouts/footer.jsp deleted file mode 100644 index d29943e49..000000000 --- a/examples/quickstart/src/main/webapp/WEB-INF/layouts/footer.jsp +++ /dev/null @@ -1,5 +0,0 @@ -<%@ page language="java" pageEncoding="UTF-8" %> - - diff --git a/examples/quickstart/src/main/webapp/WEB-INF/layouts/header.jsp b/examples/quickstart/src/main/webapp/WEB-INF/layouts/header.jsp deleted file mode 100644 index bf0c6bec0..000000000 --- a/examples/quickstart/src/main/webapp/WEB-INF/layouts/header.jsp +++ /dev/null @@ -1,28 +0,0 @@ -<%@ page language="java" pageEncoding="UTF-8" %> -<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> -<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %> - - \ No newline at end of file diff --git a/examples/quickstart/src/main/webapp/WEB-INF/spring-mvc.xml b/examples/quickstart/src/main/webapp/WEB-INF/spring-mvc.xml deleted file mode 100644 index 76b637458..000000000 --- a/examples/quickstart/src/main/webapp/WEB-INF/spring-mvc.xml +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - error/500 - - - - diff --git a/examples/quickstart/src/main/webapp/WEB-INF/tags/pagination.tag b/examples/quickstart/src/main/webapp/WEB-INF/tags/pagination.tag deleted file mode 100644 index 4e3ad4eb9..000000000 --- a/examples/quickstart/src/main/webapp/WEB-INF/tags/pagination.tag +++ /dev/null @@ -1,48 +0,0 @@ -<%@tag pageEncoding="UTF-8"%> -<%@ attribute name="page" type="org.springframework.data.domain.Page" required="true"%> -<%@ attribute name="paginationSize" type="java.lang.Integer" required="true"%> - -<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> - -<% -int current = page.getNumber() + 1; -int begin = Math.max(1, current - paginationSize/2); -int end = Math.min(begin + (paginationSize - 1), page.getTotalPages()); - -request.setAttribute("current", current); -request.setAttribute("begin", begin); -request.setAttribute("end", end); -%> - - - diff --git a/examples/quickstart/src/main/webapp/WEB-INF/tags/sort.tag b/examples/quickstart/src/main/webapp/WEB-INF/tags/sort.tag deleted file mode 100644 index 5f2838dbb..000000000 --- a/examples/quickstart/src/main/webapp/WEB-INF/tags/sort.tag +++ /dev/null @@ -1,13 +0,0 @@ -<%@tag pageEncoding="UTF-8"%> - -<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> - \ No newline at end of file diff --git a/examples/quickstart/src/main/webapp/WEB-INF/views/account/adminUserForm.jsp b/examples/quickstart/src/main/webapp/WEB-INF/views/account/adminUserForm.jsp deleted file mode 100644 index e96072420..000000000 --- a/examples/quickstart/src/main/webapp/WEB-INF/views/account/adminUserForm.jsp +++ /dev/null @@ -1,62 +0,0 @@ -<%@ page contentType="text/html;charset=UTF-8" %> -<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> -<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> - - - - - 用户管理 - - - -
- -
- 用户管理 -
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
-   - -
-
-
- - - - diff --git a/examples/quickstart/src/main/webapp/WEB-INF/views/account/adminUserList.jsp b/examples/quickstart/src/main/webapp/WEB-INF/views/account/adminUserList.jsp deleted file mode 100644 index cb3f847af..000000000 --- a/examples/quickstart/src/main/webapp/WEB-INF/views/account/adminUserList.jsp +++ /dev/null @@ -1,32 +0,0 @@ -<%@ page contentType="text/html;charset=UTF-8" %> -<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> -<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> - - - - - 用户管理 - - - - -
${message}
-
- - - - - - - - - - - - - -
登录名用户名注册时间管理
${user.loginName}${user.name} - - 删除
- - diff --git a/examples/quickstart/src/main/webapp/WEB-INF/views/account/login.jsp b/examples/quickstart/src/main/webapp/WEB-INF/views/account/login.jsp deleted file mode 100644 index d29eaf459..000000000 --- a/examples/quickstart/src/main/webapp/WEB-INF/views/account/login.jsp +++ /dev/null @@ -1,53 +0,0 @@ -<%@ page contentType="text/html;charset=UTF-8" %> -<%@ page import="org.apache.shiro.web.filter.authc.FormAuthenticationFilter"%> -<%@ page import="org.apache.shiro.authc.ExcessiveAttemptsException"%> -<%@ page import="org.apache.shiro.authc.IncorrectCredentialsException"%> -<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> - - - - - 登录页 - - - -
- <% - String error = (String) request.getAttribute(FormAuthenticationFilter.DEFAULT_ERROR_KEY_ATTRIBUTE_NAME); - if(error != null){ - %> -
- 登录失败,请重试. -
- <% - } - %> -
- -
- -
-
-
- -
- -
-
- -
-
- - 注册 - (管理员: admin/admin, 普通用户: user/user) -
-
-
- - - - diff --git a/examples/quickstart/src/main/webapp/WEB-INF/views/account/profile.jsp b/examples/quickstart/src/main/webapp/WEB-INF/views/account/profile.jsp deleted file mode 100644 index 22b5e3932..000000000 --- a/examples/quickstart/src/main/webapp/WEB-INF/views/account/profile.jsp +++ /dev/null @@ -1,49 +0,0 @@ -<%@ page contentType="text/html;charset=UTF-8" %> -<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> - - - - - 资料修改 - - - -
- -
- 资料修改 -
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
-   - -
-
-
- - - - diff --git a/examples/quickstart/src/main/webapp/WEB-INF/views/account/register.jsp b/examples/quickstart/src/main/webapp/WEB-INF/views/account/register.jsp deleted file mode 100644 index 90d45ac51..000000000 --- a/examples/quickstart/src/main/webapp/WEB-INF/views/account/register.jsp +++ /dev/null @@ -1,65 +0,0 @@ -<%@ page contentType="text/html;charset=UTF-8" %> -<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> - - - - - 用户注册 - - - - - -
-
- 用户注册 -
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
-   - -
-
-
- - diff --git a/examples/quickstart/src/main/webapp/WEB-INF/views/api/list.jsp b/examples/quickstart/src/main/webapp/WEB-INF/views/api/list.jsp deleted file mode 100644 index 9d557cb07..000000000 --- a/examples/quickstart/src/main/webapp/WEB-INF/views/api/list.jsp +++ /dev/null @@ -1,25 +0,0 @@ -<%@ page contentType="text/html;charset=UTF-8" %> -<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> - - - - - Restful API 列表 - - - - -

Restful API 列表

-

查询 API

- - -

修改API

-
    -
  • 创建任务 :http://${pageContext.request.serverName}:${pageContext.request.serverPort}${pageContext.request.contextPath}/api/v1/task method=Post, consumes=JSON
  • -
  • 修改任务(id=1) :http://${pageContext.request.serverName}:${pageContext.request.serverPort}${pageContext.request.contextPath}/api/v1/task/1 method=Put, consumes=JSON
  • -
- - diff --git a/examples/quickstart/src/main/webapp/WEB-INF/views/error/404.jsp b/examples/quickstart/src/main/webapp/WEB-INF/views/error/404.jsp deleted file mode 100644 index 033d7a180..000000000 --- a/examples/quickstart/src/main/webapp/WEB-INF/views/error/404.jsp +++ /dev/null @@ -1,16 +0,0 @@ -<%@ page contentType="text/html;charset=UTF-8"%> -<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> - -<%response.setStatus(200);%> - - - - - 404 - 页面不存在 - - - -

404 - 页面不存在.

-

">返回首页

- - \ No newline at end of file diff --git a/examples/quickstart/src/main/webapp/WEB-INF/views/error/500.jsp b/examples/quickstart/src/main/webapp/WEB-INF/views/error/500.jsp deleted file mode 100644 index 81d28de85..000000000 --- a/examples/quickstart/src/main/webapp/WEB-INF/views/error/500.jsp +++ /dev/null @@ -1,21 +0,0 @@ -<%@ page contentType="text/html;charset=UTF-8" isErrorPage="true" %> -<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> -<%@ page import="org.slf4j.Logger,org.slf4j.LoggerFactory" %> -<% - //设置返回码200,避免浏览器自带的错误页面 - response.setStatus(200); - //记录日志 - Logger logger = LoggerFactory.getLogger("500.jsp"); - logger.error(exception.getMessage(), exception); -%> - - - - - 500 - 系统内部错误 - - - -

500 - 系统发生内部错误.

- - diff --git a/examples/quickstart/src/main/webapp/WEB-INF/views/task/taskForm.jsp b/examples/quickstart/src/main/webapp/WEB-INF/views/task/taskForm.jsp deleted file mode 100644 index 6f92f0fd2..000000000 --- a/examples/quickstart/src/main/webapp/WEB-INF/views/task/taskForm.jsp +++ /dev/null @@ -1,41 +0,0 @@ -<%@ page contentType="text/html;charset=UTF-8" %> -<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> - - - - 任务管理 - - - -
- -
- 管理任务 -
- -
- -
-
-
- -
- -
-
-
-   - -
-
-
- - - diff --git a/examples/quickstart/src/main/webapp/WEB-INF/views/task/taskList.jsp b/examples/quickstart/src/main/webapp/WEB-INF/views/task/taskList.jsp deleted file mode 100644 index caeb2dc18..000000000 --- a/examples/quickstart/src/main/webapp/WEB-INF/views/task/taskList.jsp +++ /dev/null @@ -1,41 +0,0 @@ -<%@ page contentType="text/html;charset=UTF-8" %> -<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> -<%@ taglib prefix="tags" tagdir="/WEB-INF/tags" %> - - - - - 任务管理 - - - - -
${message}
-
-
-
- -
- -
- - - - - - - - - - - -
任务管理
${task.title}删除
- - - - - - diff --git a/examples/quickstart/src/main/webapp/WEB-INF/web.xml b/examples/quickstart/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index 59a622f1a..000000000 --- a/examples/quickstart/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,101 +0,0 @@ - - - quickstart - - - contextConfigLocation - - classpath*:/applicationContext.xml, - classpath*:/applicationContext-shiro.xml - - - - spring.profiles.default - production - - - - org.springframework.web.context.ContextLoaderListener - - - - encodingFilter - org.springframework.web.filter.CharacterEncodingFilter - - encoding - UTF-8 - - - forceEncoding - true - - - - encodingFilter - /* - - - - openEntityManagerInViewFilter - org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter - - - openEntityManagerInViewFilter - /* - - - - shiroFilter - org.springframework.web.filter.DelegatingFilterProxy - - targetFilterLifecycle - true - - - - shiroFilter - /* - - - - sitemeshFilter - com.opensymphony.sitemesh.webapp.SiteMeshFilter - - - sitemeshFilter - /* - - - - springServlet - org.springframework.web.servlet.DispatcherServlet - - contextConfigLocation - /WEB-INF/spring-mvc.xml - - 1 - - - springServlet - / - - - - 20 - - - - java.lang.Throwable - /WEB-INF/views/error/500.jsp - - - 500 - /WEB-INF/views/error/500.jsp - - - 404 - /WEB-INF/views/error/404.jsp - - \ No newline at end of file diff --git a/examples/quickstart/src/main/webapp/static/bootstrap/2.3.2/css/bootstrap-responsive.min.css b/examples/quickstart/src/main/webapp/static/bootstrap/2.3.2/css/bootstrap-responsive.min.css deleted file mode 100644 index f4ede63f3..000000000 --- a/examples/quickstart/src/main/webapp/static/bootstrap/2.3.2/css/bootstrap-responsive.min.css +++ /dev/null @@ -1,9 +0,0 @@ -/*! - * Bootstrap Responsive v2.3.2 - * - * Copyright 2012 Twitter, Inc - * Licensed under the Apache License v2.0 - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Designed and built with all the love in the world @twitter by @mdo and @fat. - */.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}@-ms-viewport{width:device-width}.hidden{display:none;visibility:hidden}.visible-phone{display:none!important}.visible-tablet{display:none!important}.hidden-desktop{display:none!important}.visible-desktop{display:inherit!important}@media(min-width:768px) and (max-width:979px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-tablet{display:inherit!important}.hidden-tablet{display:none!important}}@media(max-width:767px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-phone{display:inherit!important}.hidden-phone{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:inherit!important}.hidden-print{display:none!important}}@media(min-width:1200px){.row{margin-left:-30px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:30px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px}.span12{width:1170px}.span11{width:1070px}.span10{width:970px}.span9{width:870px}.span8{width:770px}.span7{width:670px}.span6{width:570px}.span5{width:470px}.span4{width:370px}.span3{width:270px}.span2{width:170px}.span1{width:70px}.offset12{margin-left:1230px}.offset11{margin-left:1130px}.offset10{margin-left:1030px}.offset9{margin-left:930px}.offset8{margin-left:830px}.offset7{margin-left:730px}.offset6{margin-left:630px}.offset5{margin-left:530px}.offset4{margin-left:430px}.offset3{margin-left:330px}.offset2{margin-left:230px}.offset1{margin-left:130px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.564102564102564%;*margin-left:2.5109110747408616%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.564102564102564%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.45299145299145%;*width:91.39979996362975%}.row-fluid .span10{width:82.90598290598291%;*width:82.8527914166212%}.row-fluid .span9{width:74.35897435897436%;*width:74.30578286961266%}.row-fluid .span8{width:65.81196581196582%;*width:65.75877432260411%}.row-fluid .span7{width:57.26495726495726%;*width:57.21176577559556%}.row-fluid .span6{width:48.717948717948715%;*width:48.664757228587014%}.row-fluid .span5{width:40.17094017094017%;*width:40.11774868157847%}.row-fluid .span4{width:31.623931623931625%;*width:31.570740134569924%}.row-fluid .span3{width:23.076923076923077%;*width:23.023731587561375%}.row-fluid .span2{width:14.52991452991453%;*width:14.476723040552828%}.row-fluid .span1{width:5.982905982905983%;*width:5.929714493544281%}.row-fluid .offset12{margin-left:105.12820512820512%;*margin-left:105.02182214948171%}.row-fluid .offset12:first-child{margin-left:102.56410256410257%;*margin-left:102.45771958537915%}.row-fluid .offset11{margin-left:96.58119658119658%;*margin-left:96.47481360247316%}.row-fluid .offset11:first-child{margin-left:94.01709401709402%;*margin-left:93.91071103837061%}.row-fluid .offset10{margin-left:88.03418803418803%;*margin-left:87.92780505546462%}.row-fluid .offset10:first-child{margin-left:85.47008547008548%;*margin-left:85.36370249136206%}.row-fluid .offset9{margin-left:79.48717948717949%;*margin-left:79.38079650845607%}.row-fluid .offset9:first-child{margin-left:76.92307692307693%;*margin-left:76.81669394435352%}.row-fluid .offset8{margin-left:70.94017094017094%;*margin-left:70.83378796144753%}.row-fluid .offset8:first-child{margin-left:68.37606837606839%;*margin-left:68.26968539734497%}.row-fluid .offset7{margin-left:62.393162393162385%;*margin-left:62.28677941443899%}.row-fluid .offset7:first-child{margin-left:59.82905982905982%;*margin-left:59.72267685033642%}.row-fluid .offset6{margin-left:53.84615384615384%;*margin-left:53.739770867430444%}.row-fluid .offset6:first-child{margin-left:51.28205128205128%;*margin-left:51.175668303327875%}.row-fluid .offset5{margin-left:45.299145299145295%;*margin-left:45.1927623204219%}.row-fluid .offset5:first-child{margin-left:42.73504273504273%;*margin-left:42.62865975631933%}.row-fluid .offset4{margin-left:36.75213675213675%;*margin-left:36.645753773413354%}.row-fluid .offset4:first-child{margin-left:34.18803418803419%;*margin-left:34.081651209310785%}.row-fluid .offset3{margin-left:28.205128205128204%;*margin-left:28.0987452264048%}.row-fluid .offset3:first-child{margin-left:25.641025641025642%;*margin-left:25.53464266230224%}.row-fluid .offset2{margin-left:19.65811965811966%;*margin-left:19.551736679396257%}.row-fluid .offset2:first-child{margin-left:17.094017094017094%;*margin-left:16.98763411529369%}.row-fluid .offset1{margin-left:11.11111111111111%;*margin-left:11.004728132387708%}.row-fluid .offset1:first-child{margin-left:8.547008547008547%;*margin-left:8.440625568285142%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:30px}input.span12,textarea.span12,.uneditable-input.span12{width:1156px}input.span11,textarea.span11,.uneditable-input.span11{width:1056px}input.span10,textarea.span10,.uneditable-input.span10{width:956px}input.span9,textarea.span9,.uneditable-input.span9{width:856px}input.span8,textarea.span8,.uneditable-input.span8{width:756px}input.span7,textarea.span7,.uneditable-input.span7{width:656px}input.span6,textarea.span6,.uneditable-input.span6{width:556px}input.span5,textarea.span5,.uneditable-input.span5{width:456px}input.span4,textarea.span4,.uneditable-input.span4{width:356px}input.span3,textarea.span3,.uneditable-input.span3{width:256px}input.span2,textarea.span2,.uneditable-input.span2{width:156px}input.span1,textarea.span1,.uneditable-input.span1{width:56px}.thumbnails{margin-left:-30px}.thumbnails>li{margin-left:30px}.row-fluid .thumbnails{margin-left:0}}@media(min-width:768px) and (max-width:979px){.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:724px}.span12{width:724px}.span11{width:662px}.span10{width:600px}.span9{width:538px}.span8{width:476px}.span7{width:414px}.span6{width:352px}.span5{width:290px}.span4{width:228px}.span3{width:166px}.span2{width:104px}.span1{width:42px}.offset12{margin-left:764px}.offset11{margin-left:702px}.offset10{margin-left:640px}.offset9{margin-left:578px}.offset8{margin-left:516px}.offset7{margin-left:454px}.offset6{margin-left:392px}.offset5{margin-left:330px}.offset4{margin-left:268px}.offset3{margin-left:206px}.offset2{margin-left:144px}.offset1{margin-left:82px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.7624309392265194%;*margin-left:2.709239449864817%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.7624309392265194%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.43646408839778%;*width:91.38327259903608%}.row-fluid .span10{width:82.87292817679558%;*width:82.81973668743387%}.row-fluid .span9{width:74.30939226519337%;*width:74.25620077583166%}.row-fluid .span8{width:65.74585635359117%;*width:65.69266486422946%}.row-fluid .span7{width:57.18232044198895%;*width:57.12912895262725%}.row-fluid .span6{width:48.61878453038674%;*width:48.56559304102504%}.row-fluid .span5{width:40.05524861878453%;*width:40.00205712942283%}.row-fluid .span4{width:31.491712707182323%;*width:31.43852121782062%}.row-fluid .span3{width:22.92817679558011%;*width:22.87498530621841%}.row-fluid .span2{width:14.3646408839779%;*width:14.311449394616199%}.row-fluid .span1{width:5.801104972375691%;*width:5.747913483013988%}.row-fluid .offset12{margin-left:105.52486187845304%;*margin-left:105.41847889972962%}.row-fluid .offset12:first-child{margin-left:102.76243093922652%;*margin-left:102.6560479605031%}.row-fluid .offset11{margin-left:96.96132596685082%;*margin-left:96.8549429881274%}.row-fluid .offset11:first-child{margin-left:94.1988950276243%;*margin-left:94.09251204890089%}.row-fluid .offset10{margin-left:88.39779005524862%;*margin-left:88.2914070765252%}.row-fluid .offset10:first-child{margin-left:85.6353591160221%;*margin-left:85.52897613729868%}.row-fluid .offset9{margin-left:79.8342541436464%;*margin-left:79.72787116492299%}.row-fluid .offset9:first-child{margin-left:77.07182320441989%;*margin-left:76.96544022569647%}.row-fluid .offset8{margin-left:71.2707182320442%;*margin-left:71.16433525332079%}.row-fluid .offset8:first-child{margin-left:68.50828729281768%;*margin-left:68.40190431409427%}.row-fluid .offset7{margin-left:62.70718232044199%;*margin-left:62.600799341718584%}.row-fluid .offset7:first-child{margin-left:59.94475138121547%;*margin-left:59.838368402492065%}.row-fluid .offset6{margin-left:54.14364640883978%;*margin-left:54.037263430116376%}.row-fluid .offset6:first-child{margin-left:51.38121546961326%;*margin-left:51.27483249088986%}.row-fluid .offset5{margin-left:45.58011049723757%;*margin-left:45.47372751851417%}.row-fluid .offset5:first-child{margin-left:42.81767955801105%;*margin-left:42.71129657928765%}.row-fluid .offset4{margin-left:37.01657458563536%;*margin-left:36.91019160691196%}.row-fluid .offset4:first-child{margin-left:34.25414364640884%;*margin-left:34.14776066768544%}.row-fluid .offset3{margin-left:28.45303867403315%;*margin-left:28.346655695309746%}.row-fluid .offset3:first-child{margin-left:25.69060773480663%;*margin-left:25.584224756083227%}.row-fluid .offset2{margin-left:19.88950276243094%;*margin-left:19.783119783707537%}.row-fluid .offset2:first-child{margin-left:17.12707182320442%;*margin-left:17.02068884448102%}.row-fluid .offset1{margin-left:11.32596685082873%;*margin-left:11.219583872105325%}.row-fluid .offset1:first-child{margin-left:8.56353591160221%;*margin-left:8.457152932878806%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:710px}input.span11,textarea.span11,.uneditable-input.span11{width:648px}input.span10,textarea.span10,.uneditable-input.span10{width:586px}input.span9,textarea.span9,.uneditable-input.span9{width:524px}input.span8,textarea.span8,.uneditable-input.span8{width:462px}input.span7,textarea.span7,.uneditable-input.span7{width:400px}input.span6,textarea.span6,.uneditable-input.span6{width:338px}input.span5,textarea.span5,.uneditable-input.span5{width:276px}input.span4,textarea.span4,.uneditable-input.span4{width:214px}input.span3,textarea.span3,.uneditable-input.span3{width:152px}input.span2,textarea.span2,.uneditable-input.span2{width:90px}input.span1,textarea.span1,.uneditable-input.span1{width:28px}}@media(max-width:767px){body{padding-right:20px;padding-left:20px}.navbar-fixed-top,.navbar-fixed-bottom,.navbar-static-top{margin-right:-20px;margin-left:-20px}.container-fluid{padding:0}.dl-horizontal dt{float:none;width:auto;clear:none;text-align:left}.dl-horizontal dd{margin-left:0}.container{width:auto}.row-fluid{width:100%}.row,.thumbnails{margin-left:0}.thumbnails>li{float:none;margin-left:0}[class*="span"],.uneditable-input[class*="span"],.row-fluid [class*="span"]{display:block;float:none;width:100%;margin-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.span12,.row-fluid .span12{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="offset"]:first-child{margin-left:0}.input-large,.input-xlarge,.input-xxlarge,input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.input-prepend input,.input-append input,.input-prepend input[class*="span"],.input-append input[class*="span"]{display:inline-block;width:auto}.controls-row [class*="span"]+[class*="span"]{margin-left:0}.modal{position:fixed;top:20px;right:20px;left:20px;width:auto;margin:0}.modal.fade{top:-100px}.modal.fade.in{top:20px}}@media(max-width:480px){.nav-collapse{-webkit-transform:translate3d(0,0,0)}.page-header h1 small{display:block;line-height:20px}input[type="checkbox"],input[type="radio"]{border:1px solid #ccc}.form-horizontal .control-label{float:none;width:auto;padding-top:0;text-align:left}.form-horizontal .controls{margin-left:0}.form-horizontal .control-list{padding-top:0}.form-horizontal .form-actions{padding-right:10px;padding-left:10px}.media .pull-left,.media .pull-right{display:block;float:none;margin-bottom:10px}.media-object{margin-right:0;margin-left:0}.modal{top:10px;right:10px;left:10px}.modal-header .close{padding:10px;margin:-10px}.carousel-caption{position:static}}@media(max-width:979px){body{padding-top:0}.navbar-fixed-top,.navbar-fixed-bottom{position:static}.navbar-fixed-top{margin-bottom:20px}.navbar-fixed-bottom{margin-top:20px}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding:5px}.navbar .container{width:auto;padding:0}.navbar .brand{padding-right:10px;padding-left:10px;margin:0 0 0 -5px}.nav-collapse{clear:both}.nav-collapse .nav{float:none;margin:0 0 10px}.nav-collapse .nav>li{float:none}.nav-collapse .nav>li>a{margin-bottom:2px}.nav-collapse .nav>.divider-vertical{display:none}.nav-collapse .nav .nav-header{color:#777;text-shadow:none}.nav-collapse .nav>li>a,.nav-collapse .dropdown-menu a{padding:9px 15px;font-weight:bold;color:#777;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.nav-collapse .btn{padding:4px 10px 4px;font-weight:normal;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.nav-collapse .dropdown-menu li+li a{margin-bottom:2px}.nav-collapse .nav>li>a:hover,.nav-collapse .nav>li>a:focus,.nav-collapse .dropdown-menu a:hover,.nav-collapse .dropdown-menu a:focus{background-color:#f2f2f2}.navbar-inverse .nav-collapse .nav>li>a,.navbar-inverse .nav-collapse .dropdown-menu a{color:#999}.navbar-inverse .nav-collapse .nav>li>a:hover,.navbar-inverse .nav-collapse .nav>li>a:focus,.navbar-inverse .nav-collapse .dropdown-menu a:hover,.navbar-inverse .nav-collapse .dropdown-menu a:focus{background-color:#111}.nav-collapse.in .btn-group{padding:0;margin-top:5px}.nav-collapse .dropdown-menu{position:static;top:auto;left:auto;display:none;float:none;max-width:none;padding:0;margin:0 15px;background-color:transparent;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.nav-collapse .open>.dropdown-menu{display:block}.nav-collapse .dropdown-menu:before,.nav-collapse .dropdown-menu:after{display:none}.nav-collapse .dropdown-menu .divider{display:none}.nav-collapse .nav>li>.dropdown-menu:before,.nav-collapse .nav>li>.dropdown-menu:after{display:none}.nav-collapse .navbar-form,.nav-collapse .navbar-search{float:none;padding:10px 15px;margin:10px 0;border-top:1px solid #f2f2f2;border-bottom:1px solid #f2f2f2;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1)}.navbar-inverse .nav-collapse .navbar-form,.navbar-inverse .nav-collapse .navbar-search{border-top-color:#111;border-bottom-color:#111}.navbar .nav-collapse .nav.pull-right{float:none;margin-left:0}.nav-collapse,.nav-collapse.collapse{height:0;overflow:hidden}.navbar .btn-navbar{display:block}.navbar-static .navbar-inner{padding-right:10px;padding-left:10px}}@media(min-width:980px){.nav-collapse.collapse{height:auto!important;overflow:visible!important}} diff --git a/examples/quickstart/src/main/webapp/static/bootstrap/2.3.2/css/bootstrap.min.css b/examples/quickstart/src/main/webapp/static/bootstrap/2.3.2/css/bootstrap.min.css deleted file mode 100644 index b6428e695..000000000 --- a/examples/quickstart/src/main/webapp/static/bootstrap/2.3.2/css/bootstrap.min.css +++ /dev/null @@ -1,9 +0,0 @@ -/*! - * Bootstrap v2.3.2 - * - * Copyright 2012 Twitter, Inc - * Licensed under the Apache License v2.0 - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Designed and built with all the love in the world @twitter by @mdo and @fat. - */.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}a:hover,a:active{outline:0}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{width:auto\9;height:auto;max-width:100%;vertical-align:middle;border:0;-ms-interpolation-mode:bicubic}#map_canvas img,.google-maps img{max-width:none}button,input,select,textarea{margin:0;font-size:100%;vertical-align:middle}button,input{*overflow:visible;line-height:normal}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}button,html input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button}label,select,button,input[type="button"],input[type="reset"],input[type="submit"],input[type="radio"],input[type="checkbox"]{cursor:pointer}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-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none}textarea{overflow:auto;vertical-align:top}@media print{*{color:#000!important;text-shadow:none!important;background:transparent!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,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}@page{margin:.5cm}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}}body{margin:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:20px;color:#333;background-color:#fff}a{color:#08c;text-decoration:none}a:hover,a:focus{color:#005580;text-decoration:underline}.img-rounded{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.img-polaroid{padding:4px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.1);box-shadow:0 1px 3px rgba(0,0,0,0.1)}.img-circle{-webkit-border-radius:500px;-moz-border-radius:500px;border-radius:500px}.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}.span12{width:940px}.span11{width:860px}.span10{width:780px}.span9{width:700px}.span8{width:620px}.span7{width:540px}.span6{width:460px}.span5{width:380px}.span4{width:300px}.span3{width:220px}.span2{width:140px}.span1{width:60px}.offset12{margin-left:980px}.offset11{margin-left:900px}.offset10{margin-left:820px}.offset9{margin-left:740px}.offset8{margin-left:660px}.offset7{margin-left:580px}.offset6{margin-left:500px}.offset5{margin-left:420px}.offset4{margin-left:340px}.offset3{margin-left:260px}.offset2{margin-left:180px}.offset1{margin-left:100px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.127659574468085%;*margin-left:2.074468085106383%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.127659574468085%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.48936170212765%;*width:91.43617021276594%}.row-fluid .span10{width:82.97872340425532%;*width:82.92553191489361%}.row-fluid .span9{width:74.46808510638297%;*width:74.41489361702126%}.row-fluid .span8{width:65.95744680851064%;*width:65.90425531914893%}.row-fluid .span7{width:57.44680851063829%;*width:57.39361702127659%}.row-fluid .span6{width:48.93617021276595%;*width:48.88297872340425%}.row-fluid .span5{width:40.42553191489362%;*width:40.37234042553192%}.row-fluid .span4{width:31.914893617021278%;*width:31.861702127659576%}.row-fluid .span3{width:23.404255319148934%;*width:23.351063829787233%}.row-fluid .span2{width:14.893617021276595%;*width:14.840425531914894%}.row-fluid .span1{width:6.382978723404255%;*width:6.329787234042553%}.row-fluid .offset12{margin-left:104.25531914893617%;*margin-left:104.14893617021275%}.row-fluid .offset12:first-child{margin-left:102.12765957446808%;*margin-left:102.02127659574467%}.row-fluid .offset11{margin-left:95.74468085106382%;*margin-left:95.6382978723404%}.row-fluid .offset11:first-child{margin-left:93.61702127659574%;*margin-left:93.51063829787232%}.row-fluid .offset10{margin-left:87.23404255319149%;*margin-left:87.12765957446807%}.row-fluid .offset10:first-child{margin-left:85.1063829787234%;*margin-left:84.99999999999999%}.row-fluid .offset9{margin-left:78.72340425531914%;*margin-left:78.61702127659572%}.row-fluid .offset9:first-child{margin-left:76.59574468085106%;*margin-left:76.48936170212764%}.row-fluid .offset8{margin-left:70.2127659574468%;*margin-left:70.10638297872339%}.row-fluid .offset8:first-child{margin-left:68.08510638297872%;*margin-left:67.9787234042553%}.row-fluid .offset7{margin-left:61.70212765957446%;*margin-left:61.59574468085106%}.row-fluid .offset7:first-child{margin-left:59.574468085106375%;*margin-left:59.46808510638297%}.row-fluid .offset6{margin-left:53.191489361702125%;*margin-left:53.085106382978715%}.row-fluid .offset6:first-child{margin-left:51.063829787234035%;*margin-left:50.95744680851063%}.row-fluid .offset5{margin-left:44.68085106382979%;*margin-left:44.57446808510638%}.row-fluid .offset5:first-child{margin-left:42.5531914893617%;*margin-left:42.4468085106383%}.row-fluid .offset4{margin-left:36.170212765957444%;*margin-left:36.06382978723405%}.row-fluid .offset4:first-child{margin-left:34.04255319148936%;*margin-left:33.93617021276596%}.row-fluid .offset3{margin-left:27.659574468085104%;*margin-left:27.5531914893617%}.row-fluid .offset3:first-child{margin-left:25.53191489361702%;*margin-left:25.425531914893618%}.row-fluid .offset2{margin-left:19.148936170212764%;*margin-left:19.04255319148936%}.row-fluid .offset2:first-child{margin-left:17.02127659574468%;*margin-left:16.914893617021278%}.row-fluid .offset1{margin-left:10.638297872340425%;*margin-left:10.53191489361702%}.row-fluid .offset1:first-child{margin-left:8.51063829787234%;*margin-left:8.404255319148938%}[class*="span"].hide,.row-fluid [class*="span"].hide{display:none}[class*="span"].pull-right,.row-fluid [class*="span"].pull-right{float:right}.container{margin-right:auto;margin-left:auto;*zoom:1}.container:before,.container:after{display:table;line-height:0;content:""}.container:after{clear:both}.container-fluid{padding-right:20px;padding-left:20px;*zoom:1}.container-fluid:before,.container-fluid:after{display:table;line-height:0;content:""}.container-fluid:after{clear:both}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:21px;font-weight:200;line-height:30px}small{font-size:85%}strong{font-weight:bold}em{font-style:italic}cite{font-style:normal}.muted{color:#999}a.muted:hover,a.muted:focus{color:#808080}.text-warning{color:#c09853}a.text-warning:hover,a.text-warning:focus{color:#a47e3c}.text-error{color:#b94a48}a.text-error:hover,a.text-error:focus{color:#953b39}.text-info{color:#3a87ad}a.text-info:hover,a.text-info:focus{color:#2d6987}.text-success{color:#468847}a.text-success:hover,a.text-success:focus{color:#356635}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}h1,h2,h3,h4,h5,h6{margin:10px 0;font-family:inherit;font-weight:bold;line-height:20px;color:inherit;text-rendering:optimizelegibility}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-weight:normal;line-height:1;color:#999}h1,h2,h3{line-height:40px}h1{font-size:38.5px}h2{font-size:31.5px}h3{font-size:24.5px}h4{font-size:17.5px}h5{font-size:14px}h6{font-size:11.9px}h1 small{font-size:24.5px}h2 small{font-size:17.5px}h3 small{font-size:14px}h4 small{font-size:14px}.page-header{padding-bottom:9px;margin:20px 0 30px;border-bottom:1px solid #eee}ul,ol{padding:0;margin:0 0 10px 25px}ul ul,ul ol,ol ol,ol ul{margin-bottom:0}li{line-height:20px}ul.unstyled,ol.unstyled{margin-left:0;list-style:none}ul.inline,ol.inline{margin-left:0;list-style:none}ul.inline>li,ol.inline>li{display:inline-block;*display:inline;padding-right:5px;padding-left:5px;*zoom:1}dl{margin-bottom:20px}dt,dd{line-height:20px}dt{font-weight:bold}dd{margin-left:10px}.dl-horizontal{*zoom:1}.dl-horizontal:before,.dl-horizontal:after{display:table;line-height:0;content:""}.dl-horizontal:after{clear:both}.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}hr{margin:20px 0;border:0;border-top:1px solid #eee;border-bottom:1px solid #fff}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #999}abbr.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:0 0 0 15px;margin:0 0 20px;border-left:5px solid #eee}blockquote p{margin-bottom:0;font-size:17.5px;font-weight:300;line-height:1.25}blockquote small{display:block;line-height:20px;color:#999}blockquote small:before{content:'\2014 \00A0'}blockquote.pull-right{float:right;padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0}blockquote.pull-right p,blockquote.pull-right small{text-align:right}blockquote.pull-right small:before{content:''}blockquote.pull-right small:after{content:'\00A0 \2014'}q:before,q:after,blockquote:before,blockquote:after{content:""}address{display:block;margin-bottom:20px;font-style:normal;line-height:20px}code,pre{padding:0 3px 2px;font-family:Monaco,Menlo,Consolas,"Courier New",monospace;font-size:12px;color:#333;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}code{padding:2px 4px;color:#d14;white-space:nowrap;background-color:#f7f7f9;border:1px solid #e1e1e8}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:20px;word-break:break-all;word-wrap:break-word;white-space:pre;white-space:pre-wrap;background-color:#f5f5f5;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.15);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}pre.prettyprint{margin-bottom:20px}pre code{padding:0;color:inherit;white-space:pre;white-space:pre-wrap;background-color:transparent;border:0}.pre-scrollable{max-height:340px;overflow-y:scroll}form{margin:0 0 20px}fieldset{padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:40px;color:#333;border:0;border-bottom:1px solid #e5e5e5}legend small{font-size:15px;color:#999}label,input,button,select,textarea{font-size:14px;font-weight:normal;line-height:20px}input,button,select,textarea{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif}label{display:block;margin-bottom:5px}select,textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{display:inline-block;height:20px;padding:4px 6px;margin-bottom:10px;font-size:14px;line-height:20px;color:#555;vertical-align:middle;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}input,textarea,.uneditable-input{width:206px}textarea{height:auto}textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{background-color:#fff;border:1px solid #ccc;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border linear .2s,box-shadow linear .2s;-moz-transition:border linear .2s,box-shadow linear .2s;-o-transition:border linear .2s,box-shadow linear .2s;transition:border linear .2s,box-shadow linear .2s}textarea:focus,input[type="text"]:focus,input[type="password"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,input[type="date"]:focus,input[type="month"]:focus,input[type="time"]:focus,input[type="week"]:focus,input[type="number"]:focus,input[type="email"]:focus,input[type="url"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="color"]:focus,.uneditable-input:focus{border-color:rgba(82,168,236,0.8);outline:0;outline:thin dotted \9;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6)}input[type="radio"],input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;*margin-top:0;line-height:normal}input[type="file"],input[type="image"],input[type="submit"],input[type="reset"],input[type="button"],input[type="radio"],input[type="checkbox"]{width:auto}select,input[type="file"]{height:30px;*margin-top:4px;line-height:30px}select{width:220px;background-color:#fff;border:1px solid #ccc}select[multiple],select[size]{height:auto}select:focus,input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.uneditable-input,.uneditable-textarea{color:#999;cursor:not-allowed;background-color:#fcfcfc;border-color:#ccc;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);box-shadow:inset 0 1px 2px rgba(0,0,0,0.025)}.uneditable-input{overflow:hidden;white-space:nowrap}.uneditable-textarea{width:auto;height:auto}input:-moz-placeholder,textarea:-moz-placeholder{color:#999}input:-ms-input-placeholder,textarea:-ms-input-placeholder{color:#999}input::-webkit-input-placeholder,textarea::-webkit-input-placeholder{color:#999}.radio,.checkbox{min-height:20px;padding-left:20px}.radio input[type="radio"],.checkbox input[type="checkbox"]{float:left;margin-left:-20px}.controls>.radio:first-child,.controls>.checkbox:first-child{padding-top:5px}.radio.inline,.checkbox.inline{display:inline-block;padding-top:5px;margin-bottom:0;vertical-align:middle}.radio.inline+.radio.inline,.checkbox.inline+.checkbox.inline{margin-left:10px}.input-mini{width:60px}.input-small{width:90px}.input-medium{width:150px}.input-large{width:210px}.input-xlarge{width:270px}.input-xxlarge{width:530px}input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"]{float:none;margin-left:0}.input-append input[class*="span"],.input-append .uneditable-input[class*="span"],.input-prepend input[class*="span"],.input-prepend .uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"],.row-fluid .input-prepend [class*="span"],.row-fluid .input-append [class*="span"]{display:inline-block}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:926px}input.span11,textarea.span11,.uneditable-input.span11{width:846px}input.span10,textarea.span10,.uneditable-input.span10{width:766px}input.span9,textarea.span9,.uneditable-input.span9{width:686px}input.span8,textarea.span8,.uneditable-input.span8{width:606px}input.span7,textarea.span7,.uneditable-input.span7{width:526px}input.span6,textarea.span6,.uneditable-input.span6{width:446px}input.span5,textarea.span5,.uneditable-input.span5{width:366px}input.span4,textarea.span4,.uneditable-input.span4{width:286px}input.span3,textarea.span3,.uneditable-input.span3{width:206px}input.span2,textarea.span2,.uneditable-input.span2{width:126px}input.span1,textarea.span1,.uneditable-input.span1{width:46px}.controls-row{*zoom:1}.controls-row:before,.controls-row:after{display:table;line-height:0;content:""}.controls-row:after{clear:both}.controls-row [class*="span"],.row-fluid .controls-row [class*="span"]{float:left}.controls-row .checkbox[class*="span"],.controls-row .radio[class*="span"]{padding-top:5px}input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{cursor:not-allowed;background-color:#eee}input[type="radio"][disabled],input[type="checkbox"][disabled],input[type="radio"][readonly],input[type="checkbox"][readonly]{background-color:transparent}.control-group.warning .control-label,.control-group.warning .help-block,.control-group.warning .help-inline{color:#c09853}.control-group.warning .checkbox,.control-group.warning .radio,.control-group.warning input,.control-group.warning select,.control-group.warning textarea{color:#c09853}.control-group.warning input,.control-group.warning select,.control-group.warning textarea{border-color:#c09853;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.warning input:focus,.control-group.warning select:focus,.control-group.warning textarea:focus{border-color:#a47e3c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e}.control-group.warning .input-prepend .add-on,.control-group.warning .input-append .add-on{color:#c09853;background-color:#fcf8e3;border-color:#c09853}.control-group.error .control-label,.control-group.error .help-block,.control-group.error .help-inline{color:#b94a48}.control-group.error .checkbox,.control-group.error .radio,.control-group.error input,.control-group.error select,.control-group.error textarea{color:#b94a48}.control-group.error input,.control-group.error select,.control-group.error textarea{border-color:#b94a48;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.error input:focus,.control-group.error select:focus,.control-group.error textarea:focus{border-color:#953b39;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392}.control-group.error .input-prepend .add-on,.control-group.error .input-append .add-on{color:#b94a48;background-color:#f2dede;border-color:#b94a48}.control-group.success .control-label,.control-group.success .help-block,.control-group.success .help-inline{color:#468847}.control-group.success .checkbox,.control-group.success .radio,.control-group.success input,.control-group.success select,.control-group.success textarea{color:#468847}.control-group.success input,.control-group.success select,.control-group.success textarea{border-color:#468847;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.success input:focus,.control-group.success select:focus,.control-group.success textarea:focus{border-color:#356635;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b}.control-group.success .input-prepend .add-on,.control-group.success .input-append .add-on{color:#468847;background-color:#dff0d8;border-color:#468847}.control-group.info .control-label,.control-group.info .help-block,.control-group.info .help-inline{color:#3a87ad}.control-group.info .checkbox,.control-group.info .radio,.control-group.info input,.control-group.info select,.control-group.info textarea{color:#3a87ad}.control-group.info input,.control-group.info select,.control-group.info textarea{border-color:#3a87ad;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.info input:focus,.control-group.info select:focus,.control-group.info textarea:focus{border-color:#2d6987;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3}.control-group.info .input-prepend .add-on,.control-group.info .input-append .add-on{color:#3a87ad;background-color:#d9edf7;border-color:#3a87ad}input:focus:invalid,textarea:focus:invalid,select:focus:invalid{color:#b94a48;border-color:#ee5f5b}input:focus:invalid:focus,textarea:focus:invalid:focus,select:focus:invalid:focus{border-color:#e9322d;-webkit-box-shadow:0 0 6px #f8b9b7;-moz-box-shadow:0 0 6px #f8b9b7;box-shadow:0 0 6px #f8b9b7}.form-actions{padding:19px 20px 20px;margin-top:20px;margin-bottom:20px;background-color:#f5f5f5;border-top:1px solid #e5e5e5;*zoom:1}.form-actions:before,.form-actions:after{display:table;line-height:0;content:""}.form-actions:after{clear:both}.help-block,.help-inline{color:#595959}.help-block{display:block;margin-bottom:10px}.help-inline{display:inline-block;*display:inline;padding-left:5px;vertical-align:middle;*zoom:1}.input-append,.input-prepend{display:inline-block;margin-bottom:10px;font-size:0;white-space:nowrap;vertical-align:middle}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input,.input-append .dropdown-menu,.input-prepend .dropdown-menu,.input-append .popover,.input-prepend .popover{font-size:14px}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input{position:relative;margin-bottom:0;*margin-left:0;vertical-align:top;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-append input:focus,.input-prepend input:focus,.input-append select:focus,.input-prepend select:focus,.input-append .uneditable-input:focus,.input-prepend .uneditable-input:focus{z-index:2}.input-append .add-on,.input-prepend .add-on{display:inline-block;width:auto;height:20px;min-width:16px;padding:4px 5px;font-size:14px;font-weight:normal;line-height:20px;text-align:center;text-shadow:0 1px 0 #fff;background-color:#eee;border:1px solid #ccc}.input-append .add-on,.input-prepend .add-on,.input-append .btn,.input-prepend .btn,.input-append .btn-group>.dropdown-toggle,.input-prepend .btn-group>.dropdown-toggle{vertical-align:top;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-append .active,.input-prepend .active{background-color:#a9dba9;border-color:#46a546}.input-prepend .add-on,.input-prepend .btn{margin-right:-1px}.input-prepend .add-on:first-child,.input-prepend .btn:first-child{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-append input,.input-append select,.input-append .uneditable-input{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-append input+.btn-group .btn:last-child,.input-append select+.btn-group .btn:last-child,.input-append .uneditable-input+.btn-group .btn:last-child{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-append .add-on,.input-append .btn,.input-append .btn-group{margin-left:-1px}.input-append .add-on:last-child,.input-append .btn:last-child,.input-append .btn-group:last-child>.dropdown-toggle{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append input,.input-prepend.input-append select,.input-prepend.input-append .uneditable-input{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-prepend.input-append input+.btn-group .btn,.input-prepend.input-append select+.btn-group .btn,.input-prepend.input-append .uneditable-input+.btn-group .btn{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append .add-on:first-child,.input-prepend.input-append .btn:first-child{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-prepend.input-append .add-on:last-child,.input-prepend.input-append .btn:last-child{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append .btn-group:first-child{margin-left:0}input.search-query{padding-right:14px;padding-right:4px \9;padding-left:14px;padding-left:4px \9;margin-bottom:0;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.form-search .input-append .search-query,.form-search .input-prepend .search-query{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.form-search .input-append .search-query{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px}.form-search .input-append .btn{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0}.form-search .input-prepend .search-query{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0}.form-search .input-prepend .btn{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px}.form-search input,.form-inline input,.form-horizontal input,.form-search textarea,.form-inline textarea,.form-horizontal textarea,.form-search select,.form-inline select,.form-horizontal select,.form-search .help-inline,.form-inline .help-inline,.form-horizontal .help-inline,.form-search .uneditable-input,.form-inline .uneditable-input,.form-horizontal .uneditable-input,.form-search .input-prepend,.form-inline .input-prepend,.form-horizontal .input-prepend,.form-search .input-append,.form-inline .input-append,.form-horizontal .input-append{display:inline-block;*display:inline;margin-bottom:0;vertical-align:middle;*zoom:1}.form-search .hide,.form-inline .hide,.form-horizontal .hide{display:none}.form-search label,.form-inline label,.form-search .btn-group,.form-inline .btn-group{display:inline-block}.form-search .input-append,.form-inline .input-append,.form-search .input-prepend,.form-inline .input-prepend{margin-bottom:0}.form-search .radio,.form-search .checkbox,.form-inline .radio,.form-inline .checkbox{padding-left:0;margin-bottom:0;vertical-align:middle}.form-search .radio input[type="radio"],.form-search .checkbox input[type="checkbox"],.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{float:left;margin-right:3px;margin-left:0}.control-group{margin-bottom:10px}legend+.control-group{margin-top:20px;-webkit-margin-top-collapse:separate}.form-horizontal .control-group{margin-bottom:20px;*zoom:1}.form-horizontal .control-group:before,.form-horizontal .control-group:after{display:table;line-height:0;content:""}.form-horizontal .control-group:after{clear:both}.form-horizontal .control-label{float:left;width:160px;padding-top:5px;text-align:right}.form-horizontal .controls{*display:inline-block;*padding-left:20px;margin-left:180px;*margin-left:0}.form-horizontal .controls:first-child{*padding-left:180px}.form-horizontal .help-block{margin-bottom:0}.form-horizontal input+.help-block,.form-horizontal select+.help-block,.form-horizontal textarea+.help-block,.form-horizontal .uneditable-input+.help-block,.form-horizontal .input-prepend+.help-block,.form-horizontal .input-append+.help-block{margin-top:10px}.form-horizontal .form-actions{padding-left:180px}table{max-width:100%;background-color:transparent;border-collapse:collapse;border-spacing:0}.table{width:100%;margin-bottom:20px}.table th,.table td{padding:8px;line-height:20px;text-align:left;vertical-align:top;border-top:1px solid #ddd}.table th{font-weight:bold}.table thead th{vertical-align:bottom}.table caption+thead tr:first-child th,.table caption+thead tr:first-child td,.table colgroup+thead tr:first-child th,.table colgroup+thead tr:first-child td,.table thead:first-child tr:first-child th,.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 th,.table-condensed td{padding:4px 5px}.table-bordered{border:1px solid #ddd;border-collapse:separate;*border-collapse:collapse;border-left:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.table-bordered th,.table-bordered td{border-left:1px solid #ddd}.table-bordered caption+thead tr:first-child th,.table-bordered caption+tbody tr:first-child th,.table-bordered caption+tbody tr:first-child td,.table-bordered colgroup+thead tr:first-child th,.table-bordered colgroup+tbody tr:first-child th,.table-bordered colgroup+tbody tr:first-child td,.table-bordered thead:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child td{border-top:0}.table-bordered thead:first-child tr:first-child>th:first-child,.table-bordered tbody:first-child tr:first-child>td:first-child,.table-bordered tbody:first-child tr:first-child>th:first-child{-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topleft:4px}.table-bordered thead:first-child tr:first-child>th:last-child,.table-bordered tbody:first-child tr:first-child>td:last-child,.table-bordered tbody:first-child tr:first-child>th:last-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-topright:4px}.table-bordered thead:last-child tr:last-child>th:first-child,.table-bordered tbody:last-child tr:last-child>td:first-child,.table-bordered tbody:last-child tr:last-child>th:first-child,.table-bordered tfoot:last-child tr:last-child>td:first-child,.table-bordered tfoot:last-child tr:last-child>th:first-child{-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px}.table-bordered thead:last-child tr:last-child>th:last-child,.table-bordered tbody:last-child tr:last-child>td:last-child,.table-bordered tbody:last-child tr:last-child>th:last-child,.table-bordered tfoot:last-child tr:last-child>td:last-child,.table-bordered tfoot:last-child tr:last-child>th:last-child{-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px}.table-bordered tfoot+tbody:last-child tr:last-child td:first-child{-webkit-border-bottom-left-radius:0;border-bottom-left-radius:0;-moz-border-radius-bottomleft:0}.table-bordered tfoot+tbody:last-child tr:last-child td:last-child{-webkit-border-bottom-right-radius:0;border-bottom-right-radius:0;-moz-border-radius-bottomright:0}.table-bordered caption+thead tr:first-child th:first-child,.table-bordered caption+tbody tr:first-child td:first-child,.table-bordered colgroup+thead tr:first-child th:first-child,.table-bordered colgroup+tbody tr:first-child td:first-child{-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topleft:4px}.table-bordered caption+thead tr:first-child th:last-child,.table-bordered caption+tbody tr:first-child td:last-child,.table-bordered colgroup+thead tr:first-child th:last-child,.table-bordered colgroup+tbody tr:first-child td:last-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-topright:4px}.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 td[class*="span"],table th[class*="span"],.row-fluid table td[class*="span"],.row-fluid table th[class*="span"]{display:table-cell;float:none;margin-left:0}.table td.span1,.table th.span1{float:none;width:44px;margin-left:0}.table td.span2,.table th.span2{float:none;width:124px;margin-left:0}.table td.span3,.table th.span3{float:none;width:204px;margin-left:0}.table td.span4,.table th.span4{float:none;width:284px;margin-left:0}.table td.span5,.table th.span5{float:none;width:364px;margin-left:0}.table td.span6,.table th.span6{float:none;width:444px;margin-left:0}.table td.span7,.table th.span7{float:none;width:524px;margin-left:0}.table td.span8,.table th.span8{float:none;width:604px;margin-left:0}.table td.span9,.table th.span9{float:none;width:684px;margin-left:0}.table td.span10,.table th.span10{float:none;width:764px;margin-left:0}.table td.span11,.table th.span11{float:none;width:844px;margin-left:0}.table td.span12,.table th.span12{float:none;width:924px;margin-left:0}.table tbody tr.success>td{background-color:#dff0d8}.table tbody tr.error>td{background-color:#f2dede}.table tbody tr.warning>td{background-color:#fcf8e3}.table tbody tr.info>td{background-color:#d9edf7}.table-hover tbody tr.success:hover>td{background-color:#d0e9c6}.table-hover tbody tr.error:hover>td{background-color:#ebcccc}.table-hover tbody tr.warning:hover>td{background-color:#faf2cc}.table-hover tbody tr.info:hover>td{background-color:#c4e3f3}[class^="icon-"],[class*=" icon-"]{display:inline-block;width:14px;height:14px;margin-top:1px;*margin-right:.3em;line-height:14px;vertical-align:text-top;background-image:url("../img/glyphicons-halflings.png");background-position:14px 14px;background-repeat:no-repeat}.icon-white,.nav-pills>.active>a>[class^="icon-"],.nav-pills>.active>a>[class*=" icon-"],.nav-list>.active>a>[class^="icon-"],.nav-list>.active>a>[class*=" icon-"],.navbar-inverse .nav>.active>a>[class^="icon-"],.navbar-inverse .nav>.active>a>[class*=" icon-"],.dropdown-menu>li>a:hover>[class^="icon-"],.dropdown-menu>li>a:focus>[class^="icon-"],.dropdown-menu>li>a:hover>[class*=" icon-"],.dropdown-menu>li>a:focus>[class*=" icon-"],.dropdown-menu>.active>a>[class^="icon-"],.dropdown-menu>.active>a>[class*=" icon-"],.dropdown-submenu:hover>a>[class^="icon-"],.dropdown-submenu:focus>a>[class^="icon-"],.dropdown-submenu:hover>a>[class*=" icon-"],.dropdown-submenu:focus>a>[class*=" icon-"]{background-image:url("../img/glyphicons-halflings-white.png")}.icon-glass{background-position:0 0}.icon-music{background-position:-24px 0}.icon-search{background-position:-48px 0}.icon-envelope{background-position:-72px 0}.icon-heart{background-position:-96px 0}.icon-star{background-position:-120px 0}.icon-star-empty{background-position:-144px 0}.icon-user{background-position:-168px 0}.icon-film{background-position:-192px 0}.icon-th-large{background-position:-216px 0}.icon-th{background-position:-240px 0}.icon-th-list{background-position:-264px 0}.icon-ok{background-position:-288px 0}.icon-remove{background-position:-312px 0}.icon-zoom-in{background-position:-336px 0}.icon-zoom-out{background-position:-360px 0}.icon-off{background-position:-384px 0}.icon-signal{background-position:-408px 0}.icon-cog{background-position:-432px 0}.icon-trash{background-position:-456px 0}.icon-home{background-position:0 -24px}.icon-file{background-position:-24px -24px}.icon-time{background-position:-48px -24px}.icon-road{background-position:-72px -24px}.icon-download-alt{background-position:-96px -24px}.icon-download{background-position:-120px -24px}.icon-upload{background-position:-144px -24px}.icon-inbox{background-position:-168px -24px}.icon-play-circle{background-position:-192px -24px}.icon-repeat{background-position:-216px -24px}.icon-refresh{background-position:-240px -24px}.icon-list-alt{background-position:-264px -24px}.icon-lock{background-position:-287px -24px}.icon-flag{background-position:-312px -24px}.icon-headphones{background-position:-336px -24px}.icon-volume-off{background-position:-360px -24px}.icon-volume-down{background-position:-384px -24px}.icon-volume-up{background-position:-408px -24px}.icon-qrcode{background-position:-432px -24px}.icon-barcode{background-position:-456px -24px}.icon-tag{background-position:0 -48px}.icon-tags{background-position:-25px -48px}.icon-book{background-position:-48px -48px}.icon-bookmark{background-position:-72px -48px}.icon-print{background-position:-96px -48px}.icon-camera{background-position:-120px -48px}.icon-font{background-position:-144px -48px}.icon-bold{background-position:-167px -48px}.icon-italic{background-position:-192px -48px}.icon-text-height{background-position:-216px -48px}.icon-text-width{background-position:-240px -48px}.icon-align-left{background-position:-264px -48px}.icon-align-center{background-position:-288px -48px}.icon-align-right{background-position:-312px -48px}.icon-align-justify{background-position:-336px -48px}.icon-list{background-position:-360px -48px}.icon-indent-left{background-position:-384px -48px}.icon-indent-right{background-position:-408px -48px}.icon-facetime-video{background-position:-432px -48px}.icon-picture{background-position:-456px -48px}.icon-pencil{background-position:0 -72px}.icon-map-marker{background-position:-24px -72px}.icon-adjust{background-position:-48px -72px}.icon-tint{background-position:-72px -72px}.icon-edit{background-position:-96px -72px}.icon-share{background-position:-120px -72px}.icon-check{background-position:-144px -72px}.icon-move{background-position:-168px -72px}.icon-step-backward{background-position:-192px -72px}.icon-fast-backward{background-position:-216px -72px}.icon-backward{background-position:-240px -72px}.icon-play{background-position:-264px -72px}.icon-pause{background-position:-288px -72px}.icon-stop{background-position:-312px -72px}.icon-forward{background-position:-336px -72px}.icon-fast-forward{background-position:-360px -72px}.icon-step-forward{background-position:-384px -72px}.icon-eject{background-position:-408px -72px}.icon-chevron-left{background-position:-432px -72px}.icon-chevron-right{background-position:-456px -72px}.icon-plus-sign{background-position:0 -96px}.icon-minus-sign{background-position:-24px -96px}.icon-remove-sign{background-position:-48px -96px}.icon-ok-sign{background-position:-72px -96px}.icon-question-sign{background-position:-96px -96px}.icon-info-sign{background-position:-120px -96px}.icon-screenshot{background-position:-144px -96px}.icon-remove-circle{background-position:-168px -96px}.icon-ok-circle{background-position:-192px -96px}.icon-ban-circle{background-position:-216px -96px}.icon-arrow-left{background-position:-240px -96px}.icon-arrow-right{background-position:-264px -96px}.icon-arrow-up{background-position:-289px -96px}.icon-arrow-down{background-position:-312px -96px}.icon-share-alt{background-position:-336px -96px}.icon-resize-full{background-position:-360px -96px}.icon-resize-small{background-position:-384px -96px}.icon-plus{background-position:-408px -96px}.icon-minus{background-position:-433px -96px}.icon-asterisk{background-position:-456px -96px}.icon-exclamation-sign{background-position:0 -120px}.icon-gift{background-position:-24px -120px}.icon-leaf{background-position:-48px -120px}.icon-fire{background-position:-72px -120px}.icon-eye-open{background-position:-96px -120px}.icon-eye-close{background-position:-120px -120px}.icon-warning-sign{background-position:-144px -120px}.icon-plane{background-position:-168px -120px}.icon-calendar{background-position:-192px -120px}.icon-random{width:16px;background-position:-216px -120px}.icon-comment{background-position:-240px -120px}.icon-magnet{background-position:-264px -120px}.icon-chevron-up{background-position:-288px -120px}.icon-chevron-down{background-position:-313px -119px}.icon-retweet{background-position:-336px -120px}.icon-shopping-cart{background-position:-360px -120px}.icon-folder-close{width:16px;background-position:-384px -120px}.icon-folder-open{width:16px;background-position:-408px -120px}.icon-resize-vertical{background-position:-432px -119px}.icon-resize-horizontal{background-position:-456px -118px}.icon-hdd{background-position:0 -144px}.icon-bullhorn{background-position:-24px -144px}.icon-bell{background-position:-48px -144px}.icon-certificate{background-position:-72px -144px}.icon-thumbs-up{background-position:-96px -144px}.icon-thumbs-down{background-position:-120px -144px}.icon-hand-right{background-position:-144px -144px}.icon-hand-left{background-position:-168px -144px}.icon-hand-up{background-position:-192px -144px}.icon-hand-down{background-position:-216px -144px}.icon-circle-arrow-right{background-position:-240px -144px}.icon-circle-arrow-left{background-position:-264px -144px}.icon-circle-arrow-up{background-position:-288px -144px}.icon-circle-arrow-down{background-position:-312px -144px}.icon-globe{background-position:-336px -144px}.icon-wrench{background-position:-360px -144px}.icon-tasks{background-position:-384px -144px}.icon-filter{background-position:-408px -144px}.icon-briefcase{background-position:-432px -144px}.icon-fullscreen{background-position:-456px -144px}.dropup,.dropdown{position:relative}.dropdown-toggle{*margin-bottom:-3px}.dropdown-toggle:active,.open .dropdown-toggle{outline:0}.caret{display:inline-block;width:0;height:0;vertical-align:top;border-top:4px solid #000;border-right:4px solid transparent;border-left:4px solid transparent;content:""}.dropdown .caret{margin-top:8px;margin-left:2px}.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;list-style:none;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);*border-right-width:2px;*border-bottom-width:2px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:20px;color:#333;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus,.dropdown-submenu:hover>a,.dropdown-submenu:focus>a{color:#fff;text-decoration:none;background-color:#0081c2;background-image:-moz-linear-gradient(top,#08c,#0077b3);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#0077b3));background-image:-webkit-linear-gradient(top,#08c,#0077b3);background-image:-o-linear-gradient(top,#08c,#0077b3);background-image:linear-gradient(to bottom,#08c,#0077b3);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0077b3',GradientType=0)}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#fff;text-decoration:none;background-color:#0081c2;background-image:-moz-linear-gradient(top,#08c,#0077b3);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#0077b3));background-image:-webkit-linear-gradient(top,#08c,#0077b3);background-image:-o-linear-gradient(top,#08c,#0077b3);background-image:linear-gradient(to bottom,#08c,#0077b3);background-repeat:repeat-x;outline:0;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0077b3',GradientType=0)}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#999}.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;cursor:default;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open{*z-index:1000}.open>.dropdown-menu{display:block}.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{border-top:0;border-bottom:4px solid #000;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}.dropdown-submenu{position:relative}.dropdown-submenu>.dropdown-menu{top:0;left:100%;margin-top:-6px;margin-left:-1px;-webkit-border-radius:0 6px 6px 6px;-moz-border-radius:0 6px 6px 6px;border-radius:0 6px 6px 6px}.dropdown-submenu:hover>.dropdown-menu{display:block}.dropup .dropdown-submenu>.dropdown-menu{top:auto;bottom:0;margin-top:0;margin-bottom:-2px;-webkit-border-radius:5px 5px 5px 0;-moz-border-radius:5px 5px 5px 0;border-radius:5px 5px 5px 0}.dropdown-submenu>a:after{display:block;float:right;width:0;height:0;margin-top:5px;margin-right:-10px;border-color:transparent;border-left-color:#ccc;border-style:solid;border-width:5px 0 5px 5px;content:" "}.dropdown-submenu:hover>a:after{border-left-color:#fff}.dropdown-submenu.pull-left{float:none}.dropdown-submenu.pull-left>.dropdown-menu{left:-100%;margin-left:10px;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px}.dropdown .dropdown-menu .nav-header{padding-right:20px;padding-left:20px}.typeahead{z-index:1051;margin-top:2px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}.well-large{padding:24px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.well-small{padding:9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.fade{opacity:0;-webkit-transition:opacity .15s linear;-moz-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{position:relative;height:0;overflow:hidden;-webkit-transition:height .35s ease;-moz-transition:height .35s ease;-o-transition:height .35s ease;transition:height .35s ease}.collapse.in{height:auto}.close{float:right;font-size:20px;font-weight:bold;line-height:20px;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)}.close:hover,.close:focus{color:#000;text-decoration:none;cursor:pointer;opacity:.4;filter:alpha(opacity=40)}button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}.btn{display:inline-block;*display:inline;padding:4px 12px;margin-bottom:0;*margin-left:.3em;font-size:14px;line-height:20px;color:#333;text-align:center;text-shadow:0 1px 1px rgba(255,255,255,0.75);vertical-align:middle;cursor:pointer;background-color:#f5f5f5;*background-color:#e6e6e6;background-image:-moz-linear-gradient(top,#fff,#e6e6e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#e6e6e6));background-image:-webkit-linear-gradient(top,#fff,#e6e6e6);background-image:-o-linear-gradient(top,#fff,#e6e6e6);background-image:linear-gradient(to bottom,#fff,#e6e6e6);background-repeat:repeat-x;border:1px solid #ccc;*border:0;border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);border-bottom-color:#b3b3b3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffe6e6e6',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);*zoom:1;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.btn:hover,.btn:focus,.btn:active,.btn.active,.btn.disabled,.btn[disabled]{color:#333;background-color:#e6e6e6;*background-color:#d9d9d9}.btn:active,.btn.active{background-color:#ccc \9}.btn:first-child{*margin-left:0}.btn:hover,.btn:focus{color:#333;text-decoration:none;background-position:0 -15px;-webkit-transition:background-position .1s linear;-moz-transition:background-position .1s linear;-o-transition:background-position .1s linear;transition:background-position .1s linear}.btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.btn.disabled,.btn[disabled]{cursor:default;background-image:none;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn-large{padding:11px 19px;font-size:17.5px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.btn-large [class^="icon-"],.btn-large [class*=" icon-"]{margin-top:4px}.btn-small{padding:2px 10px;font-size:11.9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.btn-small [class^="icon-"],.btn-small [class*=" icon-"]{margin-top:0}.btn-mini [class^="icon-"],.btn-mini [class*=" icon-"]{margin-top:-1px}.btn-mini{padding:0 6px;font-size:10.5px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.btn-block{display:block;width:100%;padding-right:0;padding-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.btn-block+.btn-block{margin-top:5px}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.btn-primary.active,.btn-warning.active,.btn-danger.active,.btn-success.active,.btn-info.active,.btn-inverse.active{color:rgba(255,255,255,0.75)}.btn-primary{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#006dcc;*background-color:#04c;background-image:-moz-linear-gradient(top,#08c,#04c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#04c));background-image:-webkit-linear-gradient(top,#08c,#04c);background-image:-o-linear-gradient(top,#08c,#04c);background-image:linear-gradient(to bottom,#08c,#04c);background-repeat:repeat-x;border-color:#04c #04c #002a80;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0044cc',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.btn-primary.disabled,.btn-primary[disabled]{color:#fff;background-color:#04c;*background-color:#003bb3}.btn-primary:active,.btn-primary.active{background-color:#039 \9}.btn-warning{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#faa732;*background-color:#f89406;background-image:-moz-linear-gradient(top,#fbb450,#f89406);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fbb450),to(#f89406));background-image:-webkit-linear-gradient(top,#fbb450,#f89406);background-image:-o-linear-gradient(top,#fbb450,#f89406);background-image:linear-gradient(to bottom,#fbb450,#f89406);background-repeat:repeat-x;border-color:#f89406 #f89406 #ad6704;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450',endColorstr='#fff89406',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-warning:hover,.btn-warning:focus,.btn-warning:active,.btn-warning.active,.btn-warning.disabled,.btn-warning[disabled]{color:#fff;background-color:#f89406;*background-color:#df8505}.btn-warning:active,.btn-warning.active{background-color:#c67605 \9}.btn-danger{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#da4f49;*background-color:#bd362f;background-image:-moz-linear-gradient(top,#ee5f5b,#bd362f);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ee5f5b),to(#bd362f));background-image:-webkit-linear-gradient(top,#ee5f5b,#bd362f);background-image:-o-linear-gradient(top,#ee5f5b,#bd362f);background-image:linear-gradient(to bottom,#ee5f5b,#bd362f);background-repeat:repeat-x;border-color:#bd362f #bd362f #802420;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b',endColorstr='#ffbd362f',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-danger:hover,.btn-danger:focus,.btn-danger:active,.btn-danger.active,.btn-danger.disabled,.btn-danger[disabled]{color:#fff;background-color:#bd362f;*background-color:#a9302a}.btn-danger:active,.btn-danger.active{background-color:#942a25 \9}.btn-success{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#5bb75b;*background-color:#51a351;background-image:-moz-linear-gradient(top,#62c462,#51a351);background-image:-webkit-gradient(linear,0 0,0 100%,from(#62c462),to(#51a351));background-image:-webkit-linear-gradient(top,#62c462,#51a351);background-image:-o-linear-gradient(top,#62c462,#51a351);background-image:linear-gradient(to bottom,#62c462,#51a351);background-repeat:repeat-x;border-color:#51a351 #51a351 #387038;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462',endColorstr='#ff51a351',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.btn-success.disabled,.btn-success[disabled]{color:#fff;background-color:#51a351;*background-color:#499249}.btn-success:active,.btn-success.active{background-color:#408140 \9}.btn-info{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#49afcd;*background-color:#2f96b4;background-image:-moz-linear-gradient(top,#5bc0de,#2f96b4);background-image:-webkit-gradient(linear,0 0,0 100%,from(#5bc0de),to(#2f96b4));background-image:-webkit-linear-gradient(top,#5bc0de,#2f96b4);background-image:-o-linear-gradient(top,#5bc0de,#2f96b4);background-image:linear-gradient(to bottom,#5bc0de,#2f96b4);background-repeat:repeat-x;border-color:#2f96b4 #2f96b4 #1f6377;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff2f96b4',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.btn-info.disabled,.btn-info[disabled]{color:#fff;background-color:#2f96b4;*background-color:#2a85a0}.btn-info:active,.btn-info.active{background-color:#24748c \9}.btn-inverse{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#363636;*background-color:#222;background-image:-moz-linear-gradient(top,#444,#222);background-image:-webkit-gradient(linear,0 0,0 100%,from(#444),to(#222));background-image:-webkit-linear-gradient(top,#444,#222);background-image:-o-linear-gradient(top,#444,#222);background-image:linear-gradient(to bottom,#444,#222);background-repeat:repeat-x;border-color:#222 #222 #000;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff444444',endColorstr='#ff222222',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-inverse:hover,.btn-inverse:focus,.btn-inverse:active,.btn-inverse.active,.btn-inverse.disabled,.btn-inverse[disabled]{color:#fff;background-color:#222;*background-color:#151515}.btn-inverse:active,.btn-inverse.active{background-color:#080808 \9}button.btn,input[type="submit"].btn{*padding-top:3px;*padding-bottom:3px}button.btn::-moz-focus-inner,input[type="submit"].btn::-moz-focus-inner{padding:0;border:0}button.btn.btn-large,input[type="submit"].btn.btn-large{*padding-top:7px;*padding-bottom:7px}button.btn.btn-small,input[type="submit"].btn.btn-small{*padding-top:3px;*padding-bottom:3px}button.btn.btn-mini,input[type="submit"].btn.btn-mini{*padding-top:1px;*padding-bottom:1px}.btn-link,.btn-link:active,.btn-link[disabled]{background-color:transparent;background-image:none;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn-link{color:#08c;cursor:pointer;border-color:transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-link:hover,.btn-link:focus{color:#005580;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,.btn-link[disabled]:focus{color:#333;text-decoration:none}.btn-group{position:relative;display:inline-block;*display:inline;*margin-left:.3em;font-size:0;white-space:nowrap;vertical-align:middle;*zoom:1}.btn-group:first-child{*margin-left:0}.btn-group+.btn-group{margin-left:5px}.btn-toolbar{margin-top:10px;margin-bottom:10px;font-size:0}.btn-toolbar>.btn+.btn,.btn-toolbar>.btn-group+.btn,.btn-toolbar>.btn+.btn-group{margin-left:5px}.btn-group>.btn{position:relative;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group>.btn+.btn{margin-left:-1px}.btn-group>.btn,.btn-group>.dropdown-menu,.btn-group>.popover{font-size:14px}.btn-group>.btn-mini{font-size:10.5px}.btn-group>.btn-small{font-size:11.9px}.btn-group>.btn-large{font-size:17.5px}.btn-group>.btn:first-child{margin-left:0;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-bottomleft:4px;-moz-border-radius-topleft:4px}.btn-group>.btn:last-child,.btn-group>.dropdown-toggle{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-bottomright:4px}.btn-group>.btn.large:first-child{margin-left:0;-webkit-border-bottom-left-radius:6px;border-bottom-left-radius:6px;-webkit-border-top-left-radius:6px;border-top-left-radius:6px;-moz-border-radius-bottomleft:6px;-moz-border-radius-topleft:6px}.btn-group>.btn.large:last-child,.btn-group>.large.dropdown-toggle{-webkit-border-top-right-radius:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;border-bottom-right-radius:6px;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:6px}.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active{z-index:2}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{*padding-top:5px;padding-right:8px;*padding-bottom:5px;padding-left:8px;-webkit-box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.btn-group>.btn-mini+.dropdown-toggle{*padding-top:2px;padding-right:5px;*padding-bottom:2px;padding-left:5px}.btn-group>.btn-small+.dropdown-toggle{*padding-top:5px;*padding-bottom:4px}.btn-group>.btn-large+.dropdown-toggle{*padding-top:7px;padding-right:12px;*padding-bottom:7px;padding-left:12px}.btn-group.open .dropdown-toggle{background-image:none;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.btn-group.open .btn.dropdown-toggle{background-color:#e6e6e6}.btn-group.open .btn-primary.dropdown-toggle{background-color:#04c}.btn-group.open .btn-warning.dropdown-toggle{background-color:#f89406}.btn-group.open .btn-danger.dropdown-toggle{background-color:#bd362f}.btn-group.open .btn-success.dropdown-toggle{background-color:#51a351}.btn-group.open .btn-info.dropdown-toggle{background-color:#2f96b4}.btn-group.open .btn-inverse.dropdown-toggle{background-color:#222}.btn .caret{margin-top:8px;margin-left:0}.btn-large .caret{margin-top:6px}.btn-large .caret{border-top-width:5px;border-right-width:5px;border-left-width:5px}.btn-mini .caret,.btn-small .caret{margin-top:8px}.dropup .btn-large .caret{border-bottom-width:5px}.btn-primary .caret,.btn-warning .caret,.btn-danger .caret,.btn-info .caret,.btn-success .caret,.btn-inverse .caret{border-top-color:#fff;border-bottom-color:#fff}.btn-group-vertical{display:inline-block;*display:inline;*zoom:1}.btn-group-vertical>.btn{display:block;float:none;max-width:100%;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group-vertical>.btn+.btn{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:first-child{-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0}.btn-group-vertical>.btn:last-child{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}.btn-group-vertical>.btn-large:first-child{-webkit-border-radius:6px 6px 0 0;-moz-border-radius:6px 6px 0 0;border-radius:6px 6px 0 0}.btn-group-vertical>.btn-large:last-child{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px}.alert{padding:8px 35px 8px 14px;margin-bottom:20px;text-shadow:0 1px 0 rgba(255,255,255,0.5);background-color:#fcf8e3;border:1px solid #fbeed5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.alert,.alert h4{color:#c09853}.alert h4{margin:0}.alert .close{position:relative;top:-2px;right:-21px;line-height:20px}.alert-success{color:#468847;background-color:#dff0d8;border-color:#d6e9c6}.alert-success h4{color:#468847}.alert-danger,.alert-error{color:#b94a48;background-color:#f2dede;border-color:#eed3d7}.alert-danger h4,.alert-error h4{color:#b94a48}.alert-info{color:#3a87ad;background-color:#d9edf7;border-color:#bce8f1}.alert-info h4{color:#3a87ad}.alert-block{padding-top:14px;padding-bottom:14px}.alert-block>p,.alert-block>ul{margin-bottom:0}.alert-block p+p{margin-top:5px}.nav{margin-bottom:20px;margin-left:0;list-style:none}.nav>li>a{display:block}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#eee}.nav>li>a>img{max-width:none}.nav>.pull-right{float:right}.nav-header{display:block;padding:3px 15px;font-size:11px;font-weight:bold;line-height:20px;color:#999;text-shadow:0 1px 0 rgba(255,255,255,0.5);text-transform:uppercase}.nav li+.nav-header{margin-top:9px}.nav-list{padding-right:15px;padding-left:15px;margin-bottom:0}.nav-list>li>a,.nav-list .nav-header{margin-right:-15px;margin-left:-15px;text-shadow:0 1px 0 rgba(255,255,255,0.5)}.nav-list>li>a{padding:3px 15px}.nav-list>.active>a,.nav-list>.active>a:hover,.nav-list>.active>a:focus{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.2);background-color:#08c}.nav-list [class^="icon-"],.nav-list [class*=" icon-"]{margin-right:2px}.nav-list .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}.nav-tabs,.nav-pills{*zoom:1}.nav-tabs:before,.nav-pills:before,.nav-tabs:after,.nav-pills:after{display:table;line-height:0;content:""}.nav-tabs:after,.nav-pills:after{clear:both}.nav-tabs>li,.nav-pills>li{float:left}.nav-tabs>li>a,.nav-pills>li>a{padding-right:12px;padding-left:12px;margin-right:2px;line-height:14px}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{margin-bottom:-1px}.nav-tabs>li>a{padding-top:8px;padding-bottom:8px;line-height:20px;border:1px solid transparent;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover,.nav-tabs>li>a:focus{border-color:#eee #eee #ddd}.nav-tabs>.active>a,.nav-tabs>.active>a:hover,.nav-tabs>.active>a:focus{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-pills>li>a{padding-top:8px;padding-bottom:8px;margin-top:2px;margin-bottom:2px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.nav-pills>.active>a,.nav-pills>.active>a:hover,.nav-pills>.active>a:focus{color:#fff;background-color:#08c}.nav-stacked>li{float:none}.nav-stacked>li>a{margin-right:0}.nav-tabs.nav-stacked{border-bottom:0}.nav-tabs.nav-stacked>li>a{border:1px solid #ddd;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.nav-tabs.nav-stacked>li:first-child>a{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-topleft:4px}.nav-tabs.nav-stacked>li:last-child>a{-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomright:4px;-moz-border-radius-bottomleft:4px}.nav-tabs.nav-stacked>li>a:hover,.nav-tabs.nav-stacked>li>a:focus{z-index:2;border-color:#ddd}.nav-pills.nav-stacked>li>a{margin-bottom:3px}.nav-pills.nav-stacked>li:last-child>a{margin-bottom:1px}.nav-tabs .dropdown-menu{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px}.nav-pills .dropdown-menu{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.nav .dropdown-toggle .caret{margin-top:6px;border-top-color:#08c;border-bottom-color:#08c}.nav .dropdown-toggle:hover .caret,.nav .dropdown-toggle:focus .caret{border-top-color:#005580;border-bottom-color:#005580}.nav-tabs .dropdown-toggle .caret{margin-top:8px}.nav .active .dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.nav-tabs .active .dropdown-toggle .caret{border-top-color:#555;border-bottom-color:#555}.nav>.dropdown.active>a:hover,.nav>.dropdown.active>a:focus{cursor:pointer}.nav-tabs .open .dropdown-toggle,.nav-pills .open .dropdown-toggle,.nav>li.dropdown.open.active>a:hover,.nav>li.dropdown.open.active>a:focus{color:#fff;background-color:#999;border-color:#999}.nav li.dropdown.open .caret,.nav li.dropdown.open.active .caret,.nav li.dropdown.open a:hover .caret,.nav li.dropdown.open a:focus .caret{border-top-color:#fff;border-bottom-color:#fff;opacity:1;filter:alpha(opacity=100)}.tabs-stacked .open>a:hover,.tabs-stacked .open>a:focus{border-color:#999}.tabbable{*zoom:1}.tabbable:before,.tabbable:after{display:table;line-height:0;content:""}.tabbable:after{clear:both}.tab-content{overflow:auto}.tabs-below>.nav-tabs,.tabs-right>.nav-tabs,.tabs-left>.nav-tabs{border-bottom:0}.tab-content>.tab-pane,.pill-content>.pill-pane{display:none}.tab-content>.active,.pill-content>.active{display:block}.tabs-below>.nav-tabs{border-top:1px solid #ddd}.tabs-below>.nav-tabs>li{margin-top:-1px;margin-bottom:0}.tabs-below>.nav-tabs>li>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}.tabs-below>.nav-tabs>li>a:hover,.tabs-below>.nav-tabs>li>a:focus{border-top-color:#ddd;border-bottom-color:transparent}.tabs-below>.nav-tabs>.active>a,.tabs-below>.nav-tabs>.active>a:hover,.tabs-below>.nav-tabs>.active>a:focus{border-color:transparent #ddd #ddd #ddd}.tabs-left>.nav-tabs>li,.tabs-right>.nav-tabs>li{float:none}.tabs-left>.nav-tabs>li>a,.tabs-right>.nav-tabs>li>a{min-width:74px;margin-right:0;margin-bottom:3px}.tabs-left>.nav-tabs{float:left;margin-right:19px;border-right:1px solid #ddd}.tabs-left>.nav-tabs>li>a{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.tabs-left>.nav-tabs>li>a:hover,.tabs-left>.nav-tabs>li>a:focus{border-color:#eee #ddd #eee #eee}.tabs-left>.nav-tabs .active>a,.tabs-left>.nav-tabs .active>a:hover,.tabs-left>.nav-tabs .active>a:focus{border-color:#ddd transparent #ddd #ddd;*border-right-color:#fff}.tabs-right>.nav-tabs{float:right;margin-left:19px;border-left:1px solid #ddd}.tabs-right>.nav-tabs>li>a{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.tabs-right>.nav-tabs>li>a:hover,.tabs-right>.nav-tabs>li>a:focus{border-color:#eee #eee #eee #ddd}.tabs-right>.nav-tabs .active>a,.tabs-right>.nav-tabs .active>a:hover,.tabs-right>.nav-tabs .active>a:focus{border-color:#ddd #ddd #ddd transparent;*border-left-color:#fff}.nav>.disabled>a{color:#999}.nav>.disabled>a:hover,.nav>.disabled>a:focus{text-decoration:none;cursor:default;background-color:transparent}.navbar{*position:relative;*z-index:2;margin-bottom:20px;overflow:visible}.navbar-inner{min-height:40px;padding-right:20px;padding-left:20px;background-color:#fafafa;background-image:-moz-linear-gradient(top,#fff,#f2f2f2);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#f2f2f2));background-image:-webkit-linear-gradient(top,#fff,#f2f2f2);background-image:-o-linear-gradient(top,#fff,#f2f2f2);background-image:linear-gradient(to bottom,#fff,#f2f2f2);background-repeat:repeat-x;border:1px solid #d4d4d4;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#fff2f2f2',GradientType=0);*zoom:1;-webkit-box-shadow:0 1px 4px rgba(0,0,0,0.065);-moz-box-shadow:0 1px 4px rgba(0,0,0,0.065);box-shadow:0 1px 4px rgba(0,0,0,0.065)}.navbar-inner:before,.navbar-inner:after{display:table;line-height:0;content:""}.navbar-inner:after{clear:both}.navbar .container{width:auto}.nav-collapse.collapse{height:auto;overflow:visible}.navbar .brand{display:block;float:left;padding:10px 20px 10px;margin-left:-20px;font-size:20px;font-weight:200;color:#777;text-shadow:0 1px 0 #fff}.navbar .brand:hover,.navbar .brand:focus{text-decoration:none}.navbar-text{margin-bottom:0;line-height:40px;color:#777}.navbar-link{color:#777}.navbar-link:hover,.navbar-link:focus{color:#333}.navbar .divider-vertical{height:40px;margin:0 9px;border-right:1px solid #fff;border-left:1px solid #f2f2f2}.navbar .btn,.navbar .btn-group{margin-top:5px}.navbar .btn-group .btn,.navbar .input-prepend .btn,.navbar .input-append .btn,.navbar .input-prepend .btn-group,.navbar .input-append .btn-group{margin-top:0}.navbar-form{margin-bottom:0;*zoom:1}.navbar-form:before,.navbar-form:after{display:table;line-height:0;content:""}.navbar-form:after{clear:both}.navbar-form input,.navbar-form select,.navbar-form .radio,.navbar-form .checkbox{margin-top:5px}.navbar-form input,.navbar-form select,.navbar-form .btn{display:inline-block;margin-bottom:0}.navbar-form input[type="image"],.navbar-form input[type="checkbox"],.navbar-form input[type="radio"]{margin-top:3px}.navbar-form .input-append,.navbar-form .input-prepend{margin-top:5px;white-space:nowrap}.navbar-form .input-append input,.navbar-form .input-prepend input{margin-top:0}.navbar-search{position:relative;float:left;margin-top:5px;margin-bottom:0}.navbar-search .search-query{padding:4px 14px;margin-bottom:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:1;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.navbar-static-top{position:static;margin-bottom:0}.navbar-static-top .navbar-inner{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030;margin-bottom:0}.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{border-width:0 0 1px}.navbar-fixed-bottom .navbar-inner{border-width:1px 0 0}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding-right:0;padding-left:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}.navbar-fixed-top{top:0}.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{-webkit-box-shadow:0 1px 10px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 10px rgba(0,0,0,0.1);box-shadow:0 1px 10px rgba(0,0,0,0.1)}.navbar-fixed-bottom{bottom:0}.navbar-fixed-bottom .navbar-inner{-webkit-box-shadow:0 -1px 10px rgba(0,0,0,0.1);-moz-box-shadow:0 -1px 10px rgba(0,0,0,0.1);box-shadow:0 -1px 10px rgba(0,0,0,0.1)}.navbar .nav{position:relative;left:0;display:block;float:left;margin:0 10px 0 0}.navbar .nav.pull-right{float:right;margin-right:0}.navbar .nav>li{float:left}.navbar .nav>li>a{float:none;padding:10px 15px 10px;color:#777;text-decoration:none;text-shadow:0 1px 0 #fff}.navbar .nav .dropdown-toggle .caret{margin-top:8px}.navbar .nav>li>a:focus,.navbar .nav>li>a:hover{color:#333;text-decoration:none;background-color:transparent}.navbar .nav>.active>a,.navbar .nav>.active>a:hover,.navbar .nav>.active>a:focus{color:#555;text-decoration:none;background-color:#e5e5e5;-webkit-box-shadow:inset 0 3px 8px rgba(0,0,0,0.125);-moz-box-shadow:inset 0 3px 8px rgba(0,0,0,0.125);box-shadow:inset 0 3px 8px rgba(0,0,0,0.125)}.navbar .btn-navbar{display:none;float:right;padding:7px 10px;margin-right:5px;margin-left:5px;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#ededed;*background-color:#e5e5e5;background-image:-moz-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f2f2f2),to(#e5e5e5));background-image:-webkit-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:-o-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:linear-gradient(to bottom,#f2f2f2,#e5e5e5);background-repeat:repeat-x;border-color:#e5e5e5 #e5e5e5 #bfbfbf;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2f2f2',endColorstr='#ffe5e5e5',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075)}.navbar .btn-navbar:hover,.navbar .btn-navbar:focus,.navbar .btn-navbar:active,.navbar .btn-navbar.active,.navbar .btn-navbar.disabled,.navbar .btn-navbar[disabled]{color:#fff;background-color:#e5e5e5;*background-color:#d9d9d9}.navbar .btn-navbar:active,.navbar .btn-navbar.active{background-color:#ccc \9}.navbar .btn-navbar .icon-bar{display:block;width:18px;height:2px;background-color:#f5f5f5;-webkit-border-radius:1px;-moz-border-radius:1px;border-radius:1px;-webkit-box-shadow:0 1px 0 rgba(0,0,0,0.25);-moz-box-shadow:0 1px 0 rgba(0,0,0,0.25);box-shadow:0 1px 0 rgba(0,0,0,0.25)}.btn-navbar .icon-bar+.icon-bar{margin-top:3px}.navbar .nav>li>.dropdown-menu:before{position:absolute;top:-7px;left:9px;display:inline-block;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-left:7px solid transparent;border-bottom-color:rgba(0,0,0,0.2);content:''}.navbar .nav>li>.dropdown-menu:after{position:absolute;top:-6px;left:10px;display:inline-block;border-right:6px solid transparent;border-bottom:6px solid #fff;border-left:6px solid transparent;content:''}.navbar-fixed-bottom .nav>li>.dropdown-menu:before{top:auto;bottom:-7px;border-top:7px solid #ccc;border-bottom:0;border-top-color:rgba(0,0,0,0.2)}.navbar-fixed-bottom .nav>li>.dropdown-menu:after{top:auto;bottom:-6px;border-top:6px solid #fff;border-bottom:0}.navbar .nav li.dropdown>a:hover .caret,.navbar .nav li.dropdown>a:focus .caret{border-top-color:#333;border-bottom-color:#333}.navbar .nav li.dropdown.open>.dropdown-toggle,.navbar .nav li.dropdown.active>.dropdown-toggle,.navbar .nav li.dropdown.open.active>.dropdown-toggle{color:#555;background-color:#e5e5e5}.navbar .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#777;border-bottom-color:#777}.navbar .nav li.dropdown.open>.dropdown-toggle .caret,.navbar .nav li.dropdown.active>.dropdown-toggle .caret,.navbar .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#555;border-bottom-color:#555}.navbar .pull-right>li>.dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right{right:0;left:auto}.navbar .pull-right>li>.dropdown-menu:before,.navbar .nav>li>.dropdown-menu.pull-right:before{right:12px;left:auto}.navbar .pull-right>li>.dropdown-menu:after,.navbar .nav>li>.dropdown-menu.pull-right:after{right:13px;left:auto}.navbar .pull-right>li>.dropdown-menu .dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right .dropdown-menu{right:100%;left:auto;margin-right:-1px;margin-left:0;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px}.navbar-inverse .navbar-inner{background-color:#1b1b1b;background-image:-moz-linear-gradient(top,#222,#111);background-image:-webkit-gradient(linear,0 0,0 100%,from(#222),to(#111));background-image:-webkit-linear-gradient(top,#222,#111);background-image:-o-linear-gradient(top,#222,#111);background-image:linear-gradient(to bottom,#222,#111);background-repeat:repeat-x;border-color:#252525;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222',endColorstr='#ff111111',GradientType=0)}.navbar-inverse .brand,.navbar-inverse .nav>li>a{color:#999;text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.navbar-inverse .brand:hover,.navbar-inverse .nav>li>a:hover,.navbar-inverse .brand:focus,.navbar-inverse .nav>li>a:focus{color:#fff}.navbar-inverse .brand{color:#999}.navbar-inverse .navbar-text{color:#999}.navbar-inverse .nav>li>a:focus,.navbar-inverse .nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .nav .active>a,.navbar-inverse .nav .active>a:hover,.navbar-inverse .nav .active>a:focus{color:#fff;background-color:#111}.navbar-inverse .navbar-link{color:#999}.navbar-inverse .navbar-link:hover,.navbar-inverse .navbar-link:focus{color:#fff}.navbar-inverse .divider-vertical{border-right-color:#222;border-left-color:#111}.navbar-inverse .nav li.dropdown.open>.dropdown-toggle,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle{color:#fff;background-color:#111}.navbar-inverse .nav li.dropdown>a:hover .caret,.navbar-inverse .nav li.dropdown>a:focus .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-inverse .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#999;border-bottom-color:#999}.navbar-inverse .nav li.dropdown.open>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-inverse .navbar-search .search-query{color:#fff;background-color:#515151;border-color:#111;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);-webkit-transition:none;-moz-transition:none;-o-transition:none;transition:none}.navbar-inverse .navbar-search .search-query:-moz-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query:-ms-input-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query::-webkit-input-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query:focus,.navbar-inverse .navbar-search .search-query.focused{padding:5px 15px;color:#333;text-shadow:0 1px 0 #fff;background-color:#fff;border:0;outline:0;-webkit-box-shadow:0 0 3px rgba(0,0,0,0.15);-moz-box-shadow:0 0 3px rgba(0,0,0,0.15);box-shadow:0 0 3px rgba(0,0,0,0.15)}.navbar-inverse .btn-navbar{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#0e0e0e;*background-color:#040404;background-image:-moz-linear-gradient(top,#151515,#040404);background-image:-webkit-gradient(linear,0 0,0 100%,from(#151515),to(#040404));background-image:-webkit-linear-gradient(top,#151515,#040404);background-image:-o-linear-gradient(top,#151515,#040404);background-image:linear-gradient(to bottom,#151515,#040404);background-repeat:repeat-x;border-color:#040404 #040404 #000;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff151515',endColorstr='#ff040404',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.navbar-inverse .btn-navbar:hover,.navbar-inverse .btn-navbar:focus,.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active,.navbar-inverse .btn-navbar.disabled,.navbar-inverse .btn-navbar[disabled]{color:#fff;background-color:#040404;*background-color:#000}.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active{background-color:#000 \9}.breadcrumb{padding:8px 15px;margin:0 0 20px;list-style:none;background-color:#f5f5f5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.breadcrumb>li{display:inline-block;*display:inline;text-shadow:0 1px 0 #fff;*zoom:1}.breadcrumb>li>.divider{padding:0 5px;color:#ccc}.breadcrumb>.active{color:#999}.pagination{margin:20px 0}.pagination ul{display:inline-block;*display:inline;margin-bottom:0;margin-left:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;*zoom:1;-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:0 1px 2px rgba(0,0,0,0.05);box-shadow:0 1px 2px rgba(0,0,0,0.05)}.pagination ul>li{display:inline}.pagination ul>li>a,.pagination ul>li>span{float:left;padding:4px 12px;line-height:20px;text-decoration:none;background-color:#fff;border:1px solid #ddd;border-left-width:0}.pagination ul>li>a:hover,.pagination ul>li>a:focus,.pagination ul>.active>a,.pagination ul>.active>span{background-color:#f5f5f5}.pagination ul>.active>a,.pagination ul>.active>span{color:#999;cursor:default}.pagination ul>.disabled>span,.pagination ul>.disabled>a,.pagination ul>.disabled>a:hover,.pagination ul>.disabled>a:focus{color:#999;cursor:default;background-color:transparent}.pagination ul>li:first-child>a,.pagination ul>li:first-child>span{border-left-width:1px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-bottomleft:4px;-moz-border-radius-topleft:4px}.pagination ul>li:last-child>a,.pagination ul>li:last-child>span{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-bottomright:4px}.pagination-centered{text-align:center}.pagination-right{text-align:right}.pagination-large ul>li>a,.pagination-large ul>li>span{padding:11px 19px;font-size:17.5px}.pagination-large ul>li:first-child>a,.pagination-large ul>li:first-child>span{-webkit-border-bottom-left-radius:6px;border-bottom-left-radius:6px;-webkit-border-top-left-radius:6px;border-top-left-radius:6px;-moz-border-radius-bottomleft:6px;-moz-border-radius-topleft:6px}.pagination-large ul>li:last-child>a,.pagination-large ul>li:last-child>span{-webkit-border-top-right-radius:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;border-bottom-right-radius:6px;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:6px}.pagination-mini ul>li:first-child>a,.pagination-small ul>li:first-child>a,.pagination-mini ul>li:first-child>span,.pagination-small ul>li:first-child>span{-webkit-border-bottom-left-radius:3px;border-bottom-left-radius:3px;-webkit-border-top-left-radius:3px;border-top-left-radius:3px;-moz-border-radius-bottomleft:3px;-moz-border-radius-topleft:3px}.pagination-mini ul>li:last-child>a,.pagination-small ul>li:last-child>a,.pagination-mini ul>li:last-child>span,.pagination-small ul>li:last-child>span{-webkit-border-top-right-radius:3px;border-top-right-radius:3px;-webkit-border-bottom-right-radius:3px;border-bottom-right-radius:3px;-moz-border-radius-topright:3px;-moz-border-radius-bottomright:3px}.pagination-small ul>li>a,.pagination-small ul>li>span{padding:2px 10px;font-size:11.9px}.pagination-mini ul>li>a,.pagination-mini ul>li>span{padding:0 6px;font-size:10.5px}.pager{margin:20px 0;text-align:center;list-style:none;*zoom:1}.pager:before,.pager:after{display:table;line-height:0;content:""}.pager:after{clear:both}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#f5f5f5}.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:#999;cursor:default;background-color:#fff}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop,.modal-backdrop.fade.in{opacity:.8;filter:alpha(opacity=80)}.modal{position:fixed;top:10%;left:50%;z-index:1050;width:560px;margin-left:-280px;background-color:#fff;border:1px solid #999;border:1px solid rgba(0,0,0,0.3);*border:1px solid #999;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;outline:0;-webkit-box-shadow:0 3px 7px rgba(0,0,0,0.3);-moz-box-shadow:0 3px 7px rgba(0,0,0,0.3);box-shadow:0 3px 7px rgba(0,0,0,0.3);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box}.modal.fade{top:-25%;-webkit-transition:opacity .3s linear,top .3s ease-out;-moz-transition:opacity .3s linear,top .3s ease-out;-o-transition:opacity .3s linear,top .3s ease-out;transition:opacity .3s linear,top .3s ease-out}.modal.fade.in{top:10%}.modal-header{padding:9px 15px;border-bottom:1px solid #eee}.modal-header .close{margin-top:2px}.modal-header h3{margin:0;line-height:30px}.modal-body{position:relative;max-height:400px;padding:15px;overflow-y:auto}.modal-form{margin-bottom:0}.modal-footer{padding:14px 15px 15px;margin-bottom:0;text-align:right;background-color:#f5f5f5;border-top:1px solid #ddd;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;*zoom:1;-webkit-box-shadow:inset 0 1px 0 #fff;-moz-box-shadow:inset 0 1px 0 #fff;box-shadow:inset 0 1px 0 #fff}.modal-footer:before,.modal-footer:after{display:table;line-height:0;content:""}.modal-footer:after{clear:both}.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}.tooltip{position:absolute;z-index:1030;display:block;font-size:11px;line-height:1.4;opacity:0;filter:alpha(opacity=0);visibility:visible}.tooltip.in{opacity:.8;filter:alpha(opacity=80)}.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:8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;-webkit-border-radius:4px;-moz-border-radius:4px;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-top-color:#000;border-width:5px 5px 0}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-right-color:#000;border-width:5px 5px 5px 0}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-left-color:#000;border-width:5px 0 5px 5px}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-bottom-color:#000;border-width:0 5px 5px}.popover{position:absolute;top:0;left:0;z-index:1010;display:none;max-width:276px;padding:1px;text-align:left;white-space:normal;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.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:normal;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0}.popover-title:empty{display:none}.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{border-width:10px;content:""}.popover.top .arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,0.25);border-bottom-width:0}.popover.top .arrow:after{bottom:1px;margin-left:-10px;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,0.25);border-left-width:0}.popover.right .arrow:after{bottom:-10px;left:1px;border-right-color:#fff;border-left-width:0}.popover.bottom .arrow{top:-11px;left:50%;margin-left:-11px;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,0.25);border-top-width:0}.popover.bottom .arrow:after{top:1px;margin-left:-10px;border-bottom-color:#fff;border-top-width:0}.popover.left .arrow{top:50%;right:-11px;margin-top:-11px;border-left-color:#999;border-left-color:rgba(0,0,0,0.25);border-right-width:0}.popover.left .arrow:after{right:1px;bottom:-10px;border-left-color:#fff;border-right-width:0}.thumbnails{margin-left:-20px;list-style:none;*zoom:1}.thumbnails:before,.thumbnails:after{display:table;line-height:0;content:""}.thumbnails:after{clear:both}.row-fluid .thumbnails{margin-left:0}.thumbnails>li{float:left;margin-bottom:20px;margin-left:20px}.thumbnail{display:block;padding:4px;line-height:20px;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.055);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.055);box-shadow:0 1px 3px rgba(0,0,0,0.055);-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}a.thumbnail:hover,a.thumbnail:focus{border-color:#08c;-webkit-box-shadow:0 1px 4px rgba(0,105,214,0.25);-moz-box-shadow:0 1px 4px rgba(0,105,214,0.25);box-shadow:0 1px 4px rgba(0,105,214,0.25)}.thumbnail>img{display:block;max-width:100%;margin-right:auto;margin-left:auto}.thumbnail .caption{padding:9px;color:#555}.media,.media-body{overflow:hidden;*overflow:visible;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{margin-left:0;list-style:none}.label,.badge{display:inline-block;padding:2px 4px;font-size:11.844px;font-weight:bold;line-height:14px;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);white-space:nowrap;vertical-align:baseline;background-color:#999}.label{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.badge{padding-right:9px;padding-left:9px;-webkit-border-radius:9px;-moz-border-radius:9px;border-radius:9px}.label:empty,.badge:empty{display:none}a.label:hover,a.label:focus,a.badge:hover,a.badge:focus{color:#fff;text-decoration:none;cursor:pointer}.label-important,.badge-important{background-color:#b94a48}.label-important[href],.badge-important[href]{background-color:#953b39}.label-warning,.badge-warning{background-color:#f89406}.label-warning[href],.badge-warning[href]{background-color:#c67605}.label-success,.badge-success{background-color:#468847}.label-success[href],.badge-success[href]{background-color:#356635}.label-info,.badge-info{background-color:#3a87ad}.label-info[href],.badge-info[href]{background-color:#2d6987}.label-inverse,.badge-inverse{background-color:#333}.label-inverse[href],.badge-inverse[href]{background-color:#1a1a1a}.btn .label,.btn .badge{position:relative;top:-1px}.btn-mini .label,.btn-mini .badge{top:0}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-moz-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-ms-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:0 0}to{background-position:40px 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:#f7f7f7;background-image:-moz-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f5f5f5),to(#f9f9f9));background-image:-webkit-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:-o-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:linear-gradient(to bottom,#f5f5f5,#f9f9f9);background-repeat:repeat-x;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5',endColorstr='#fff9f9f9',GradientType=0);-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1)}.progress .bar{float:left;width:0;height:100%;font-size:12px;color:#fff;text-align:center;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#0e90d2;background-image:-moz-linear-gradient(top,#149bdf,#0480be);background-image:-webkit-gradient(linear,0 0,0 100%,from(#149bdf),to(#0480be));background-image:-webkit-linear-gradient(top,#149bdf,#0480be);background-image:-o-linear-gradient(top,#149bdf,#0480be);background-image:linear-gradient(to bottom,#149bdf,#0480be);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf',endColorstr='#ff0480be',GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-moz-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-transition:width .6s ease;-moz-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress .bar+.bar{-webkit-box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15);-moz-box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15)}.progress-striped .bar{background-color:#149bdf;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;-moz-background-size:40px 40px;-o-background-size:40px 40px;background-size:40px 40px}.progress.active .bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-moz-animation:progress-bar-stripes 2s linear infinite;-ms-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-danger .bar,.progress .bar-danger{background-color:#dd514c;background-image:-moz-linear-gradient(top,#ee5f5b,#c43c35);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ee5f5b),to(#c43c35));background-image:-webkit-linear-gradient(top,#ee5f5b,#c43c35);background-image:-o-linear-gradient(top,#ee5f5b,#c43c35);background-image:linear-gradient(to bottom,#ee5f5b,#c43c35);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b',endColorstr='#ffc43c35',GradientType=0)}.progress-danger.progress-striped .bar,.progress-striped .bar-danger{background-color:#ee5f5b;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-success .bar,.progress .bar-success{background-color:#5eb95e;background-image:-moz-linear-gradient(top,#62c462,#57a957);background-image:-webkit-gradient(linear,0 0,0 100%,from(#62c462),to(#57a957));background-image:-webkit-linear-gradient(top,#62c462,#57a957);background-image:-o-linear-gradient(top,#62c462,#57a957);background-image:linear-gradient(to bottom,#62c462,#57a957);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462',endColorstr='#ff57a957',GradientType=0)}.progress-success.progress-striped .bar,.progress-striped .bar-success{background-color:#62c462;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-info .bar,.progress .bar-info{background-color:#4bb1cf;background-image:-moz-linear-gradient(top,#5bc0de,#339bb9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#5bc0de),to(#339bb9));background-image:-webkit-linear-gradient(top,#5bc0de,#339bb9);background-image:-o-linear-gradient(top,#5bc0de,#339bb9);background-image:linear-gradient(to bottom,#5bc0de,#339bb9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff339bb9',GradientType=0)}.progress-info.progress-striped .bar,.progress-striped .bar-info{background-color:#5bc0de;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-warning .bar,.progress .bar-warning{background-color:#faa732;background-image:-moz-linear-gradient(top,#fbb450,#f89406);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fbb450),to(#f89406));background-image:-webkit-linear-gradient(top,#fbb450,#f89406);background-image:-o-linear-gradient(top,#fbb450,#f89406);background-image:linear-gradient(to bottom,#fbb450,#f89406);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450',endColorstr='#fff89406',GradientType=0)}.progress-warning.progress-striped .bar,.progress-striped .bar-warning{background-color:#fbb450;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.accordion{margin-bottom:20px}.accordion-group{margin-bottom:2px;border:1px solid #e5e5e5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.accordion-heading{border-bottom:0}.accordion-heading .accordion-toggle{display:block;padding:8px 15px}.accordion-toggle{cursor:pointer}.accordion-inner{padding:9px 15px;border-top:1px solid #e5e5e5}.carousel{position:relative;margin-bottom:20px;line-height:1}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-moz-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{display:block;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:40%;left:15px;width:40px;height:40px;margin-top:-20px;font-size:60px;font-weight:100;line-height:30px;color:#fff;text-align:center;background:#222;border:3px solid #fff;-webkit-border-radius:23px;-moz-border-radius:23px;border-radius:23px;opacity:.5;filter:alpha(opacity=50)}.carousel-control.right{right:15px;left:auto}.carousel-control:hover,.carousel-control:focus{color:#fff;text-decoration:none;opacity:.9;filter:alpha(opacity=90)}.carousel-indicators{position:absolute;top:15px;right:15px;z-index:5;margin:0;list-style:none}.carousel-indicators li{display:block;float:left;width:10px;height:10px;margin-left:5px;text-indent:-999px;background-color:#ccc;background-color:rgba(255,255,255,0.25);border-radius:5px}.carousel-indicators .active{background-color:#fff}.carousel-caption{position:absolute;right:0;bottom:0;left:0;padding:15px;background:#333;background:rgba(0,0,0,0.75)}.carousel-caption h4,.carousel-caption p{line-height:20px;color:#fff}.carousel-caption h4{margin:0 0 5px}.carousel-caption p{margin-bottom:0}.hero-unit{padding:60px;margin-bottom:30px;font-size:18px;font-weight:200;line-height:30px;color:inherit;background-color:#eee;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;letter-spacing:-1px;color:inherit}.hero-unit li{line-height:30px}.pull-right{float:right}.pull-left{float:left}.hide{display:none}.show{display:block}.invisible{visibility:hidden}.affix{position:fixed} diff --git a/examples/quickstart/src/main/webapp/static/bootstrap/2.3.2/img/glyphicons-halflings-white.png b/examples/quickstart/src/main/webapp/static/bootstrap/2.3.2/img/glyphicons-halflings-white.png deleted file mode 100644 index 3bf6484a2..000000000 Binary files a/examples/quickstart/src/main/webapp/static/bootstrap/2.3.2/img/glyphicons-halflings-white.png and /dev/null differ diff --git a/examples/quickstart/src/main/webapp/static/bootstrap/2.3.2/img/glyphicons-halflings.png b/examples/quickstart/src/main/webapp/static/bootstrap/2.3.2/img/glyphicons-halflings.png deleted file mode 100644 index a99699932..000000000 Binary files a/examples/quickstart/src/main/webapp/static/bootstrap/2.3.2/img/glyphicons-halflings.png and /dev/null differ diff --git a/examples/quickstart/src/main/webapp/static/bootstrap/2.3.2/js/bootstrap.min.js b/examples/quickstart/src/main/webapp/static/bootstrap/2.3.2/js/bootstrap.min.js deleted file mode 100644 index f9cbdae7c..000000000 --- a/examples/quickstart/src/main/webapp/static/bootstrap/2.3.2/js/bootstrap.min.js +++ /dev/null @@ -1,6 +0,0 @@ -/*! -* Bootstrap.js by @fat & @mdo -* Copyright 2012 Twitter, Inc. -* http://www.apache.org/licenses/LICENSE-2.0.txt -*/ -!function(e){"use strict";e(function(){e.support.transition=function(){var e=function(){var e=document.createElement("bootstrap"),t={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"},n;for(n in t)if(e.style[n]!==undefined)return t[n]}();return e&&{end:e}}()})}(window.jQuery),!function(e){"use strict";var t='[data-dismiss="alert"]',n=function(n){e(n).on("click",t,this.close)};n.prototype.close=function(t){function s(){i.trigger("closed").remove()}var n=e(this),r=n.attr("data-target"),i;r||(r=n.attr("href"),r=r&&r.replace(/.*(?=#[^\s]*$)/,"")),i=e(r),t&&t.preventDefault(),i.length||(i=n.hasClass("alert")?n:n.parent()),i.trigger(t=e.Event("close"));if(t.isDefaultPrevented())return;i.removeClass("in"),e.support.transition&&i.hasClass("fade")?i.on(e.support.transition.end,s):s()};var r=e.fn.alert;e.fn.alert=function(t){return this.each(function(){var r=e(this),i=r.data("alert");i||r.data("alert",i=new n(this)),typeof t=="string"&&i[t].call(r)})},e.fn.alert.Constructor=n,e.fn.alert.noConflict=function(){return e.fn.alert=r,this},e(document).on("click.alert.data-api",t,n.prototype.close)}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.button.defaults,n)};t.prototype.setState=function(e){var t="disabled",n=this.$element,r=n.data(),i=n.is("input")?"val":"html";e+="Text",r.resetText||n.data("resetText",n[i]()),n[i](r[e]||this.options[e]),setTimeout(function(){e=="loadingText"?n.addClass(t).attr(t,t):n.removeClass(t).removeAttr(t)},0)},t.prototype.toggle=function(){var e=this.$element.closest('[data-toggle="buttons-radio"]');e&&e.find(".active").removeClass("active"),this.$element.toggleClass("active")};var n=e.fn.button;e.fn.button=function(n){return this.each(function(){var r=e(this),i=r.data("button"),s=typeof n=="object"&&n;i||r.data("button",i=new t(this,s)),n=="toggle"?i.toggle():n&&i.setState(n)})},e.fn.button.defaults={loadingText:"loading..."},e.fn.button.Constructor=t,e.fn.button.noConflict=function(){return e.fn.button=n,this},e(document).on("click.button.data-api","[data-toggle^=button]",function(t){var n=e(t.target);n.hasClass("btn")||(n=n.closest(".btn")),n.button("toggle")})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.$indicators=this.$element.find(".carousel-indicators"),this.options=n,this.options.pause=="hover"&&this.$element.on("mouseenter",e.proxy(this.pause,this)).on("mouseleave",e.proxy(this.cycle,this))};t.prototype={cycle:function(t){return t||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(e.proxy(this.next,this),this.options.interval)),this},getActiveIndex:function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},to:function(t){var n=this.getActiveIndex(),r=this;if(t>this.$items.length-1||t<0)return;return this.sliding?this.$element.one("slid",function(){r.to(t)}):n==t?this.pause().cycle():this.slide(t>n?"next":"prev",e(this.$items[t]))},pause:function(t){return t||(this.paused=!0),this.$element.find(".next, .prev").length&&e.support.transition.end&&(this.$element.trigger(e.support.transition.end),this.cycle(!0)),clearInterval(this.interval),this.interval=null,this},next:function(){if(this.sliding)return;return this.slide("next")},prev:function(){if(this.sliding)return;return this.slide("prev")},slide:function(t,n){var r=this.$element.find(".item.active"),i=n||r[t](),s=this.interval,o=t=="next"?"left":"right",u=t=="next"?"first":"last",a=this,f;this.sliding=!0,s&&this.pause(),i=i.length?i:this.$element.find(".item")[u](),f=e.Event("slide",{relatedTarget:i[0],direction:o});if(i.hasClass("active"))return;this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid",function(){var t=e(a.$indicators.children()[a.getActiveIndex()]);t&&t.addClass("active")}));if(e.support.transition&&this.$element.hasClass("slide")){this.$element.trigger(f);if(f.isDefaultPrevented())return;i.addClass(t),i[0].offsetWidth,r.addClass(o),i.addClass(o),this.$element.one(e.support.transition.end,function(){i.removeClass([t,o].join(" ")).addClass("active"),r.removeClass(["active",o].join(" ")),a.sliding=!1,setTimeout(function(){a.$element.trigger("slid")},0)})}else{this.$element.trigger(f);if(f.isDefaultPrevented())return;r.removeClass("active"),i.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return s&&this.cycle(),this}};var n=e.fn.carousel;e.fn.carousel=function(n){return this.each(function(){var r=e(this),i=r.data("carousel"),s=e.extend({},e.fn.carousel.defaults,typeof n=="object"&&n),o=typeof n=="string"?n:s.slide;i||r.data("carousel",i=new t(this,s)),typeof n=="number"?i.to(n):o?i[o]():s.interval&&i.pause().cycle()})},e.fn.carousel.defaults={interval:5e3,pause:"hover"},e.fn.carousel.Constructor=t,e.fn.carousel.noConflict=function(){return e.fn.carousel=n,this},e(document).on("click.carousel.data-api","[data-slide], [data-slide-to]",function(t){var n=e(this),r,i=e(n.attr("data-target")||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,"")),s=e.extend({},i.data(),n.data()),o;i.carousel(s),(o=n.attr("data-slide-to"))&&i.data("carousel").pause().to(o).cycle(),t.preventDefault()})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.collapse.defaults,n),this.options.parent&&(this.$parent=e(this.options.parent)),this.options.toggle&&this.toggle()};t.prototype={constructor:t,dimension:function(){var e=this.$element.hasClass("width");return e?"width":"height"},show:function(){var t,n,r,i;if(this.transitioning||this.$element.hasClass("in"))return;t=this.dimension(),n=e.camelCase(["scroll",t].join("-")),r=this.$parent&&this.$parent.find("> .accordion-group > .in");if(r&&r.length){i=r.data("collapse");if(i&&i.transitioning)return;r.collapse("hide"),i||r.data("collapse",null)}this.$element[t](0),this.transition("addClass",e.Event("show"),"shown"),e.support.transition&&this.$element[t](this.$element[0][n])},hide:function(){var t;if(this.transitioning||!this.$element.hasClass("in"))return;t=this.dimension(),this.reset(this.$element[t]()),this.transition("removeClass",e.Event("hide"),"hidden"),this.$element[t](0)},reset:function(e){var t=this.dimension();return this.$element.removeClass("collapse")[t](e||"auto")[0].offsetWidth,this.$element[e!==null?"addClass":"removeClass"]("collapse"),this},transition:function(t,n,r){var i=this,s=function(){n.type=="show"&&i.reset(),i.transitioning=0,i.$element.trigger(r)};this.$element.trigger(n);if(n.isDefaultPrevented())return;this.transitioning=1,this.$element[t]("in"),e.support.transition&&this.$element.hasClass("collapse")?this.$element.one(e.support.transition.end,s):s()},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}};var n=e.fn.collapse;e.fn.collapse=function(n){return this.each(function(){var r=e(this),i=r.data("collapse"),s=e.extend({},e.fn.collapse.defaults,r.data(),typeof n=="object"&&n);i||r.data("collapse",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.collapse.defaults={toggle:!0},e.fn.collapse.Constructor=t,e.fn.collapse.noConflict=function(){return e.fn.collapse=n,this},e(document).on("click.collapse.data-api","[data-toggle=collapse]",function(t){var n=e(this),r,i=n.attr("data-target")||t.preventDefault()||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,""),s=e(i).data("collapse")?"toggle":n.data();n[e(i).hasClass("in")?"addClass":"removeClass"]("collapsed"),e(i).collapse(s)})}(window.jQuery),!function(e){"use strict";function r(){e(".dropdown-backdrop").remove(),e(t).each(function(){i(e(this)).removeClass("open")})}function i(t){var n=t.attr("data-target"),r;n||(n=t.attr("href"),n=n&&/#/.test(n)&&n.replace(/.*(?=#[^\s]*$)/,"")),r=n&&e(n);if(!r||!r.length)r=t.parent();return r}var t="[data-toggle=dropdown]",n=function(t){var n=e(t).on("click.dropdown.data-api",this.toggle);e("html").on("click.dropdown.data-api",function(){n.parent().removeClass("open")})};n.prototype={constructor:n,toggle:function(t){var n=e(this),s,o;if(n.is(".disabled, :disabled"))return;return s=i(n),o=s.hasClass("open"),r(),o||("ontouchstart"in document.documentElement&&e('