2016-12-30 6 views
0

これはより一般的なJava質問ですが、私は何をしようとしているのかを説明し、誰かが私に正しい方法を教えてくれることを願っています。Dropwizard抽象リソースデザイン

私のすべてのリソースが拡張できる汎用抽象クラスを作成しようとしています。

抽象クラスは、標準のもののための基本的なCRUD実装を持っている

私は単純に

@Path("/movies") 
public class MovieResource extends AbstractResource { 
    public MovieResource(MovieRepository repository) { 
     super(repository); 
    } 
} 

をやって問題なくこれを使用することができますし、私は今、すべてのメソッドにアクセスすることができ、必要に応じて上書き

@Produces("application/vnd.api+json") 
@Consumes("application/vnd.api+json") 
public abstract class AbstractResource { 

    static final Logger LOGGER = LoggerFactory.getLogger(AbstractResource.class); 

    AbstractRepository repository; 

    AbstractResource(AbstractRepository repository) { 
     this.repository = repository; 
    } 

    @GET 
    public Response getAll(@Auth User user, @QueryParam("query") String query) { 
     String result = query != null ? repository.getByQuery(query) : repository.getAll(); 
     return Response.status(Response.Status.OK).entity(result).build(); 
    } 

    @GET 
    @Path("/{id}") 
    public Response getById(@Auth User user, @PathParam("id") String id) { 
     String result = repository.getById(id); 
     return Response.status(Response.Status.OK).entity(result).build(); 
    } 

    @POST 
    public Response save(@Auth User user, String payload) { 
     String result = repository.save(payload); 
     return Response.status(Response.Status.OK).entity(result).build(); 
    } 

    @PATCH 
    @Path("/{id}") 
    public Response update(@Auth User user, @PathParam("id") String id, String payload) { 
     String result = repository.update(payload); 
     return Response.status(Response.Status.OK).entity(result).build(); 
    } 

    @DELETE 
    @Path("/{id}") 
    public Response delete(@Auth User user, @PathParam("id") String id) { 
     repository.delete(id); 
     return Response.status(Response.Status.NO_CONTENT).build(); 
    } 

} 

問題が発生するのは、メソッドをオーバーロードする必要があるときです。一例として、抽象クラスから最初のgetAll方法を取る、私は唯一のMovie.class

@Path("/movies") 
public class MovieResource extends AbstractResource { 

    public MovieResource(MovieRepository repository) { 
     super(repository); 
    } 

    @GET 
    public Response getAll(@Auth User user, @QueryParam("query") String query, @QueryParam("limit") String limit, @QueryParam("page") String page) { 
     String result = repository.getPaginated(limit, page); 
     return Response.status(Response.Status.OK).entity(result).build(); 
    } 

} 

でパラメータを変更したいのでgetAll方法はただMovie.classのパラメータの異なるセットを持っています。これは、抽象の元getAll方法が既に@GET注釈を持っているため

[[FATAL] A resource model has ambiguous (sub-)resource method for HTTP method GET and input mime-types as defined by"@Consumes" and "@Produces" annotations at Java methods public javax.ws.rs.core.Response space.cuttlefish.domain.resources.MovieResource.getAll(space.cuttlefish.domain.model.User,java.lang.String,java.lang.String,java.lang.String) and public javax.ws.rs.core.Response space.cuttlefish.domain.resources.AbstractResource.getAll(space.cuttlefish.domain.model.User,java.lang.String) at matching regular expression /movies. These two methods produces and consumes exactly the same mime-types and therefore their invocation as a resource methods will always fail.; source='[email protected]'] 

を爆破するジャージーの原因となります。

どうすればこの問題を解決できますか?

抽象クラスからすべての注釈を削除してから、各リソースの注釈を上書きして再追加する必要がありますか?それはちょうど乱雑になり、エラーになりやすい...ここにはもっと良い解決策があるはずですか?

私はただ見落としてしまった何かが目に見えて明白ですか?

いくつかのヘルプが大好きです。

+0

あなたはすべてのサブクラスが短い署名でのgetAllメソッドを持ってしたくない場合は、それは抽象ベースクラスであってはなりません。継承されたメソッドは確かにここでトラブルを引き起こします。質問にJerseyタグまたはJAX-RSタグを追加することができます。 – JayK

+0

タグチップありがとうございました。うーん、それは私が現時点で行ってきたことですが、1つのクラスだけが異なるものを扱うケースを解決するために、同じコードを20の異なるクラスにコピーしても、愚かなようです。 –

+0

mixinメカニズムはいいと思います。非常に曖昧な答えは「継承を超える合成」ですが、今までJAX-RSを使ったことがないので、このマントラをここでうまく適用できるかどうかはわかりません。 – JayK

答えて

1

私はジェネリックを使用することをお勧めします。

私たちはこれと似ていますが、かなり複雑なバージョンです。当初はそれを正しく取得するのは少し難しかったですが、コードの再利用性(Javaの場合)が最大限で、コードを読みやすく/寄稿するのが簡単でした。

public abstract class AbstractResource<T extends AbstractObject, K extends AbstractObjectDto> { 

    static final Logger LOGGER = LoggerFactory.getLogger(AbstractResource.class); 

    AbstractRepository<T> repository; 
    // We have used modelmapper library to automatically convert DTO objects to database objects. But you can come up with your own solution for that. I.E implementing conversion logic on each DTO and database classes. 
    ModelMapper modelMapper = new ModelMapper(); 

    // With Java Generics, one cannot access the class type directly by simply calling 'K.class'. So you need to pass the class types explicitly as well. That is if you're using modelmapper. 
    private final Class<T> objectClass; 
    private final Class<K> objectDtoClass; 

    AbstractResource(AbstractRepository<T> repository, Class<T> objectClass, Class<K> objectDtoClass) { 
     this.repository = repository; 
     this.objectClass = objectClass; 
     this.objectDtoClass = objectDtoClass; 
    } 

    ... 

    @POST 
    public K save(@Auth User user, @Valid K payload) { 
     T databaseObject = modelmapper.map(payload, objectClass); 
     T result = repository.save(databaseObject); 
     K resultDto = modelMapper.map(result, objectDtoClass); 
     retun resultDto; 
    } 
    ... 
} 

その後、あなたはAbstractRepositoryをオーバーライドし、各オブジェクトタイプのためにsaveなどの必要なメソッドを持っているリポジトリクラス、getPaginatedなどを作成する必要があります。もちろん、MovieAbstractObjectクラスを拡張し、MovieDtoAbstractObjectDtoクラスを拡張する必要があります。

public class MovieRepository extends AbstractRepository<Movie> { 
    .... 
    Movie save(Movie movie) {...} 
} 

、残りはこのように簡単です:

@Path("/movies") 
public class MovieResource extends AbstractResource<Movie, MovieDto> { 

    public MovieResource(MovieRepository repository) { 
     super(repository, Movie.class, MovieDto.class); 
    } 
} 
+0

ありがとう、これは私が可能になることを望んでいたものです –

0

あなたのために失敗する理由は、複数のメソッドが同じURLパスにマップされているからです。しかし、方法をオーバーライドすればジャージーは不平を言うことはありません。

私はどちらかあなたの方法に @Context UriInfo uriInfoを渡すと、一般的なユーティリティメソッドにそのクエリのparamsを解析し、または

@Path("/{segment: .*}") 
@GET 
@Produces("application/json") 
public Response getAll(@PathParam("segment") PathSegment segment) 
... 

経由行列パラメータのようなものを使用して解析し、あなたのAbstractResource、中に一般的な方法を持つことをお勧めします

一般的なデフォルトの方法、あるいはその両方の組み合わせを介して再びそれらを呼び出すことができます。

このようにして、多くの場合、共通のエンドポイントにデフォルトを設定したり、カスタムの前処理を行い、一般的な使用事例のために一般的な解析メソッドに委任することができます。私はあなたが望んでいたように右の何かを持っている場合

は、次のプロジェクトで試行されました:https://github.com/researchgate/restler (免責事項:私はそこに貢献してる)