T get(String key) {
30 | return (T) (context.get().get(key));
31 | }
32 |
33 | /**
34 | * 清理ThreadLocal的Context内容.
35 | */
36 | public static void reset() {
37 | context.get().clear();
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/examples/boot-showcase/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | # server
2 | server.port=8080
3 | management.port=7002
4 |
5 | # db init settings
6 | spring.jpa.hibernate.ddl-auto=validate
7 | spring.jpa.showSql=false
8 | spring.datasource.initialize=true
9 | spring.datasource.sqlScriptEncoding=UTF-8
10 |
11 | # other
12 | spring.main.show-banner=false
13 | http.mappers.json-pretty-print=true
14 | http.mappers.json-sort-keys=true
15 |
16 | # disable useless endpoints
17 | endpoints.autoconfig.enabled=false
18 | endpoints.beans.enabled=false
19 | endpoints.configprops.enabled=false
20 | endpoints.mappings.enabled=false
21 | endpoints.trace.enabled=false
22 | #endpoints.shutdown.enabled=true
23 |
24 | # mail setting
25 | spring.mail.host=smtp.163.com
26 | spring.mail.username=springside@163.com
27 | spring.mail.password=springside123
28 | spring.mail.properties.mail.smtp.auth=true
29 |
30 | # /info endpoint
31 | info.app.name=Advanced Spring Boot Showcase Example
32 | info.app.version=${project.version}
33 |
--------------------------------------------------------------------------------
/modules/utils/src/main/java/org/springside/modules/utils/StringBuilderHolder.java:
--------------------------------------------------------------------------------
1 | package org.springside.modules.utils;
2 |
3 | /**
4 | * 参考BigDecimal, 可重用的StringBuilder, 节约StringBuilder内部的char[]
5 | *
6 | * 参考下面的示例代码将其保存为ThreadLocal.
7 | *
8 | *
9 | * private static final ThreadLocal threadLocalStringBuilderHolder = new ThreadLocal() {
10 | * @Override
11 | * protected StringBuilderHelper initialValue() {
12 | * return new StringBuilderHelper(256);
13 | * }
14 | * };
15 | *
16 | * StringBuilder sb = threadLocalStringBuilderHolder.get().resetAndGetStringBuilder();
17 | *
18 | *
19 | *
20 | * @author calvin
21 | *
22 | */
23 | public class StringBuilderHolder {
24 |
25 | private final StringBuilder sb;
26 |
27 | public StringBuilderHolder(int capacity) {
28 | sb = new StringBuilder(capacity);
29 | }
30 |
31 | /**
32 | * 重置StringBuilder内部的writerIndex, 而char[]保留不动.
33 | */
34 | public StringBuilder resetAndGetStringBuilder() {
35 | sb.setLength(0);
36 | return sb;
37 | }
38 | }
--------------------------------------------------------------------------------
/examples/boot-showcase/src/main/resources/application-prod.properties:
--------------------------------------------------------------------------------
1 | spring.jpa.database=H2
2 |
3 | spring.datasource.driverClassName=org.h2.Driver
4 | spring.datasource.url=jdbc:h2:file:~/.h2/bootmore;AUTO_SERVER=TRUE;DB_CLOSE_DELAY=-1;
5 | spring.datasource.username=sa
6 | spring.datasource.password=
7 |
8 | spring.datasource.initialize=false
9 | spring.jpa.hibernate.ddl-auto=none
10 |
11 | spring.datasource.initial-size=10
12 | spring.datasource.max-active=100
13 | spring.datasource.min-idle=8
14 | spring.datasource.max-idle=8
15 | #spring.datasource.max-wait=
16 | #spring.datasource.time-between-eviction-runs-millis=
17 | #spring.datasource.min-evictable-idle-time-millis=
18 |
19 |
20 | # logging
21 | logging.file=/var/log/springside/boot-showcase.log
22 | #logging.level.org.hibernate=WARN
23 |
24 | # tomcat settings
25 | #server.contextPath=/ by default
26 | #server.tomcat.maxThreads=200 by default
27 | #server.tomcat.compression=on(off by default)
28 | #server.tomcat.compressableMimeTypes=application/json,application/xml (text/html, text/xml, and text/plain by default)
29 | #server.address
30 | #server.sessiontimeout
31 |
32 |
33 |
--------------------------------------------------------------------------------
/examples/boot-api/src/main/java/org/springside/examples/bootapi/domain/Message.java:
--------------------------------------------------------------------------------
1 | package org.springside.examples.bootapi.domain;
2 |
3 | import java.util.Date;
4 |
5 | import javax.persistence.Entity;
6 | import javax.persistence.GeneratedValue;
7 | import javax.persistence.GenerationType;
8 | import javax.persistence.Id;
9 | import javax.persistence.JoinColumn;
10 | import javax.persistence.ManyToOne;
11 |
12 | import org.apache.commons.lang3.builder.ToStringBuilder;
13 |
14 | // JPA实体类的标识
15 | @Entity
16 | public class Message {
17 |
18 | // JPA 主键标识, 策略为由数据库生成主键
19 | @Id
20 | @GeneratedValue(strategy = GenerationType.IDENTITY)
21 | public Long id;
22 |
23 | @ManyToOne
24 | @JoinColumn(name = "receiver_id")
25 | public Account receiver;
26 |
27 | public String message;
28 |
29 | public Date receiveDate;
30 |
31 | public Message() {
32 |
33 | }
34 |
35 | public Message(Account receiver, String message, Date receiveDate) {
36 | this.receiver = receiver;
37 | this.message = message;
38 | this.receiveDate = receiveDate;
39 | }
40 |
41 | @Override
42 | public String toString() {
43 | return ToStringBuilder.reflectionToString(this);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/examples/boot-api/src/main/resources/application-prod.properties:
--------------------------------------------------------------------------------
1 | #set H2 in file mode as the production DB
2 | spring.jpa.database=H2
3 | spring.datasource.driverClassName=org.h2.Driver
4 | spring.datasource.url=jdbc:h2:file:~/.h2/bootapi;AUTO_SERVER=TRUE;DB_CLOSE_DELAY=-1;
5 | spring.datasource.username=sa
6 | spring.datasource.password=
7 |
8 | #disable automatic initialize for embedded H2
9 | spring.jpa.hibernate.ddl-auto=none
10 | spring.datasource.initialize=false
11 | flyway.enabled=true
12 |
13 | #connection pool settings
14 | spring.datasource.initial-size=10
15 | spring.datasource.max-active=100
16 | spring.datasource.min-idle=8
17 | spring.datasource.max-idle=8
18 | #spring.datasource.max-wait=
19 | #spring.datasource.time-between-eviction-runs-millis=
20 | #spring.datasource.min-evictable-idle-time-millis=
21 |
22 |
23 | # logging settings
24 | logging.file=/var/log/springside/boot-api.log
25 | #logging.level.org.hibernate=WARN
26 |
27 | # optional tomcat settings
28 | #server.contextPath=/ by default
29 | #server.tomcat.maxThreads=200 by default
30 | #server.tomcat.compression=on(off by default)
31 | #server.tomcat.compressableMimeTypes=application/json,application/xml (text/html, text/xml, and text/plain by default)
--------------------------------------------------------------------------------
/examples/boot-api/src/main/java/org/springside/examples/bootapi/config/H2ConsoleConfiguration.java:
--------------------------------------------------------------------------------
1 | package org.springside.examples.bootapi.config;
2 |
3 | import javax.servlet.ServletContext;
4 | import javax.servlet.ServletException;
5 | import javax.servlet.ServletRegistration;
6 |
7 | import org.springframework.boot.context.embedded.ServletContextInitializer;
8 | import org.springframework.context.annotation.Configuration;
9 | import org.springframework.context.annotation.Profile;
10 |
11 | /**
12 | * 在非生产环境里,初始化H2Console管理嵌入式H2.
13 | *
14 | * @author calvin
15 | */
16 | @Configuration
17 | @Profile(Profiles.NOT_PRODUCTION)
18 | public class H2ConsoleConfiguration implements ServletContextInitializer {
19 |
20 | @Override
21 | public void onStartup(ServletContext servletContext) throws ServletException {
22 | initH2Console(servletContext);
23 | }
24 |
25 | private void initH2Console(ServletContext servletContext) {
26 | ServletRegistration.Dynamic h2ConsoleServlet = servletContext.addServlet("H2Console",
27 | new org.h2.server.web.WebServlet());
28 | h2ConsoleServlet.addMapping("/h2/*");
29 | h2ConsoleServlet.setInitParameter("-properties", "src/main/resources");
30 | h2ConsoleServlet.setLoadOnStartup(1);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/examples/boot-api/src/test/java/org/springside/examples/bootapi/functional/BaseFunctionalTest.java:
--------------------------------------------------------------------------------
1 | package org.springside.examples.bootapi.functional;
2 |
3 | import org.junit.Rule;
4 | import org.junit.rules.TestRule;
5 | import org.junit.runner.RunWith;
6 | import org.springframework.beans.factory.annotation.Value;
7 | import org.springframework.boot.test.SpringApplicationConfiguration;
8 | import org.springframework.boot.test.WebIntegrationTest;
9 | import org.springframework.test.annotation.DirtiesContext;
10 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
11 | import org.springside.examples.bootapi.BootApiApplication;
12 | import org.springside.modules.test.rule.TestProgress;
13 |
14 | @RunWith(SpringJUnit4ClassRunner.class)
15 | @SpringApplicationConfiguration(classes = BootApiApplication.class)
16 | // 定义为Web集成测试,并使用随机端口号
17 | @WebIntegrationTest("server.port=0")
18 | // 定义每执行完一个Test文件刷新一次Spring Application Context,避免Case间的数据影响.
19 | // 但Test文件内多个测试方法间的影响仍需注意
20 | @DirtiesContext
21 | public abstract class BaseFunctionalTest {
22 |
23 | // 注入启动server后的实际端口号
24 | @Value("${local.server.port}")
25 | protected int port;
26 |
27 | // 在Console里打印Case的开始与结束,更容易分清Console里的日志归属于哪个Case.
28 | @Rule
29 | public TestRule testProgress = new TestProgress();
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/modules/core/src/main/java/org/springside/modules/mapper/BeanMapper.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright (c) 2005, 2014 springside.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | *******************************************************************************/
6 | package org.springside.modules.mapper;
7 |
8 | import java.util.List;
9 |
10 | import ma.glasnost.orika.MapperFacade;
11 | import ma.glasnost.orika.MapperFactory;
12 | import ma.glasnost.orika.impl.DefaultMapperFactory;
13 |
14 | /**
15 | * 简单封装orika, 实现深度转换Bean<->Bean的Mapper.
16 | */
17 | public class BeanMapper {
18 |
19 | private static MapperFacade mapper = null;
20 |
21 | static {
22 | MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
23 | mapper = mapperFactory.getMapperFacade();
24 |
25 | }
26 |
27 | /**
28 | * 基于Dozer转换对象的类型.
29 | */
30 | public static D map(S source, Class destinationClass) {
31 | return mapper.map(source, destinationClass);
32 | }
33 |
34 | /**
35 | * 基于Dozer转换Collection中对象的类型.
36 | */
37 | public static List mapList(Iterable sourceList, Class destinationClass) {
38 | return mapper.mapAsList(sourceList, destinationClass);
39 | }
40 |
41 | }
--------------------------------------------------------------------------------
/modules/utils/src/main/java/org/springside/modules/utils/Ids.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright (c) 2005, 2014 springside.github.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | *******************************************************************************/
6 | package org.springside.modules.utils;
7 |
8 | import java.security.SecureRandom;
9 | import java.util.UUID;
10 |
11 | /**
12 | * 封装各种生成唯一性ID算法的工具类.
13 | *
14 | * @author calvin
15 | */
16 | public class Ids {
17 |
18 | private static SecureRandom random = new SecureRandom();
19 |
20 | /**
21 | * 封装JDK自带的UUID, 通过Random数字生成, 中间有-分割.
22 | */
23 | public static String uuid() {
24 | return UUID.randomUUID().toString();
25 | }
26 |
27 | /**
28 | * 封装JDK自带的UUID, 通过Random数字生成, 中间无-分割.
29 | */
30 | public static String uuid2() {
31 | return UUID.randomUUID().toString().replaceAll("-", "");
32 | }
33 |
34 | /**
35 | * 使用SecureRandom随机生成Long.
36 | */
37 | public static long randomLong() {
38 | return Math.abs(random.nextLong());
39 | }
40 |
41 | /**
42 | * 基于Base62编码的SecureRandom随机生成bytes.
43 | */
44 | public static String randomBase62(int length) {
45 | byte[] randomBytes = new byte[length];
46 | random.nextBytes(randomBytes);
47 | return Encodes.encodeBase62(randomBytes);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/examples/boot-api/src/test/java/org/springside/examples/bootapi/repository/BookDaoTest.java:
--------------------------------------------------------------------------------
1 | package org.springside.examples.bootapi.repository;
2 |
3 | import static org.assertj.core.api.Assertions.*;
4 |
5 | import java.util.List;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 | import org.springframework.beans.factory.annotation.Autowired;
10 | import org.springframework.boot.test.SpringApplicationConfiguration;
11 | import org.springframework.data.domain.PageRequest;
12 | import org.springframework.test.annotation.DirtiesContext;
13 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
14 | import org.springside.examples.bootapi.BootApiApplication;
15 | import org.springside.examples.bootapi.domain.Book;
16 |
17 | @RunWith(SpringJUnit4ClassRunner.class)
18 | @SpringApplicationConfiguration(classes = BootApiApplication.class)
19 | @DirtiesContext
20 | public class BookDaoTest {
21 |
22 | @Autowired
23 | private BookDao bookDao;
24 |
25 | @Test
26 | public void findByOwnerId() {
27 | List books = bookDao.findByOwnerId(1L, new PageRequest(0, 10));
28 | assertThat(books).hasSize(2);
29 | assertThat(books.get(0).title).isEqualTo("Big Data日知录");
30 | }
31 |
32 | @Test
33 | public void findByBorrowerId() {
34 | List books = bookDao.findByBorrowerId(1L, new PageRequest(0, 10));
35 | assertThat(books).hasSize(0);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/examples/boot-showcase/src/main/java/org/springside/examples/showcase/config/JavaSimonConfig.java:
--------------------------------------------------------------------------------
1 | package org.springside.examples.showcase.config;
2 |
3 | import org.javasimon.console.SimonConsoleServlet;
4 | import org.javasimon.spring.MonitoredMeasuringPointcut;
5 | import org.javasimon.spring.MonitoringInterceptor;
6 | import org.springframework.aop.support.DefaultPointcutAdvisor;
7 | import org.springframework.boot.context.embedded.ServletRegistrationBean;
8 | import org.springframework.context.annotation.Bean;
9 | import org.springframework.context.annotation.Configuration;
10 |
11 | /**
12 | * 演示配置能力,包括AOP配置与Servlet配置
13 | */
14 | @Configuration
15 | public class JavaSimonConfig {
16 |
17 | // 定义AOP, 对标注了@Monitored的方法进行监控
18 | @Bean(name = "monitoringAdvisor")
19 | public DefaultPointcutAdvisor monitoringAdvisor() {
20 | DefaultPointcutAdvisor monitoringAdvisor = new DefaultPointcutAdvisor();
21 | monitoringAdvisor.setAdvice(new MonitoringInterceptor());
22 | monitoringAdvisor.setPointcut(new MonitoredMeasuringPointcut());
23 | return monitoringAdvisor;
24 | }
25 |
26 | // 定义Servlet URL Mapping
27 | @Bean
28 | public ServletRegistrationBean dispatcherRegistration() {
29 | ServletRegistrationBean registration = new ServletRegistrationBean(new SimonConsoleServlet());
30 | registration.addInitParameter("url-prefix", "/javasimon");
31 | registration.addUrlMappings("/javasimon/*");
32 | return registration;
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/examples/boot-api/src/main/java/org/springside/examples/bootapi/domain/Book.java:
--------------------------------------------------------------------------------
1 | package org.springside.examples.bootapi.domain;
2 |
3 | import java.util.Date;
4 |
5 | import javax.persistence.Entity;
6 | import javax.persistence.GeneratedValue;
7 | import javax.persistence.GenerationType;
8 | import javax.persistence.Id;
9 | import javax.persistence.JoinColumn;
10 | import javax.persistence.ManyToOne;
11 |
12 | import org.apache.commons.lang3.builder.ToStringBuilder;
13 |
14 | // JPA实体类的标识
15 | @Entity
16 | public class Book {
17 |
18 | public static final String STATUS_IDLE = "idle";
19 | public static final String STATUS_REQUEST = "request";
20 | public static final String STATUS_OUT = "out";
21 |
22 | // JPA 主键标识, 策略为由数据库生成主键
23 | @Id
24 | @GeneratedValue(strategy = GenerationType.IDENTITY)
25 | public Long id;
26 |
27 | public String doubanId;
28 |
29 | public String title;
30 |
31 | public String url;
32 |
33 | public String status;
34 |
35 | @ManyToOne
36 | @JoinColumn(name = "owner_id")
37 | public Account owner;
38 |
39 | public Date onboardDate;
40 |
41 | @ManyToOne
42 | @JoinColumn(name = "borrower_id")
43 | public Account borrower;
44 |
45 | public Date borrowDate;
46 |
47 | public Book() {
48 | }
49 |
50 | public Book(Long id) {
51 | this.id = id;
52 | }
53 |
54 | @Override
55 | public String toString() {
56 | return ToStringBuilder.reflectionToString(this);
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/examples/boot-api/src/main/resources/db/migration/V1.0__init.sql:
--------------------------------------------------------------------------------
1 | create table book (
2 | id bigint generated by default as identity,
3 | douban_id varchar(64) not null,
4 | title varchar(128) not null,
5 | url varchar(255),
6 | description varchar(255),
7 | owner_id bigint not null,
8 | onboard_date timestamp,
9 | status varchar(32) not null,
10 | borrower_id bigint null,
11 | borrow_date timestamp,
12 | primary key (id)
13 | );
14 |
15 | create table account (
16 | id bigint generated by default as identity,
17 | name varchar(64) not null,
18 | email varchar(128),
19 | hash_password varchar(255),
20 | primary key (id)
21 | );
22 |
23 |
24 | 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');
25 | 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');
26 | 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');
27 |
28 | insert into account (id,email,name,hash_password) values(1,'calvin.xiao@springside.io','Calvin','+2MunThvGcEfdYIFlT4NQQHt6z4=');
29 | insert into account (id,email,name,hash_password) values(2,'david.wang@springside.io','David','+2MunThvGcEfdYIFlT4NQQHt6z4=');
--------------------------------------------------------------------------------
/support/h2/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 | io.springside
6 | h2-console
7 | 5.0.0-SNAPSHOT
8 | H2 Console
9 | pom
10 |
11 |
12 |
13 | com.h2database
14 | h2
15 | 1.4.190
16 | runtime
17 |
18 |
19 |
20 |
21 |
22 |
23 | org.codehaus.mojo
24 | exec-maven-plugin
25 |
26 |
27 |
28 | java
29 |
30 |
31 |
32 |
33 | org.h2.tools.Server
34 |
35 | -web
36 | -webPort
37 | 8090
38 | -browser
39 | -properties
40 | .
41 |
42 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/modules/utils/src/test/java/org/springside/modules/test/data/RandomData.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright (c) 2005, 2014 springside.github.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | *******************************************************************************/
6 | package org.springside.modules.test.data;
7 |
8 | import java.util.Collections;
9 | import java.util.List;
10 | import java.util.Random;
11 |
12 | /**
13 | * 随机测试数据生成工具类.
14 | *
15 | * @author calvin
16 | */
17 | public class RandomData {
18 |
19 | private static Random random = new Random();
20 |
21 | /**
22 | * 返回随机ID.
23 | */
24 | public static long randomId() {
25 | return random.nextLong();
26 | }
27 |
28 | /**
29 | * 返回随机名称, prefix字符串+5位随机数字.
30 | */
31 | public static String randomName(String prefix) {
32 | return prefix + random.nextInt(10000);
33 | }
34 |
35 | /**
36 | * 从输入list中随机返回一个对象.
37 | */
38 | public static T randomOne(List list) {
39 | Collections.shuffle(list);
40 | return list.get(0);
41 | }
42 |
43 | /**
44 | * 从输入list中随机返回n个对象.
45 | */
46 | public static List randomSome(List list, int n) {
47 | Collections.shuffle(list);
48 | return list.subList(0, n);
49 | }
50 |
51 | /**
52 | * 从输入list中随机返回随机个对象.
53 | */
54 | public static List randomSome(List list) {
55 | int size = random.nextInt(list.size());
56 | if (size == 0) {
57 | size = 1;
58 | }
59 | return randomSome(list, size);
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/modules/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 | io.springside
6 | springside-modules
7 | 5.0.0-SNAPSHOT
8 | Springside :: Module
9 | pom
10 |
11 | 4.1.8.RELEASE
12 | 5.0.0-SNAPSHOT
13 | 18.0
14 | 3.4
15 | 1.10
16 | 2.4
17 | 1.9.2
18 | 1.4.6
19 | 2.6.1
20 | 1.7.12
21 | 1.1.3
22 | 4.12
23 | 2.2.0
24 | 1.10.19
25 | 1.6.3
26 |
27 |
28 | UTF-8
29 | 1.7
30 | ${java.version}
31 | ${java.version}
32 |
33 |
34 |
35 | utils
36 | core
37 |
38 |
39 |
--------------------------------------------------------------------------------
/modules/utils/src/main/java/org/springside/modules/constants/MediaTypes.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright (c) 2005, 2014 springside.github.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | *******************************************************************************/
6 | package org.springside.modules.constants;
7 |
8 | /**
9 | * 带UTF-8 charset 定义的MediaType.
10 | *
11 | * Jax-RS和Spring的MediaType没有UTF-8的版本;
12 | *
13 | * Google的MediaType必须再调用toString()函数而不是常量,不能用于Restful方法的annotation。
14 | *
15 | * @author calvin
16 | */
17 | public class MediaTypes {
18 |
19 | public static final String APPLICATION_XML = "application/xml";
20 | public static final String APPLICATION_XML_UTF_8 = "application/xml; charset=UTF-8";
21 |
22 | public static final String JSON = "application/json";
23 | public static final String JSON_UTF_8 = "application/json; charset=UTF-8";
24 |
25 | public static final String JAVASCRIPT = "application/javascript";
26 | public static final String JAVASCRIPT_UTF_8 = "application/javascript; charset=UTF-8";
27 |
28 | public static final String APPLICATION_XHTML_XML = "application/xhtml+xml";
29 | public static final String APPLICATION_XHTML_XML_UTF_8 = "application/xhtml+xml; charset=UTF-8";
30 |
31 | public static final String TEXT_PLAIN = "text/plain";
32 | public static final String TEXT_PLAIN_UTF_8 = "text/plain; charset=UTF-8";
33 |
34 | public static final String TEXT_XML = "text/xml";
35 | public static final String TEXT_XML_UTF_8 = "text/xml; charset=UTF-8";
36 |
37 | public static final String TEXT_HTML = "text/html";
38 | public static final String TEXT_HTML_UTF_8 = "text/html; charset=UTF-8";
39 | }
40 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 | io.springside
5 | springside-project
6 | 5.0.0-SNAPSHOT
7 | Springside :: Project
8 | pom
9 |
10 | SpringSide is a Spring Framework based JavaEE application reference architecture.
11 | http://www.github.com/springside/springside4
12 | 2006-2012
13 |
14 | SpringSide
15 | http://www.springside.org.cn
16 |
17 |
18 |
19 |
20 | calvin
21 | Calvin Xiao
22 | calvinxiu at gmail.com
23 |
24 | Project leader
25 |
26 | +8
27 |
28 |
29 |
30 |
31 |
32 | Apache License, Version 2.0
33 | http://www.apache.org/licenses/LICENSE-2.0
34 |
35 |
36 |
37 |
38 | modules
39 | examples
40 |
41 |
42 |
43 | Github
44 | https://github.com/springside/springside4/issues
45 |
46 |
47 |
48 | https://github.com/springside/springside4
49 | scm:git:git://github.com/springside/springside4.git
50 | scm:git:ssh://git@github.com:springside/springside4.git
51 |
52 |
--------------------------------------------------------------------------------
/modules/utils/src/test/java/org/springside/modules/utils/Collecitons3Test.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright (c) 2005, 2014 springside.github.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | *******************************************************************************/
6 | package org.springside.modules.utils;
7 |
8 | import static org.assertj.core.api.Assertions.*;
9 |
10 | import java.util.List;
11 |
12 | import org.junit.Test;
13 |
14 | import com.google.common.collect.Lists;
15 |
16 | public class Collecitons3Test {
17 |
18 | @Test
19 | public void convertElementPropertyToString() {
20 | TestBean3 bean1 = new TestBean3();
21 | bean1.setId(1);
22 | TestBean3 bean2 = new TestBean3();
23 | bean2.setId(2);
24 |
25 | List list = Lists.newArrayList(bean1, bean2);
26 |
27 | assertThat(Collections3.extractToString(list, "id", ",")).isEqualTo("1,2");
28 | }
29 |
30 | @Test
31 | public void convertElementPropertyToList() {
32 | TestBean3 bean1 = new TestBean3();
33 | bean1.setId(1);
34 | TestBean3 bean2 = new TestBean3();
35 | bean2.setId(2);
36 |
37 | List list = Lists.newArrayList(bean1, bean2);
38 | List result = Collections3.extractToList(list, "id");
39 | assertThat(result).containsOnly(1, 2);
40 | }
41 |
42 | @Test
43 | public void convertCollectionToString() {
44 | List list = Lists.newArrayList("aa", "bb");
45 | String result = Collections3.convertToString(list, ",");
46 | assertThat(result).isEqualTo("aa,bb");
47 |
48 | result = Collections3.convertToString(list, "", "");
49 | assertThat(result).isEqualTo("aabb");
50 | }
51 |
52 | public static class TestBean3 {
53 |
54 | private int id;
55 |
56 | public int getId() {
57 | return id;
58 | }
59 |
60 | public void setId(int id) {
61 | this.id = id;
62 | }
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/modules/utils/src/test/java/org/springside/modules/utils/EncodesTest.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright (c) 2005, 2014 springside.github.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | *******************************************************************************/
6 | package org.springside.modules.utils;
7 |
8 | import static org.assertj.core.api.Assertions.*;
9 |
10 | import org.junit.Test;
11 |
12 | public class EncodesTest {
13 |
14 | @Test
15 | public void hexEncode() {
16 | String input = "haha,i am a very long message";
17 | String result = Encodes.encodeHex(input.getBytes());
18 | assertThat(new String(Encodes.decodeHex(result))).isEqualTo(input);
19 | }
20 |
21 | @Test
22 | public void base64Encode() {
23 | String input = "haha,i am a very long message";
24 | String result = Encodes.encodeBase64(input.getBytes());
25 | assertThat(new String(Encodes.decodeBase64(result))).isEqualTo(input);
26 | }
27 |
28 | @Test
29 | public void base64UrlSafeEncode() {
30 | String input = "haha,i am a very long message";
31 | String result = Encodes.encodeUrlSafeBase64(input.getBytes());
32 | assertThat(new String(Encodes.decodeBase64(result))).isEqualTo(input);
33 | }
34 |
35 | @Test
36 | public void urlEncode() {
37 | String input = "http://locahost/?q=中文&t=1";
38 | String result = Encodes.urlEncode(input);
39 | System.out.println(result);
40 |
41 | assertThat(Encodes.urlDecode(result)).isEqualTo(input);
42 | }
43 |
44 | @Test
45 | public void xmlEncode() {
46 | String input = "1>2";
47 | String result = Encodes.escapeXml(input);
48 | assertThat(result).isEqualTo("1>2");
49 | assertThat(Encodes.unescapeXml(result)).isEqualTo(input);
50 | }
51 |
52 | @Test
53 | public void html() {
54 | String input = "1>2";
55 | String result = Encodes.escapeHtml(input);
56 | assertThat(result).isEqualTo("1>2");
57 | assertThat(Encodes.unescapeHtml(result)).isEqualTo(input);
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/modules/utils/src/main/java/org/springside/modules/utils/Clock.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright (c) 2005, 2014 springside.github.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | *******************************************************************************/
6 | package org.springside.modules.utils;
7 |
8 | import java.util.Date;
9 |
10 | /**
11 | * 日期提供者,使用它而不是直接取得系统时间,方便测试。
12 | *
13 | * @author calvin
14 | */
15 | public interface Clock {
16 |
17 | static final Clock DEFAULT = new DefaultClock();
18 |
19 | Date getCurrentDate();
20 |
21 | long getCurrentTimeInMillis();
22 |
23 | /**
24 | * 默认时间提供者,返回当前的时间,线程安全。
25 | */
26 | public static class DefaultClock implements Clock {
27 |
28 | @Override
29 | public Date getCurrentDate() {
30 | return new Date();
31 | }
32 |
33 | @Override
34 | public long getCurrentTimeInMillis() {
35 | return System.currentTimeMillis();
36 | }
37 | }
38 |
39 | /**
40 | * 可配置的时间提供者,用于测试.
41 | */
42 | public static class MockClock implements Clock {
43 |
44 | private long time;
45 |
46 | public MockClock() {
47 | this(0);
48 | }
49 |
50 | public MockClock(Date date) {
51 | this.time = date.getTime();
52 | }
53 |
54 | public MockClock(long time) {
55 | this.time = time;
56 | }
57 |
58 | @Override
59 | public Date getCurrentDate() {
60 | return new Date(time);
61 | }
62 |
63 | @Override
64 | public long getCurrentTimeInMillis() {
65 | return time;
66 | }
67 |
68 | /**
69 | * 重新设置日期。
70 | */
71 | public void update(Date newDate) {
72 | time = newDate.getTime();
73 | }
74 |
75 | /**
76 | * 重新设置时间。
77 | */
78 | public void update(long newTime) {
79 | this.time = newTime;
80 | }
81 |
82 | /**
83 | * 滚动时间.
84 | */
85 | public void increaseTime(int millis) {
86 | time += millis;
87 | }
88 |
89 | /**
90 | * 滚动时间.
91 | */
92 | public void decreaseTime(int millis) {
93 | time -= millis;
94 | }
95 | }
96 |
97 | }
98 |
--------------------------------------------------------------------------------
/modules/utils/src/test/java/org/springside/modules/utils/ExceptionsTest.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright (c) 2005, 2014 springside.github.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | *******************************************************************************/
6 | package org.springside.modules.utils;
7 |
8 | import static org.assertj.core.api.Assertions.*;
9 |
10 | import java.io.IOException;
11 |
12 | import org.junit.Test;
13 |
14 | public class ExceptionsTest {
15 |
16 | @Test
17 | public void unchecked() {
18 | // convert Exception to RuntimeException with cause
19 | Exception exception = new Exception("my exception");
20 | RuntimeException runtimeException = Exceptions.unchecked(exception);
21 | assertThat(runtimeException.getCause()).isEqualTo(exception);
22 |
23 | // do nothing of RuntimeException
24 | RuntimeException runtimeException2 = Exceptions.unchecked(runtimeException);
25 | assertThat(runtimeException2).isSameAs(runtimeException);
26 | }
27 |
28 | @Test
29 | public void getStackTraceAsString() {
30 | Exception exception = new Exception("my exception");
31 | RuntimeException runtimeException = new RuntimeException(exception);
32 |
33 | String stack = Exceptions.getStackTraceAsString(runtimeException);
34 | System.out.println(stack);
35 | }
36 |
37 | @Test
38 | public void isCausedBy() {
39 | IOException ioexception = new IOException("my exception");
40 | IllegalStateException illegalStateException = new IllegalStateException(ioexception);
41 | RuntimeException runtimeException = new RuntimeException(illegalStateException);
42 |
43 | assertThat(Exceptions.isCausedBy(runtimeException, IOException.class)).isTrue();
44 | assertThat(Exceptions.isCausedBy(runtimeException, IllegalStateException.class, IOException.class)).isTrue();
45 | assertThat(Exceptions.isCausedBy(runtimeException, Exception.class)).isTrue();
46 | assertThat(Exceptions.isCausedBy(runtimeException, IllegalAccessException.class)).isFalse();
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/modules/utils/src/main/java/org/springside/modules/utils/Exceptions.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright (c) 2005, 2014 springside.github.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | *******************************************************************************/
6 | package org.springside.modules.utils;
7 |
8 | import java.io.PrintWriter;
9 | import java.io.StringWriter;
10 |
11 | /**
12 | * 关于异常的工具类.
13 | *
14 | * 参考了guava的Throwables。
15 | *
16 | * @author calvin
17 | */
18 | public class Exceptions {
19 |
20 | /**
21 | * 将CheckedException转换为UncheckedException.
22 | */
23 | public static RuntimeException unchecked(Throwable ex) {
24 | if (ex instanceof RuntimeException) {
25 | return (RuntimeException) ex;
26 | } else {
27 | return new RuntimeException(ex);
28 | }
29 | }
30 |
31 | /**
32 | * 将ErrorStack转化为String.
33 | */
34 | public static String getStackTraceAsString(Throwable ex) {
35 | StringWriter stringWriter = new StringWriter();
36 | ex.printStackTrace(new PrintWriter(stringWriter));
37 | return stringWriter.toString();
38 | }
39 |
40 | /**
41 | * 获取组合本异常信息与底层异常信息的异常描述, 适用于本异常为统一包装异常类,底层异常才是根本原因的情况。
42 | */
43 | public static String getErrorMessageWithNestedException(Throwable ex) {
44 | Throwable nestedException = ex.getCause();
45 | return new StringBuilder().append(ex.getMessage()).append(" nested exception is ")
46 | .append(nestedException.getClass().getName()).append(":").append(nestedException.getMessage())
47 | .toString();
48 | }
49 |
50 | /**
51 | * 获取异常的Root Cause.
52 | */
53 | public static Throwable getRootCause(Throwable ex) {
54 | Throwable cause;
55 | while ((cause = ex.getCause()) != null) {
56 | ex = cause;
57 | }
58 | return ex;
59 | }
60 |
61 | /**
62 | * 判断异常是否由某些底层的异常引起.
63 | */
64 | public static boolean isCausedBy(Exception ex, Class extends Exception>... causeExceptionClasses) {
65 | Throwable cause = ex;
66 | while (cause != null) {
67 | for (Class extends Exception> causeClass : causeExceptionClasses) {
68 | if (causeClass.isInstance(cause)) {
69 | return true;
70 | }
71 | }
72 | cause = cause.getCause();
73 | }
74 | return false;
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/examples/boot-api/src/main/java/org/springside/examples/bootapi/api/AccountEndPoint.java:
--------------------------------------------------------------------------------
1 | package org.springside.examples.bootapi.api;
2 |
3 | import java.util.Collections;
4 | import java.util.Map;
5 |
6 | import org.slf4j.Logger;
7 | import org.slf4j.LoggerFactory;
8 | import org.springframework.beans.factory.annotation.Autowired;
9 | import org.springframework.util.StringUtils;
10 | import org.springframework.web.bind.annotation.RequestMapping;
11 | import org.springframework.web.bind.annotation.RequestParam;
12 | import org.springframework.web.bind.annotation.RestController;
13 | import org.springside.examples.bootapi.service.AccountService;
14 | import org.springside.examples.bootapi.service.exception.ErrorCode;
15 | import org.springside.examples.bootapi.service.exception.ServiceException;
16 | import org.springside.modules.constants.MediaTypes;
17 |
18 | // Spring Restful MVC Controller的标识, 直接输出内容,不调用template引擎.
19 | @RestController
20 | public class AccountEndPoint {
21 |
22 | private static Logger logger = LoggerFactory.getLogger(AccountEndPoint.class);
23 |
24 | @Autowired
25 | private AccountService accountServcie;
26 |
27 | @RequestMapping(value = "/api/accounts/login", produces = MediaTypes.JSON_UTF_8)
28 | public Map login(@RequestParam("email") String email, @RequestParam("password") String password) {
29 |
30 | if (StringUtils.isEmpty(email) || StringUtils.isEmpty(password)) {
31 | throw new ServiceException("User or password empty", ErrorCode.BAD_REQUEST);
32 | }
33 |
34 | String token = accountServcie.login(email, password);
35 |
36 | return Collections.singletonMap("token", token);
37 | }
38 |
39 | @RequestMapping(value = "/api/accounts/logout")
40 | public void logout(@RequestParam(value = "token", required = false) String token) {
41 | accountServcie.logout(token);
42 | }
43 |
44 | @RequestMapping(value = "/api/accounts/register")
45 | public void register(@RequestParam("email") String email,
46 | @RequestParam(value = "name", required = false) String name, @RequestParam("password") String password) {
47 |
48 | if (StringUtils.isEmpty(email) || StringUtils.isEmpty(name) || StringUtils.isEmpty(password)) {
49 | throw new ServiceException("User or name or password empty", ErrorCode.BAD_REQUEST);
50 | }
51 |
52 | accountServcie.register(email, name, password);
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/modules/utils/src/test/java/org/springside/modules/utils/CryptosTest.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright (c) 2005, 2014 springside.github.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | *******************************************************************************/
6 | package org.springside.modules.utils;
7 |
8 | import static org.assertj.core.api.Assertions.*;
9 |
10 | import org.junit.Test;
11 | import org.springside.modules.utils.Cryptos;
12 | import org.springside.modules.utils.Encodes;
13 |
14 | public class CryptosTest {
15 | @Test
16 | public void mac() {
17 | String input = "foo message";
18 |
19 | // key可为任意字符串
20 | // byte[] key = "a foo key".getBytes();
21 | byte[] key = Cryptos.generateHmacSha1Key();
22 | assertThat(key).hasSize(20);
23 |
24 | byte[] macResult = Cryptos.hmacSha1(input.getBytes(), key);
25 | System.out.println("hmac-sha1 key in hex :" + Encodes.encodeHex(key));
26 | System.out.println("hmac-sha1 in hex result :" + Encodes.encodeHex(macResult));
27 |
28 | assertThat(Cryptos.isMacValid(macResult, input.getBytes(), key)).isTrue();
29 | }
30 |
31 | @Test
32 | public void aes() {
33 | byte[] key = Cryptos.generateAesKey();
34 | assertThat(key).hasSize(16);
35 | String input = "foo message";
36 |
37 | byte[] encryptResult = Cryptos.aesEncrypt(input.getBytes(), key);
38 | String descryptResult = Cryptos.aesDecrypt(encryptResult, key);
39 |
40 | System.out.println("aes key in hex :" + Encodes.encodeHex(key));
41 | System.out.println("aes encrypt in hex result :" + Encodes.encodeHex(encryptResult));
42 | assertThat(descryptResult).isEqualTo(input);
43 | }
44 |
45 | @Test
46 | public void aesWithIV() {
47 | byte[] key = Cryptos.generateAesKey();
48 | byte[] iv = Cryptos.generateIV();
49 | assertThat(key).hasSize(16);
50 | assertThat(iv).hasSize(16);
51 | String input = "foo message";
52 |
53 | byte[] encryptResult = Cryptos.aesEncrypt(input.getBytes(), key, iv);
54 | String descryptResult = Cryptos.aesDecrypt(encryptResult, key, iv);
55 |
56 | System.out.println("aes key in hex :" + Encodes.encodeHex(key));
57 | System.out.println("iv in hex :" + Encodes.encodeHex(iv));
58 | System.out.println("aes encrypt in hex result :" + Encodes.encodeHex(encryptResult));
59 | assertThat(descryptResult).isEqualTo(input);
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/modules/utils/src/main/java/org/springside/modules/utils/ConcurrentHashSet.java:
--------------------------------------------------------------------------------
1 | package org.springside.modules.utils;
2 |
3 | import java.util.AbstractSet;
4 | import java.util.Collection;
5 | import java.util.Iterator;
6 | import java.util.Map;
7 | import java.util.Set;
8 | import java.util.concurrent.ConcurrentHashMap;
9 |
10 | /**
11 | * JDK并没有提供ConcurrenHashSet,考虑到JDK的HashSet也是基于HashMap实现的,因此ConcurrenHashSet也由ConcurrenHashMap完成。
12 | *
13 | * 实现参考了Jetty的实现.
14 | */
15 | public class ConcurrentHashSet extends AbstractSet implements Set {
16 | private final Map map;
17 | private transient Set keys;
18 |
19 | public ConcurrentHashSet() {
20 | map = new ConcurrentHashMap();
21 | keys = map.keySet();
22 | }
23 |
24 | public ConcurrentHashSet(int initialCapacity) {
25 | map = new ConcurrentHashMap(initialCapacity);
26 | keys = map.keySet();
27 | }
28 |
29 | @Override
30 | public boolean add(E e) {
31 | return map.put(e, Boolean.TRUE) == null;
32 | }
33 |
34 | @Override
35 | public boolean remove(Object o) {
36 | return map.remove(o) != null;
37 | }
38 |
39 | @Override
40 | public boolean contains(Object o) {
41 | return map.containsKey(o);
42 | }
43 |
44 | @Override
45 | public boolean isEmpty() {
46 | return map.isEmpty();
47 | }
48 |
49 | @Override
50 | public int size() {
51 | return map.size();
52 | }
53 |
54 | @Override
55 | public void clear() {
56 | map.clear();
57 | }
58 |
59 | @Override
60 | public Iterator iterator() {
61 | return keys.iterator();
62 | }
63 |
64 | @Override
65 | public boolean containsAll(Collection> c) {
66 | return keys.containsAll(c);
67 | }
68 |
69 | @Override
70 | public boolean removeAll(Collection> c) {
71 | return keys.removeAll(c);
72 | }
73 |
74 | @Override
75 | public boolean retainAll(Collection> c) {
76 | return keys.retainAll(c);
77 | }
78 |
79 | @Override
80 | public Object[] toArray() {
81 | return keys.toArray();
82 | }
83 |
84 | @Override
85 | public T[] toArray(T[] a) {
86 | return keys.toArray(a);
87 | }
88 |
89 | @Override
90 | public boolean equals(Object o) {
91 | return o == this || keys.equals(o);
92 | }
93 |
94 | @Override
95 | public int hashCode() {
96 | return keys.hashCode();
97 | }
98 |
99 | @Override
100 | public String toString() {
101 | return keys.toString();
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/examples/boot-api/src/main/java/org/springside/examples/bootapi/api/support/ErrorPageController.java:
--------------------------------------------------------------------------------
1 | package org.springside.examples.bootapi.api.support;
2 |
3 | import java.util.Map;
4 |
5 | import javax.servlet.http.HttpServletRequest;
6 |
7 | import org.slf4j.Logger;
8 | import org.slf4j.LoggerFactory;
9 | import org.springframework.beans.factory.annotation.Value;
10 | import org.springframework.boot.autoconfigure.web.DefaultErrorAttributes;
11 | import org.springframework.boot.autoconfigure.web.ErrorAttributes;
12 | import org.springframework.boot.autoconfigure.web.ErrorController;
13 | import org.springframework.stereotype.Controller;
14 | import org.springframework.web.bind.annotation.RequestMapping;
15 | import org.springframework.web.bind.annotation.ResponseBody;
16 | import org.springframework.web.context.request.RequestAttributes;
17 | import org.springframework.web.context.request.ServletRequestAttributes;
18 | import org.springside.modules.constants.MediaTypes;
19 | import org.springside.modules.mapper.JsonMapper;
20 |
21 | /**
22 | * 重载替换Spring Boot默认的BasicErrorController, 增加日志并让错误返回方式统一.
23 | *
24 | * @author calvin
25 | */
26 | @Controller
27 | public class ErrorPageController implements ErrorController {
28 |
29 | private static Logger logger = LoggerFactory.getLogger(ErrorPageController.class);
30 |
31 | @Value("${error.path:/error}")
32 | private String errorPath;
33 |
34 | private JsonMapper jsonMapper = new JsonMapper();
35 |
36 | private ErrorAttributes errorAttributes = new DefaultErrorAttributes();
37 |
38 | @RequestMapping(value = "${error.path:/error}", produces = MediaTypes.JSON_UTF_8)
39 | @ResponseBody
40 | public ErrorResult handle(HttpServletRequest request) {
41 | Map attributes = getErrorAttributes(request);
42 |
43 | ErrorResult result = new ErrorResult();
44 | result.code = (int) attributes.get("status");
45 | result.message = (String) attributes.get("error");
46 |
47 | logError(attributes, request);
48 |
49 | return result;
50 | }
51 |
52 | private Map getErrorAttributes(HttpServletRequest request) {
53 | RequestAttributes requestAttributes = new ServletRequestAttributes(request);
54 | return this.errorAttributes.getErrorAttributes(requestAttributes, false);
55 | }
56 |
57 | private void logError(Map attributes, HttpServletRequest request) {
58 | attributes.put("from", request.getRemoteAddr());
59 | logger.error(jsonMapper.toJson(attributes));
60 | }
61 |
62 | @Override
63 | public String getErrorPath() {
64 | return this.errorPath;
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/examples/boot-api/src/main/webapp/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Spring Boot Web Service示例
5 |
6 |
7 |
8 | 1. 顺序访问P2P图书馆 API:
9 |
10 | step1:
11 |
15 |
16 | step2:
17 |
18 |
23 |
24 | step3:
25 |
26 |
31 |
32 | step4:
33 |
34 |
38 |
39 | other flow:
40 |
41 |
45 |
46 |
47 | 2. 访问下列应用管理Endpoint:
48 |
56 |
57 |
58 | 3. JMX通过Restful的jolokia访问:
59 |
62 |
63 |
64 | 4. 在开发Profile下的H2 Console,用于查看内存中数据库的内容:
65 |
68 |
69 |
--------------------------------------------------------------------------------
/modules/utils/src/test/java/org/springside/modules/utils/DigestsTest.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright (c) 2005, 2014 springside.github.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | *******************************************************************************/
6 | package org.springside.modules.utils;
7 |
8 | import java.io.IOException;
9 | import java.io.InputStream;
10 |
11 | import org.junit.Test;
12 |
13 | public class DigestsTest {
14 |
15 | @Test
16 | public void digestString() {
17 | String input = "user";
18 | byte[] sha1Result = Digests.sha1(input.getBytes());
19 | System.out.println("sha1 in hex result :" + Encodes.encodeHex(sha1Result));
20 |
21 | byte[] salt = Digests.generateSalt(8);
22 | System.out.println("salt in hex :" + Encodes.encodeHex(salt));
23 | sha1Result = Digests.sha1(input.getBytes(), salt);
24 | System.out.println("sha1 in hex result with salt :" + Encodes.encodeHex(sha1Result));
25 |
26 | sha1Result = Digests.sha1(input.getBytes(), salt, 1024);
27 | System.out.println("sha1 in hex result with salt and 1024 interations:" + Encodes.encodeHex(sha1Result));
28 |
29 | }
30 |
31 | @Test
32 | public void digestFile() throws IOException {
33 |
34 | InputStream is = this.getClass().getClassLoader().getResourceAsStream("test.txt");
35 | byte[] md5result = Digests.md5(is);
36 | byte[] sha1result = Digests.sha1(is);
37 | System.out.println("md5: " + Encodes.encodeHex(md5result));
38 | System.out.println("sha1:" + Encodes.encodeHex(sha1result));
39 | }
40 |
41 | @Test
42 | public void crc32String() {
43 |
44 | String input = "user1";
45 | int result = Digests.crc32(input);
46 | System.out.println("crc32 for user1:" + result);
47 |
48 | input = "user2";
49 | result = Digests.crc32(input);
50 | System.out.println("crc32 for user2:" + result);
51 | }
52 |
53 | @Test
54 | public void murmurString() {
55 |
56 | String input1 = "user1";
57 | int result = Digests.murmur32(input1);
58 | System.out.println("murmur32 for user1:" + result);
59 |
60 | String input2 = "user2";
61 | result = Digests.murmur32(input2);
62 | System.out.println("murmur32 for user2:" + result);
63 |
64 | int seed = (int) System.currentTimeMillis();
65 | result = Digests.murmur32(input1, seed);
66 | System.out.println("murmur32 with seed for user1:" + result);
67 |
68 | result = Digests.murmur32(input2, seed);
69 | System.out.println("murmur32 with seed for user2:" + result);
70 |
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/modules/utils/src/test/java/org/springside/modules/test/log/LogbackListAppenderTest.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright (c) 2005, 2014 springside.github.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | *******************************************************************************/
6 | package org.springside.modules.test.log;
7 |
8 | import static org.assertj.core.api.Assertions.*;
9 |
10 | import org.junit.Test;
11 | import org.slf4j.Logger;
12 | import org.slf4j.LoggerFactory;
13 |
14 | public class LogbackListAppenderTest {
15 |
16 | @Test
17 | public void normal() {
18 | String testString1 = "Hello";
19 | String testString2 = "World";
20 | LogbackListAppender appender = new LogbackListAppender();
21 | appender.addToLogger(LogbackListAppenderTest.class);
22 |
23 | // null
24 | assertThat(appender.getFirstLog()).isNull();
25 | assertThat(appender.getLastLog()).isNull();
26 | assertThat(appender.getFirstMessage()).isNull();
27 | assertThat(appender.getFirstMessage()).isNull();
28 |
29 | Logger logger = LoggerFactory.getLogger(LogbackListAppenderTest.class);
30 | logger.warn(testString1);
31 | logger.warn(testString2);
32 |
33 | // getFirstLog/getLastLog
34 | assertThat(appender.getFirstLog().getMessage()).isEqualTo(testString1);
35 | assertThat(appender.getLastLog().getMessage()).isEqualTo(testString2);
36 |
37 | assertThat(appender.getFirstMessage()).isEqualTo(testString1);
38 | assertThat(appender.getLastMessage()).isEqualTo(testString2);
39 |
40 | // getAllLogs
41 | assertThat(appender.getLogsCount()).isEqualTo(2);
42 | assertThat(appender.getAllLogs()).hasSize(2);
43 | assertThat(appender.getAllLogs().get(1).getMessage()).isEqualTo(testString2);
44 |
45 | // clearLogs
46 | appender.clearLogs();
47 | assertThat(appender.getFirstLog()).isNull();
48 | assertThat(appender.getLastLog()).isNull();
49 | }
50 |
51 | @Test
52 | public void addAndRemoveAppender() {
53 | String testString = "Hello";
54 | Logger logger = LoggerFactory.getLogger(LogbackListAppenderTest.class);
55 | LogbackListAppender appender = new LogbackListAppender();
56 | // class
57 | appender.addToLogger(LogbackListAppenderTest.class);
58 | logger.warn(testString);
59 | assertThat(appender.getFirstLog()).isNotNull();
60 |
61 | appender.clearLogs();
62 | appender.removeFromLogger(LogbackListAppenderTest.class);
63 | logger.warn(testString);
64 | assertThat(appender.getFirstLog()).isNull();
65 |
66 | // name
67 | appender.clearLogs();
68 | appender.addToLogger("org.springside.modules.test.log");
69 | logger.warn(testString);
70 | assertThat(appender.getFirstLog()).isNotNull();
71 |
72 | appender.clearLogs();
73 | appender.removeFromLogger("org.springside.modules.test.log");
74 | logger.warn(testString);
75 | assertThat(appender.getFirstLog()).isNull();
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/examples/boot-api/src/test/java/org/springside/examples/bootapi/service/BookBorrowServiceTest.java:
--------------------------------------------------------------------------------
1 | package org.springside.examples.bootapi.service;
2 |
3 | import static org.assertj.core.api.Assertions.*;
4 |
5 | import org.junit.Before;
6 | import org.junit.Test;
7 | import org.mockito.Mockito;
8 | import org.springside.examples.bootapi.domain.Account;
9 | import org.springside.examples.bootapi.domain.Book;
10 | import org.springside.examples.bootapi.domain.Message;
11 | import org.springside.examples.bootapi.repository.BookDao;
12 | import org.springside.examples.bootapi.repository.MessageDao;
13 | import org.springside.modules.test.log.LogbackListAppender;
14 |
15 | public class BookBorrowServiceTest {
16 |
17 | private BookBorrowService service;
18 |
19 | private BookDao mockBookDao;
20 |
21 | private MessageDao mockMessageDao;
22 |
23 | private LogbackListAppender appender;
24 |
25 | @Before
26 | public void setup() {
27 | service = new BookBorrowService();
28 | mockBookDao = Mockito.mock(BookDao.class);
29 | mockMessageDao = Mockito.mock(MessageDao.class);
30 | service.bookDao = mockBookDao;
31 | service.messageDao = mockMessageDao;
32 |
33 | appender = new LogbackListAppender();
34 | appender.addToLogger(BookBorrowService.class);
35 | }
36 |
37 | public void tearDown() {
38 | appender.removeFromLogger(BookBorrowService.class);
39 | }
40 |
41 | @Test
42 | public void applyBorrowRequest() {
43 |
44 | Book book = new Book(1L);
45 | book.status = Book.STATUS_IDLE;
46 | book.owner = new Account(1L);
47 |
48 | Mockito.when(mockBookDao.findOne(1L)).thenReturn(book);
49 |
50 | service.applyBorrowRequest(1L, new Account(3L));
51 |
52 | Mockito.verify(mockBookDao).save(Mockito.any(Book.class));
53 | Mockito.verify(mockMessageDao).save(Mockito.any(Message.class));
54 | }
55 |
56 | @Test
57 | public void applyBorrowRequestWithError() {
58 |
59 | // 自己借自己的书
60 | Book book = new Book(1L);
61 | book.status = Book.STATUS_IDLE;
62 | book.owner = new Account(1L);
63 |
64 | Mockito.when(mockBookDao.findOne(1L)).thenReturn(book);
65 | try {
66 | service.applyBorrowRequest(1L, new Account(1L));
67 | fail("should fail here");
68 | } catch (Exception e) {
69 | assertThat(e).hasMessageContaining("User shouldn't borrower the book which is himeself");
70 | assertThat(appender.getLastMessage()).contains("user id:1,book id:1");
71 | }
72 | // 保证BookDao没被调用
73 | Mockito.verify(mockBookDao, Mockito.never()).save(Mockito.any(Book.class));
74 |
75 | // 借已借出的书
76 | book.status = Book.STATUS_REQUEST;
77 |
78 | try {
79 | service.applyBorrowRequest(1L, new Account(3L));
80 | fail("should fail here");
81 | } catch (Exception e) {
82 | assertThat(e).hasMessageContaining("The book is not idle");
83 | assertThat(appender.getLastMessage()).contains("user id:3,book id:1,status:request");
84 | }
85 | Mockito.verify(mockBookDao, Mockito.never()).save(Mockito.any(Book.class));
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/examples/boot-api/src/main/java/org/springside/examples/bootapi/service/BookAdminService.java:
--------------------------------------------------------------------------------
1 | package org.springside.examples.bootapi.service;
2 |
3 | import java.util.List;
4 |
5 | import org.slf4j.Logger;
6 | import org.slf4j.LoggerFactory;
7 | import org.springframework.beans.factory.annotation.Autowired;
8 | import org.springframework.data.domain.Pageable;
9 | import org.springframework.stereotype.Service;
10 | import org.springframework.transaction.annotation.Transactional;
11 | import org.springside.examples.bootapi.domain.Account;
12 | import org.springside.examples.bootapi.domain.Book;
13 | import org.springside.examples.bootapi.repository.BookDao;
14 | import org.springside.examples.bootapi.service.exception.ErrorCode;
15 | import org.springside.examples.bootapi.service.exception.ServiceException;
16 | import org.springside.modules.utils.Clock;
17 |
18 | // Spring Bean的标识.
19 | @Service
20 | public class BookAdminService {
21 |
22 | private static Logger logger = LoggerFactory.getLogger(BookBorrowService.class);
23 |
24 | @Autowired
25 | private BookDao bookDao;
26 |
27 | // 可注入的Clock,方便测试时控制日期
28 | protected Clock clock = Clock.DEFAULT;
29 |
30 | @Transactional(readOnly = true)
31 | public Iterable findAll(Pageable pageable) {
32 | return bookDao.findAll(pageable);
33 | }
34 |
35 | @Transactional(readOnly = true)
36 | public Book findOne(Long id) {
37 | return bookDao.findOne(id);
38 | }
39 |
40 | @Transactional(readOnly = true)
41 | public List listMyBook(Long ownerId, Pageable pageable) {
42 | return bookDao.findByOwnerId(ownerId, pageable);
43 | }
44 |
45 | @Transactional
46 | public void saveBook(Book book, Account owner) {
47 |
48 | book.owner = owner;
49 | book.status = Book.STATUS_IDLE;
50 | book.onboardDate = clock.getCurrentDate();
51 |
52 | bookDao.save(book);
53 | }
54 |
55 | @Transactional
56 | public void modifyBook(Book book, Long currentAccountId) {
57 | if (!currentAccountId.equals(book.owner.id)) {
58 | logger.error("user:" + currentAccountId + " try to modified a book:" + book.id + " which is not him");
59 | throw new ServiceException("User can't modify others book", ErrorCode.BOOK_OWNERSHIP_WRONG);
60 | }
61 |
62 | Book orginalBook = bookDao.findOne(book.id);
63 |
64 | if (orginalBook == null) {
65 | logger.error("user:" + currentAccountId + " try to modified a book:" + book.id + " which is not exist");
66 | throw new ServiceException("The Book is not exist", ErrorCode.BAD_REQUEST);
67 | }
68 |
69 | orginalBook.title = book.title;
70 | orginalBook.url = book.url;
71 | bookDao.save(orginalBook);
72 | }
73 |
74 | @Transactional
75 | public void deleteBook(Long id, Long currentAccountId) {
76 | Book book = bookDao.findOne(id);
77 |
78 | if (book == null) {
79 | logger.error("user:" + currentAccountId + " try to delete a book:" + id + " which is not exist");
80 | throw new ServiceException("The Book is not exist", ErrorCode.BAD_REQUEST);
81 | }
82 |
83 | if (!currentAccountId.equals(book.owner.id)) {
84 | logger.error("user:" + currentAccountId + " try to delete a book:" + book.id + " which is not him");
85 | throw new ServiceException("User can't delete others book", ErrorCode.BOOK_OWNERSHIP_WRONG);
86 | }
87 |
88 | bookDao.delete(id);
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/modules/utils/src/test/java/org/springside/modules/utils/ThreadsTest.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright (c) 2005, 2014 springside.github.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | *******************************************************************************/
6 | package org.springside.modules.utils;
7 |
8 | import static org.assertj.core.api.Assertions.*;
9 |
10 | import java.util.concurrent.CountDownLatch;
11 | import java.util.concurrent.ExecutorService;
12 | import java.util.concurrent.Executors;
13 | import java.util.concurrent.TimeUnit;
14 |
15 | import org.junit.Test;
16 | import org.slf4j.Logger;
17 | import org.slf4j.LoggerFactory;
18 | import org.springside.modules.test.log.LogbackListAppender;
19 |
20 | public class ThreadsTest {
21 |
22 | @Test
23 | public void gracefulShutdown() throws InterruptedException {
24 |
25 | Logger logger = LoggerFactory.getLogger("test");
26 | LogbackListAppender appender = new LogbackListAppender();
27 | appender.addToLogger("test");
28 |
29 | // time enough to shutdown
30 | ExecutorService pool = Executors.newSingleThreadExecutor();
31 | Runnable task = new Task(logger, 500, 0);
32 | pool.execute(task);
33 | Threads.gracefulShutdown(pool, 1000, TimeUnit.MILLISECONDS);
34 | assertThat(pool.isTerminated()).isTrue();
35 | assertThat(appender.getFirstLog()).isNull();
36 |
37 | // time not enough to shutdown,call shutdownNow
38 | appender.clearLogs();
39 | pool = Executors.newSingleThreadExecutor();
40 | task = new Task(logger, 1000, 0);
41 | pool.execute(task);
42 | Threads.gracefulShutdown(pool, 500, TimeUnit.MILLISECONDS);
43 | assertThat(pool.isTerminated()).isTrue();
44 | assertThat(appender.getFirstLog().getMessage()).isEqualTo("InterruptedException");
45 |
46 | // self thread interrupt while calling gracefulShutdown
47 | appender.clearLogs();
48 |
49 | final ExecutorService self = Executors.newSingleThreadExecutor();
50 | task = new Task(logger, 100000, 0);
51 | self.execute(task);
52 |
53 | final CountDownLatch lock = new CountDownLatch(1);
54 | Thread thread = new Thread(new Runnable() {
55 |
56 | @Override
57 | public void run() {
58 | lock.countDown();
59 | Threads.gracefulShutdown(self, 200000, TimeUnit.MILLISECONDS);
60 | }
61 | });
62 | thread.start();
63 | lock.await();
64 | thread.interrupt();
65 | Threads.sleep(500);
66 | assertThat(appender.getFirstLog().getMessage()).isEqualTo("InterruptedException");
67 | }
68 |
69 | static class Task implements Runnable {
70 | private final Logger logger;
71 |
72 | private int runTime = 0;
73 |
74 | private final int sleepTime;
75 |
76 | Task(Logger logger, int sleepTime, int runTime) {
77 | this.logger = logger;
78 | this.sleepTime = sleepTime;
79 | this.runTime = runTime;
80 | }
81 |
82 | @Override
83 | public void run() {
84 | System.out.println("start task");
85 | if (runTime > 0) {
86 | long start = System.currentTimeMillis();
87 | while ((System.currentTimeMillis() - start) < runTime) {
88 | }
89 | }
90 |
91 | try {
92 | Thread.sleep(sleepTime);
93 | } catch (InterruptedException e) {
94 | logger.warn("InterruptedException");
95 | }
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/modules/utils/src/main/java/org/springside/modules/utils/Encodes.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright (c) 2005, 2014 springside.github.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | *******************************************************************************/
6 | package org.springside.modules.utils;
7 |
8 | import java.io.UnsupportedEncodingException;
9 | import java.net.URLDecoder;
10 | import java.net.URLEncoder;
11 |
12 | import org.apache.commons.codec.DecoderException;
13 | import org.apache.commons.codec.binary.Base64;
14 | import org.apache.commons.codec.binary.Hex;
15 | import org.apache.commons.lang3.StringEscapeUtils;
16 |
17 | /**
18 | * 封装各种格式的编码解码工具类.
19 | *
20 | * 1.Commons-Codec的 hex/base64 编码
21 | * 2.自制的base62 编码
22 | * 3.Commons-Lang的xml/html escape
23 | * 4.JDK提供的URLEncoder
24 | *
25 | * @author calvin
26 | */
27 | public class Encodes {
28 |
29 | private static final String DEFAULT_URL_ENCODING = "UTF-8";
30 | private static final char[] BASE62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".toCharArray();
31 |
32 | /**
33 | * Hex编码.
34 | */
35 | public static String encodeHex(byte[] input) {
36 | return Hex.encodeHexString(input);
37 | }
38 |
39 | /**
40 | * Hex解码.
41 | */
42 | public static byte[] decodeHex(String input) {
43 | try {
44 | return Hex.decodeHex(input.toCharArray());
45 | } catch (DecoderException e) {
46 | throw Exceptions.unchecked(e);
47 | }
48 | }
49 |
50 | /**
51 | * Base64编码.
52 | */
53 | public static String encodeBase64(byte[] input) {
54 | return Base64.encodeBase64String(input);
55 | }
56 |
57 | /**
58 | * Base64编码, URL安全(将Base64中的URL非法字符'+'和'/'转为'-'和'_', 见RFC3548).
59 | */
60 | public static String encodeUrlSafeBase64(byte[] input) {
61 | return Base64.encodeBase64URLSafeString(input);
62 | }
63 |
64 | /**
65 | * Base64解码.
66 | */
67 | public static byte[] decodeBase64(String input) {
68 | return Base64.decodeBase64(input);
69 | }
70 |
71 | /**
72 | * Base62编码。
73 | */
74 | public static String encodeBase62(byte[] input) {
75 | char[] chars = new char[input.length];
76 | for (int i = 0; i < input.length; i++) {
77 | chars[i] = BASE62[(input[i] & 0xFF) % BASE62.length];
78 | }
79 | return new String(chars);
80 | }
81 |
82 | /**
83 | * Html 转码.
84 | */
85 | public static String escapeHtml(String html) {
86 | return StringEscapeUtils.escapeHtml4(html);
87 | }
88 |
89 | /**
90 | * Html 解码.
91 | */
92 | public static String unescapeHtml(String htmlEscaped) {
93 | return StringEscapeUtils.unescapeHtml4(htmlEscaped);
94 | }
95 |
96 | /**
97 | * Xml 转码.
98 | */
99 | public static String escapeXml(String xml) {
100 | return StringEscapeUtils.escapeXml(xml);
101 | }
102 |
103 | /**
104 | * Xml 解码.
105 | */
106 | public static String unescapeXml(String xmlEscaped) {
107 | return StringEscapeUtils.unescapeXml(xmlEscaped);
108 | }
109 |
110 | /**
111 | * URL 编码, Encode默认为UTF-8.
112 | */
113 | public static String urlEncode(String part) {
114 | try {
115 | return URLEncoder.encode(part, DEFAULT_URL_ENCODING);
116 | } catch (UnsupportedEncodingException e) {
117 | throw Exceptions.unchecked(e);
118 | }
119 | }
120 |
121 | /**
122 | * URL 解码, Encode默认为UTF-8.
123 | */
124 | public static String urlDecode(String part) {
125 |
126 | try {
127 | return URLDecoder.decode(part, DEFAULT_URL_ENCODING);
128 | } catch (UnsupportedEncodingException e) {
129 | throw Exceptions.unchecked(e);
130 | }
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/examples/boot-api/src/main/java/org/springside/examples/bootapi/api/support/CustomExceptionHandler.java:
--------------------------------------------------------------------------------
1 | package org.springside.examples.bootapi.api.support;
2 |
3 | import java.util.Map;
4 |
5 | import javax.servlet.http.HttpServletRequest;
6 |
7 | import org.slf4j.Logger;
8 | import org.slf4j.LoggerFactory;
9 | import org.springframework.http.HttpHeaders;
10 | import org.springframework.http.HttpStatus;
11 | import org.springframework.http.MediaType;
12 | import org.springframework.http.ResponseEntity;
13 | import org.springframework.web.bind.annotation.ControllerAdvice;
14 | import org.springframework.web.bind.annotation.ExceptionHandler;
15 | import org.springframework.web.bind.annotation.RestController;
16 | import org.springframework.web.context.request.WebRequest;
17 | import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
18 | import org.springside.examples.bootapi.service.exception.ServiceException;
19 | import org.springside.modules.constants.MediaTypes;
20 | import org.springside.modules.mapper.JsonMapper;
21 |
22 | import com.google.common.collect.Maps;
23 |
24 | @ControllerAdvice(annotations = { RestController.class })
25 | public class CustomExceptionHandler extends ResponseEntityExceptionHandler {
26 |
27 | private Logger logger = LoggerFactory.getLogger(CustomExceptionHandler.class);
28 |
29 | private JsonMapper jsonMapper = new JsonMapper();
30 |
31 | @ExceptionHandler(value = { ServiceException.class })
32 | public final ResponseEntity handleServiceException(ServiceException ex, HttpServletRequest request) {
33 | // 注入servletRequest,用于出错时打印请求URL与来源地址
34 | logError(ex, request);
35 |
36 | HttpHeaders headers = new HttpHeaders();
37 | headers.setContentType(MediaType.parseMediaType(MediaTypes.JSON_UTF_8));
38 | ErrorResult result = new ErrorResult(ex.errorCode.code, ex.getMessage());
39 | return new ResponseEntity(result, headers, HttpStatus.valueOf(ex.errorCode.httpStatus));
40 | }
41 |
42 | @ExceptionHandler(value = { Exception.class })
43 | public final ResponseEntity handleGeneralException(Exception ex, HttpServletRequest request) {
44 | logError(ex, request);
45 |
46 | HttpHeaders headers = new HttpHeaders();
47 | headers.setContentType(MediaType.parseMediaType(MediaTypes.JSON_UTF_8));
48 | ErrorResult result = new ErrorResult(HttpStatus.INTERNAL_SERVER_ERROR.value(),
49 | HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase());
50 | return new ResponseEntity(result, headers, HttpStatus.INTERNAL_SERVER_ERROR);
51 | }
52 |
53 | /**
54 | * 重载ResponseEntityExceptionHandler的方法,加入日志
55 | */
56 | @Override
57 | protected ResponseEntity