これは私の Resource クラスです:
@Component
@Path("forum/categories")
@Produces(MediaType.APPLICATION_JSON)
public class ForumCategoryResource {
@Context
UriInfo uriInfo;
@Resource(name="forumService")
private ForumService forumService;
@GET
@Path("tree/{rootId}")
@Produces(MediaType.APPLICATION_JSON)
public Response getCategoryTreeById(@PathParam("rootId") Integer rootId) {
System.out.println("Got request for tree by id: " + rootId);
Tree<ForumCategory> tree = forumService.getCategoryTreeById(rootId);
return Response.ok(new GenericEntity<Tree<ForumCategory>>(
tree, tree.getClass()
)).build();
}
そして、これは私のデータ型ジェネリック クラスです。
public class Tree<T> {
public interface Converter<T, S> {
S convert (T t);
}
public enum IterationStrategy {
PREORDER,
LEVELORDER,
POSTORDER
}
private T value;
private Tree<T> parent;
private List<Tree<T>> children;
private int height;
public Tree(Tree<T> tree) {
this.value = tree.value;
this.children = createListAdapterForHeightCalculation(tree.children);
this.parent = tree.parent;
this.height = tree.height;
}
public Tree(T value) {
this.value = value;
this.children = createListAdapterForHeightCalculation(Lists.<Tree<T>>newArrayList());
this.height = 1;
}
public Tree<T> add(Tree<T> tree) {
children.add(tree);
tree.parent = this;
calculateHeightToRoot(tree);
return tree;
}
private ListAdapter<Tree<T>> createListAdapterForHeightCalculation(final List<Tree<T>> list) {
return new ListAdapter<Tree<T>>(list) {
@Override
public Tree<T> remove(int index) {
Tree<T> removedElement = super.remove(index);
int newMaxHeightAtThisLevel = getMaxHeight(list);
removedElement.parent.height = newMaxHeightAtThisLevel + 1;
calculateHeightToRoot(removedElement.parent);
return removedElement;
}
private int getMaxHeight(List<Tree<T>> children) {
int result = 0;
for (Tree<T> tree : children) {
result = Math.max(tree.height, result);
}
return result;
}
};
}
private void calculateHeight(Tree<T> parent, Tree<T> child) {
int height = Math.max(parent.height, child.height + 1);
parent.height = height;
}
private void calculateHeightToRoot(Tree<T> tree) {
Tree<T> parent = tree.parent;
Tree<T> child = tree;
while ( ! child.isRoot() ) {
calculateHeight(parent, child);
parent = parent.parent;
child = child.parent;
}
}
public <S> Tree<S> convert(Converter<T, S> converter) {
Tree<S> result = new Tree<S>(converter.convert(this.value));
for (Tree<T> child : this.children) {
result.add(child.convert(converter));
}
return result;
}
public Tree<T> add(T value) {
return add(new Tree<T>(value));
}
public int getHeight() {
return height;
}
public Tree<T> getParent() {
return parent;
}
public T getValue() {
return value;
}
public List<Tree<T>> getChildren() {
return children;
}
public boolean isRoot() {
return parent == null;
}
public boolean isLeaf() {
return children.isEmpty();
}
public boolean hasParent() {
return ! isRoot();
}
public boolean hasChildren() {
return ! isLeaf();
}
@Override
public String toString() {
return toString(this, null);
}
private String toString(Tree<T> tree, String prefix) {
prefix = (prefix == null) ? "" : prefix;
StringBuilder result = new StringBuilder(prefix + ObjectUtils.toString(tree.value) + ":" + tree.height);
result.append("\n");
for (Tree<T> child : tree.children) {
String newPrefix;
if (StringUtils.isEmpty(prefix)) {
newPrefix = "|- ";
}
else {
newPrefix = "| " + prefix;
}
result.append(toString(child, newPrefix));
}
return result.toString();
}
public List<T> getPathFromRoot() {
List<T> result = getPathToRoot();
Collections.reverse(result);
return result;
}
public List<T> getPathToRoot() {
List<T> result = new ArrayList<T>();
Tree<T> pathElement = this;
while ( ! pathElement.isRoot() ) {
result.add(pathElement.value);
pathElement = pathElement.parent;
}
result.add(pathElement.value);
return result;
}
public boolean containsChildWithValue(T value) {
for (Tree<T> child : children) {
if (ObjectUtils.equals(value,child.getValue())) return true;
}
return false;
}
public Tree<T> findFirst(T value) {
return findFirst(value, IterationStrategy.PREORDER);
}
public Tree<T> findFirst(T value, IterationStrategy strategy) {
switch (strategy) {
case PREORDER: return findFirstPreOrder(this, value);
case LEVELORDER: return findFirstLevelOrder(Arrays.asList(this), value);
case POSTORDER: return findFirstPostOrder(this, value);
}
return null;
}
private Tree<T> findFirstLevelOrder(List<Tree<T>> treeList, T value) {
List<Tree<T>> treeListChildren = new ArrayList<Tree<T>>();
for (Tree<T> tree : treeList) {
if (tree.value.equals(value)) {
return tree;
}
treeListChildren.addAll(tree.children);
}
if (treeListChildren.size() > 0) {
return findFirstLevelOrder(treeListChildren, value);
}
return null;
}
private Tree<T> findFirstPostOrder(Tree<T> tree, T value) {
for (Tree<T> child : tree.children) {
Tree<T> result = findFirstPostOrder(child, value);
if (result != null) {
return result;
}
}
if (tree.value.equals(value)) {
return tree;
}
return null;
}
private Tree<T> findFirstPreOrder(Tree<T> tree, T value) {
if (tree.value.equals(value)) {
return tree;
}
for (Tree<T> child : tree.children) {
Tree<T> result = findFirstPreOrder(child, value);
if (result != null) return result;
}
return null;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((children == null) ? 0 : children.hashCode());
result = prime * result + height;
result = prime * result + ((value == null) ? 0 : value.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
@SuppressWarnings("unchecked")
Tree<T> other = (Tree<T>) obj;
if (children == null) {
if (other.children != null)
return false;
} else if (!children.equals(other.children))
return false;
if (height != other.height)
return false;
if (value == null) {
if (other.value != null)
return false;
} else if (!value.equals(other.value))
return false;
return true;
}
}
そして、これは私のドメインクラスです:
public class ForumCategory {
private Integer forumCategoryId;
private String nameKey;
private String descriptionKey;
public ForumCategory() {
}
public ForumCategory(String nameKey, String descriptionKey) {
this.nameKey = nameKey;
this.descriptionKey = descriptionKey;
}
@Override
public int hashCode() {
return new HashCodeBuilder().append(forumCategoryId).toHashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
ForumCategory other = (ForumCategory) obj;
return new EqualsBuilder().append(this.forumCategoryId, other.forumCategoryId).isEquals();
}
public String getNameKey() {
return nameKey;
}
public void setNameKey(String nameKey) {
this.nameKey = nameKey;
}
public String getDescriptionKey() {
return descriptionKey;
}
public void setDescriptionKey(String descriptionKey) {
this.descriptionKey = descriptionKey;
}
public Integer getForumCategoryId() {
return forumCategoryId;
}
public void setForumCategoryId(Integer forumCategoryId) {
this.forumCategoryId = forumCategoryId;
}
@Override
public String toString() {
return "ForumCategory [forumCategoryId=" + forumCategoryId
+ ", nameKey=" + nameKey + ", descriptionKey=" + descriptionKey
+ "]";
}
}
そして、これは Tree クラスの MessageBodyWriter です。
@Provider
@Produces(MediaType.APPLICATION_JSON)
public class TreeWriter implements MessageBodyWriter<Tree<?>> {
private static final Tree<?> NULL_TREE = new Tree<Object>((Object)null);
private static final Charset CHARSET = Charset.forName("UTF-8");
private ObjectMapper objectMapper = new ObjectMapper();
@Override
public boolean isWriteable(Class<?> type, Type genericType,
Annotation[] annotations, MediaType mediaType) {
System.out.println("isWritable");
return type == NULL_TREE.getClass();
}
@Override
public long getSize(Tree<?> tree, Class<?> type, Type genericType,
Annotation[] annotations, MediaType mediaType) {
System.out.println("getSize");
try {
return getTreeAsJsonString(tree).length() * Character.SIZE / Byte.SIZE;
}
catch (IOException e) {
throw new WebApplicationException(e);
}
}
@Override
public void writeTo(Tree<?> tree, Class<?> type, Type genericType,
Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, Object> httpHeaders,
OutputStream entityStream) throws IOException,
WebApplicationException {
System.out.println("writeTo");
System.out.println(tree);
String jsonString = getTreeAsJsonString(tree);
System.out.println(jsonString);
entityStream.write(jsonString.getBytes(CHARSET));
entityStream.flush();
}
private String getTreeAsJsonString(Tree<?> tree) throws IOException {
if (tree != null) {
StringBuilder result = new StringBuilder();
appendTree(result, tree);
return result.toString();
}
return "null";
}
private void appendTree(StringBuilder result, Tree<?> tree) throws IOException {
result.append("{");
appendTreeValue(result, tree);
result.append(",");
appendTreeChildren(result, tree);
result.append(",");
appendTreeHeight(result, tree);
result.append("}");
}
private void appendTreeHeight(StringBuilder result, Tree<?> tree) {
result.append("\"height\":");
result.append(tree.getHeight());
}
private void appendTreeChildren(StringBuilder result, Tree<?> tree) throws IOException {
result.append("\"children\":[");
boolean hasChildren = false;
for (Tree<?> child : tree.getChildren()) {
appendTree(result, child);
result.append(",");
hasChildren = true;
}
if (hasChildren) {
result.deleteCharAt(result.length() - 1);
}
result.append("]");
}
private void appendTreeValue(StringBuilder result, Tree<?> tree) throws IOException {
result.append("\"value\":");
result.append(objectMapper.writeValueAsString(tree.getValue()));
}
}
System.out.println コードから次の出力が得られます。
Got request for tree by id: 1
isWritable
getSize
writeTo
ForumCategory [forumCategoryId=1, nameKey=cat.name.a, descriptionKey=cat.desc.a]:2
|- ForumCategory [forumCategoryId=2, nameKey=a, descriptionKey=b]:1
{"value":{"forumCategoryId":1,"nameKey":"cat.name.a","descriptionKey":"cat.desc.a"},"children":[{"value":{"forumCategoryId":2,"nameKey":"a","descriptionKey":"b"},"children":[],"height":1}],"height":2}
問題は、ブラウザに何も表示されないことです。通常の POJO から json を受け取るのは魅力的です。私が抱えている唯一の問題は、 Tree<?> データを返す残りのサービスから回答を得ることです。
編集:
POSTMAN レスト クライアントを使用してみましたが、次のメッセージで失敗しました。
応答ステータスは 0 でした。これがいつ発生するかについて詳しくは、W3C XMLHttpRequest レベル 2 仕様を確認してください。
W3C仕様には次のように書かれています:
status 属性は、次の手順を実行した結果を返す必要があります。
状態が UNSENT または OPENED の場合は、0 を返し、これらの手順を終了します。
エラー フラグが設定されている場合は、0 を返し、これらの手順を終了します。
HTTP ステータス コードを返します。