[自分用]Spring BootでRESTful Web Service立てたやり方まとめ


#目的
サーバ側でいろいろ処理してクライアントでは結果だけ出したかった。

#はじめに
http://spring.io/guides/gs/rest-service/ 
の公式ガイドに簡単な流れがあるのでまずはこれを実行してみると良い。

#SPRING INITIALIZER
https://start.spring.io 
でGradleとかMavenのプロジェクトを生成してくれる。
本まとめではGradleを使う

DependenciesでWebとかMongoDBとか必要なのを入れておくと楽

##実行
適当にIntelliJかなんかで開いて、既にある`hogehogeApplication.java`のmainを実行すればデバッグ的には楽か？

生成されたプロジェクトのそのままだと実行してもすぐ終了してしまうので、[Greeting.java](https://github.com/spring-guides/gs-rest-service/blob/master/complete/src/main/java/hello/Greeting.java)と[GreetingController.java](https://github.com/spring-guides/gs-rest-service/blob/master/complete/src/main/java/hello/GreetingController.java)をぶち込めば http://localhost:8080/greeting でHello Worldできるはず。

#https
##証明書作る
プロジェクトのルートで

	keytool -genkey -alias tomcat -storetype PKCS12 -keyalg RSA -keysize 2048 -keystore keystore.p12 -validity 3650

適当に情報を入れてオレオレ証明書を作る。

##httpsを有効にする
`src/main/resources/application.properties`に

	server.ssl.key-store: keystore.p12
	server.ssl.key-store-password: 証明書のパスワード

を追記するだけ。
そしたら https://localhost:8443

#MongoDB
##設定
HostとかPortとかを設定しないとけないので、AbstractMongoConfigurationを継承したクラスを作る。

例えばこういう感じで作成する。

```java:SpringMongoConfig.java
@Configuration
@EnableMongoRepositories("jp.ac.ritsumei.cs.ubi")
public class SpringMongoConfig extends AbstractMongoConfiguration {
    @Value("${spring.data.mongodb.host}")
    private String mongoHost;

    @Value("${spring.data.mongodb.port}")
    private String mongoPort;

    @Value("${spring.data.mongodb.database}")
    private String mongoDB;

    @Override
    public MongoMappingContext mongoMappingContext()
            throws ClassNotFoundException {
        // TODO Auto-generated method stub
        return super.mongoMappingContext();
    }

    @Override
    @Bean
    public Mongo mongo() throws Exception {
        System.out.println("mongo host: " + mongoHost);
        System.out.println("mongo db: " + mongoDB);

        MongoCredential credential = MongoCredential.createMongoCRCredential("ユーザ名", mongoDB, "パス".toCharArray());
        ServerAddress serverAddress = new ServerAddress(mongoHost, Integer.parseInt(mongoPort));

        return new MongoClient(serverAddress, new ArrayList<MongoCredential>() {{
            add(credential);
        }});
    }

    @Override
    protected String getDatabaseName() {
        // TODO Auto-generated method stub
        return mongoDB;
    }
}
```

hostやportは`application.properties`に追記する

	spring.data.mongodb.host: localhost
	spring.data.mongodb.port: 27017
	spring.data.mongodb.database: hogehoge
	
##モデル
MongoDBから取ってきた結果をぶち込むクラスを適当に作る。

`@Document`をつけることで、どのコレクションを対象とするか指定する
変数名とフィールド名が違うなら`@Field`で指定する

```java:HogeNode.java
@Document(collection = "HogeNode")
public class HogeNode {
    @Field("_id")
    private String objectId;
    //省略
```

##Repository
findとかするクラスを作る。

```java:HogeRepository.java
public interface HogeRepository extends MongoRepository<Hoge, String> {
    public HogeNode findByフィールド名(String id);
}
```

findByとかすると、By以降のフィールド名で引数の値をfindしてくれる。
Likeを加えたり、AndやOrを足したり、countByにするとそのようにMongoDBを叩いてくれる。

##呼ぶ
	@Autowired
    	private HogeRepository repository;
    	
`@Autowired`を付けると勝手にバインド？してくれるので、適当な場所でインスタンス作る。
    	
    	HogeNode hogeNode = repository.findById(id);
    		
さっき作ったfindByで呼ぶと結果が得られる。
結果がなかったらnull

##Aggregate
Aggregateしたい場合には、

```java:ProductRepositoryCustom.java
interface ProductRepositoryCustom {
    List<HogeNode> aggregate(int sort);
}
```

このようなinterfaceを作って、

```java:ProductRepositoryImpl.java
@Repository
class ProductRepositoryImpl implements ProductRepositoryCustom {

    @Autowired
    private MongoTemplate mongoTemplate;

    @Override
    public List<HogeNode> aggregate(int sort) {
            Aggregation aggregation = Aggregation.newAggregation(
                Aggregation.sort(sort == 1 ? Sort.Direction.DESC : Sort.Direction.ASC, "nodeId"),
        );
        AggregationResults<HogeNode> results = mongoTemplate.aggregate(aggregation, "CollectionName", HogeNode.class);
        return results.getMappedResults();
    }
```

implementsしたクラスで`MongoTemplete`を`@Autowired`して`Aggregation`を作る。

###呼ぶ
	@Autowired
    	private ProductRepositoryCustom repositoryCustom;
    	
でインスタンスが得られるので、コントローラあたりから呼ぶ

###group
	Aggregation.group("param1")
		.first("param1").as("param1")
		.avg("param2").as("param2")

groupした後の結果は`HogeNode.class`のメンバを全部網羅しておかないとダメっぽい？

##複数のDB
先述した`SpringMongoConfig.java`に

	@Bean(autowire = Autowire.BY_NAME, name = "hogeTemplate")
	public MongoTemplate ritsubiMongoTemplate() throws Exception {
    	return new MongoTemplate(mongo(), "hogeDB");
    }

任意の`MongoTemplate`の名前を指定して、任意のDB名を引数として`MongoTemplate`を返すメソッドを実装する。
`MongoTemplate`を`@Autowire`してるところで指定したインスタンス名にすると、そのDBに繋がった`MongoTemplate`となる。

ただし、こうすると先述した`HogeRepository.java`が`SpringMongoConfig.java`の

    @Override
    protected String getDatabaseName() {
        return mongoDB;
    }

に依存してしまう？

#エラーぺージ

```java:Config.java
@Configuration
public class Config {
    @Bean
    public ErrorAttributes errorAttributes() {
        return new DefaultErrorAttributes() {
            @Override
            public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
                Map<String, Object> errorAttributes = new HashMap<>();
                errorAttributes.put("timestamp", new Date());
                errorAttributes.put("status", 400);
                errorAttributes.put("error", "Bad Request");
                errorAttributes.put("message", "Bad Request");

                return errorAttributes;
            }
        };
    }
}
```

`@Configuration`をつけたクラスに`@Bean`を付けて`ErrorAttributes`を実装。
putしてるvalueは必須。

#認証
簡単化のため、インメモリでユーザ認証するので以下を実装

```java:WebSecurityConfig.java
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("name").password("pass").authorities("ADMIN");
    }
}
```

こんな https://user:pass@localhost:8443/hogehoge 感じに書くだけで認証されてアクセスできる。

ただ、ブラウザからアクセスすると`/login`にリダイレクトされる。
(ブラウザが勝手にやってるだけ？）

#レスポンスのJSON
レスポンスのJSONはインデントが綺麗になってないので読み辛いが、

	@Configuration
	public class CustomWebMvcConfiguration extends WebMvcConfigurationSupport {
	
	    @Override
	    protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
	        for (HttpMessageConverter<?> converter : converters) {
	            if (converter instanceof MappingJackson2HttpMessageConverter) {
	                MappingJackson2HttpMessageConverter jacksonConverter = (MappingJackson2HttpMessageConverter) converter;
	                jacksonConverter.setPrettyPrint(true);
	            }
	        }
	    }
	}
	
を実装すると、prettyになる。