http://git-wip-us.apache.org/repos/asf/zookeeper/blob/eab8c1eb/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/resources/RuntimeExceptionMapper.java ---------------------------------------------------------------------- diff --git a/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/resources/RuntimeExceptionMapper.java b/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/resources/RuntimeExceptionMapper.java new file mode 100644 index 0000000..46f33bb --- /dev/null +++ b/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/resources/RuntimeExceptionMapper.java @@ -0,0 +1,55 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.zookeeper.server.jersey.resources; + +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; + +import org.apache.zookeeper.server.jersey.jaxb.ZError; + +/** + * Map RuntimeException to HTTP status codes + */ +@Provider +public class RuntimeExceptionMapper + implements ExceptionMapper<RuntimeException> +{ + private UriInfo ui; + + public RuntimeExceptionMapper(@Context UriInfo ui) { + this.ui = ui; + } + + public Response toResponse(RuntimeException e) { + // don't try to handle jersey exceptions ourselves + if (e instanceof WebApplicationException) { + WebApplicationException ie =(WebApplicationException) e; + return ie.getResponse(); + } + + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity( + new ZError(ui.getRequestUri().toString(), + "Error processing request due to " + e + )).build(); + } +}
http://git-wip-us.apache.org/repos/asf/zookeeper/blob/eab8c1eb/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/resources/SessionsResource.java ---------------------------------------------------------------------- diff --git a/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/resources/SessionsResource.java b/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/resources/SessionsResource.java new file mode 100644 index 0000000..0744604 --- /dev/null +++ b/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/resources/SessionsResource.java @@ -0,0 +1,135 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.zookeeper.server.jersey.resources; + +import java.io.IOException; +import java.net.URI; +import java.util.UUID; + +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.DefaultValue; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.apache.zookeeper.server.jersey.ZooKeeperService; +import org.apache.zookeeper.server.jersey.jaxb.ZError; +import org.apache.zookeeper.server.jersey.jaxb.ZSession; + +import com.sun.jersey.api.json.JSONWithPadding; + +@Path("sessions/v1/{session: .*}") +public class SessionsResource { + + private static Logger LOG = LoggerFactory.getLogger(SessionsResource.class); + + private String contextPath; + + public SessionsResource(@Context HttpServletRequest request) { + contextPath = request.getContextPath(); + if (contextPath.equals("")) { + contextPath = "/"; + } + } + + @PUT + @Produces( { MediaType.APPLICATION_JSON, "application/javascript", + MediaType.APPLICATION_XML }) + @Consumes(MediaType.APPLICATION_OCTET_STREAM) + public Response keepAliveSession(@PathParam("session") String session, + @Context UriInfo ui, byte[] data) { + + if (!ZooKeeperService.isConnected(contextPath, session)) { + throwNotFound(session, ui); + } + + ZooKeeperService.resetTimer(contextPath, session); + return Response.status(Response.Status.OK).build(); + } + + @POST + @Produces( { MediaType.APPLICATION_JSON, "application/javascript", + MediaType.APPLICATION_XML }) + public Response createSession(@QueryParam("op") String op, + @DefaultValue("5") @QueryParam("expire") String expire, + @Context UriInfo ui) { + if (!op.equals("create")) { + throw new WebApplicationException(Response.status( + Response.Status.BAD_REQUEST).entity( + new ZError(ui.getRequestUri().toString(), "")).build()); + } + + int expireInSeconds; + try { + expireInSeconds = Integer.parseInt(expire); + } catch (NumberFormatException e) { + throw new WebApplicationException(Response.status( + Response.Status.BAD_REQUEST).build()); + } + + String uuid = UUID.randomUUID().toString(); + while (ZooKeeperService.isConnected(contextPath, uuid)) { + uuid = UUID.randomUUID().toString(); + } + + // establish the connection to the ZooKeeper cluster + try { + ZooKeeperService.getClient(contextPath, uuid, expireInSeconds); + } catch (IOException e) { + LOG.error("Failed while trying to create a new session", e); + + throw new WebApplicationException(Response.status( + Response.Status.INTERNAL_SERVER_ERROR).build()); + } + + URI uri = ui.getAbsolutePathBuilder().path(uuid).build(); + return Response.created(uri).entity( + new JSONWithPadding(new ZSession(uuid, uri.toString()))) + .build(); + } + + @DELETE + @Produces( { MediaType.APPLICATION_JSON, "application/javascript", + MediaType.APPLICATION_XML, MediaType.APPLICATION_OCTET_STREAM }) + public void deleteSession(@PathParam("session") String session, + @Context UriInfo ui) { + ZooKeeperService.close(contextPath, session); + } + + private static void throwNotFound(String session, UriInfo ui) + throws WebApplicationException { + throw new WebApplicationException(Response.status( + Response.Status.NOT_FOUND).entity( + new ZError(ui.getRequestUri().toString(), session + + " not found")).build()); + } + +} http://git-wip-us.apache.org/repos/asf/zookeeper/blob/eab8c1eb/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/resources/ZErrorWriter.java ---------------------------------------------------------------------- diff --git a/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/resources/ZErrorWriter.java b/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/resources/ZErrorWriter.java new file mode 100644 index 0000000..706ab89 --- /dev/null +++ b/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/resources/ZErrorWriter.java @@ -0,0 +1,63 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.zookeeper.server.jersey.resources; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; + +import javax.ws.rs.Produces; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.ext.MessageBodyWriter; +import javax.ws.rs.ext.Provider; + +import org.apache.zookeeper.server.jersey.jaxb.ZError; + +/** + * Tell Jersey how to format an octet response error message. + */ +@Produces(MediaType.APPLICATION_OCTET_STREAM) +@Provider +public class ZErrorWriter implements MessageBodyWriter<ZError> { + + public long getSize(ZError t, Class<?> type, Type genericType, + Annotation[] annotations, MediaType mediaType) { + return -1; + } + + public boolean isWriteable(Class<?> type, Type genericType, + Annotation[] annotations, MediaType mediaType) { + return ZError.class.isAssignableFrom(type); + } + + public void writeTo(ZError t, Class<?> type, Type genericType, + Annotation[] annotations, MediaType mediaType, + MultivaluedMap<String, Object> httpHeaders, + OutputStream os) + throws IOException, WebApplicationException + { + PrintStream p = new PrintStream(os); + p.print("Request " + t.request + " failed due to " + t.message); + p.flush(); + } +} http://git-wip-us.apache.org/repos/asf/zookeeper/blob/eab8c1eb/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/resources/ZNodeResource.java ---------------------------------------------------------------------- diff --git a/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/resources/ZNodeResource.java b/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/resources/ZNodeResource.java new file mode 100644 index 0000000..77371ea --- /dev/null +++ b/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/resources/ZNodeResource.java @@ -0,0 +1,412 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.zookeeper.server.jersey.resources; + +import java.io.IOException; +import java.net.URI; +import java.util.ArrayList; +import java.util.List; + +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.DefaultValue; +import javax.ws.rs.GET; +import javax.ws.rs.HEAD; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; + +import org.apache.zookeeper.CreateMode; +import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.ZooDefs.Ids; +import org.apache.zookeeper.data.Stat; +import org.apache.zookeeper.server.jersey.ZooKeeperService; +import org.apache.zookeeper.server.jersey.jaxb.ZChildren; +import org.apache.zookeeper.server.jersey.jaxb.ZChildrenJSON; +import org.apache.zookeeper.server.jersey.jaxb.ZError; +import org.apache.zookeeper.server.jersey.jaxb.ZPath; +import org.apache.zookeeper.server.jersey.jaxb.ZStat; + +import com.sun.jersey.api.json.JSONWithPadding; + +/** + * Version 1 implementation of the ZooKeeper REST specification. + */ +// TODO test octet fully +@Path("znodes/v1{path: /.*}") +public class ZNodeResource { + private final ZooKeeper zk; + + public ZNodeResource(@DefaultValue("") @QueryParam("session") String session, + @Context UriInfo ui, + @Context HttpServletRequest request + ) + throws IOException { + + String contextPath = request.getContextPath(); + if (contextPath.equals("")) { + contextPath = "/"; + } + if (session.equals("")) { + session = null; + } else if (!ZooKeeperService.isConnected(contextPath, session)) { + throw new WebApplicationException(Response.status( + Response.Status.UNAUTHORIZED).build()); + } + zk = ZooKeeperService.getClient(contextPath, session); + } + + private void ensurePathNotNull(String path) { + if (path == null) { + throw new IllegalArgumentException("Invalid path \"" + path + "\""); + } + } + + @HEAD + @Produces( { MediaType.APPLICATION_JSON, "application/javascript", + MediaType.APPLICATION_XML }) + public Response existsZNode(@PathParam("path") String path, + @Context UriInfo ui) throws InterruptedException, KeeperException { + Stat stat = zk.exists(path, false); + if (stat == null) { + throwNotFound(path, ui); + } + return Response.status(Response.Status.OK).build(); + } + + @HEAD + @Produces( { MediaType.APPLICATION_OCTET_STREAM }) + public Response existsZNodeAsOctet(@PathParam("path") String path, + @Context UriInfo ui) throws InterruptedException, KeeperException { + Stat stat = zk.exists(path, false); + if (stat == null) { + throwNotFound(path, ui); + } + return Response.status(Response.Status.NO_CONTENT).build(); + } + + /* + * getZNodeList and getZNodeListJSON are bogus - but necessary. + * Unfortunately Jersey 1.0.3 is unable to render both xml and json properly + * in the case where a object contains a list/array. It's impossible to get + * it to render properly for both. As a result we need to split into two + * jaxb classes. + */ + + @GET + @Produces( { MediaType.APPLICATION_JSON, "application/javascript" }) + public Response getZNodeListJSON( + @PathParam("path") String path, + @QueryParam("callback") String callback, + @DefaultValue("data") @QueryParam("view") String view, + @DefaultValue("base64") @QueryParam("dataformat") String dataformat, + @Context UriInfo ui) throws InterruptedException, KeeperException { + return getZNodeList(true, path, callback, view, dataformat, ui); + } + + @GET + @Produces(MediaType.APPLICATION_XML) + public Response getZNodeList( + @PathParam("path") String path, + @QueryParam("callback") String callback, + @DefaultValue("data") @QueryParam("view") String view, + @DefaultValue("base64") @QueryParam("dataformat") String dataformat, + @Context UriInfo ui) throws InterruptedException, KeeperException { + return getZNodeList(false, path, callback, view, dataformat, ui); + } + + private Response getZNodeList(boolean json, String path, String callback, + String view, String dataformat, UriInfo ui) + throws InterruptedException, KeeperException { + ensurePathNotNull(path); + + if (view.equals("children")) { + List<String> children = new ArrayList<String>(); + for (String child : zk.getChildren(path, false)) { + children.add(child); + } + + Object child; + String childTemplate = ui.getAbsolutePath().toString(); + if (!childTemplate.endsWith("/")) { + childTemplate += "/"; + } + childTemplate += "{child}"; + if (json) { + child = new ZChildrenJSON(path, + ui.getAbsolutePath().toString(), childTemplate, + children); + } else { + child = new ZChildren(path, ui.getAbsolutePath().toString(), + childTemplate, children); + } + return Response.status(Response.Status.OK).entity( + new JSONWithPadding(child, callback)).build(); + } else { + Stat stat = new Stat(); + byte[] data = zk.getData(path, false, stat); + + byte[] data64; + String dataUtf8; + if (data == null) { + data64 = null; + dataUtf8 = null; + } else if (!dataformat.equals("utf8")) { + data64 = data; + dataUtf8 = null; + } else { + data64 = null; + dataUtf8 = new String(data); + } + ZStat zstat = new ZStat(path, ui.getAbsolutePath().toString(), + data64, dataUtf8, stat.getCzxid(), stat.getMzxid(), stat + .getCtime(), stat.getMtime(), stat.getVersion(), + stat.getCversion(), stat.getAversion(), stat + .getEphemeralOwner(), stat.getDataLength(), stat + .getNumChildren(), stat.getPzxid()); + + return Response.status(Response.Status.OK).entity( + new JSONWithPadding(zstat, callback)).build(); + } + } + + @GET + @Produces(MediaType.APPLICATION_OCTET_STREAM) + public Response getZNodeListAsOctet(@PathParam("path") String path) + throws InterruptedException, KeeperException { + ensurePathNotNull(path); + + Stat stat = new Stat(); + byte[] data = zk.getData(path, false, stat); + + if (data == null) { + return Response.status(Response.Status.NO_CONTENT).build(); + } else { + return Response.status(Response.Status.OK).entity(data).build(); + } + } + + @PUT + @Produces( { MediaType.APPLICATION_JSON, "application/javascript", + MediaType.APPLICATION_XML }) + @Consumes(MediaType.APPLICATION_OCTET_STREAM) + public Response setZNode( + @PathParam("path") String path, + @QueryParam("callback") String callback, + @DefaultValue("-1") @QueryParam("version") String versionParam, + @DefaultValue("base64") @QueryParam("dataformat") String dataformat, + @DefaultValue("false") @QueryParam("null") String setNull, + @Context UriInfo ui, byte[] data) throws InterruptedException, + KeeperException { + ensurePathNotNull(path); + + int version; + try { + version = Integer.parseInt(versionParam); + } catch (NumberFormatException e) { + throw new WebApplicationException(Response.status( + Response.Status.BAD_REQUEST).entity( + new ZError(ui.getRequestUri().toString(), path + + " bad version " + versionParam)).build()); + } + + if (setNull.equals("true")) { + data = null; + } + + Stat stat = zk.setData(path, data, version); + + ZStat zstat = new ZStat(path, ui.getAbsolutePath().toString(), null, + null, stat.getCzxid(), stat.getMzxid(), stat.getCtime(), stat + .getMtime(), stat.getVersion(), stat.getCversion(), + stat.getAversion(), stat.getEphemeralOwner(), stat + .getDataLength(), stat.getNumChildren(), stat + .getPzxid()); + + return Response.status(Response.Status.OK).entity( + new JSONWithPadding(zstat, callback)).build(); + } + + @PUT + @Produces(MediaType.APPLICATION_OCTET_STREAM) + @Consumes(MediaType.APPLICATION_OCTET_STREAM) + public void setZNodeAsOctet(@PathParam("path") String path, + @DefaultValue("-1") @QueryParam("version") String versionParam, + @DefaultValue("false") @QueryParam("null") String setNull, + @Context UriInfo ui, byte[] data) throws InterruptedException, + KeeperException { + ensurePathNotNull(path); + + int version; + try { + version = Integer.parseInt(versionParam); + } catch (NumberFormatException e) { + throw new WebApplicationException(Response.status( + Response.Status.BAD_REQUEST).entity( + new ZError(ui.getRequestUri().toString(), path + + " bad version " + versionParam)).build()); + } + + if (setNull.equals("true")) { + data = null; + } + + zk.setData(path, data, version); + } + + @POST + @Produces( { MediaType.APPLICATION_JSON, "application/javascript", + MediaType.APPLICATION_XML }) + @Consumes(MediaType.APPLICATION_OCTET_STREAM) + public Response createZNode( + @PathParam("path") String path, + @QueryParam("callback") String callback, + @DefaultValue("create") @QueryParam("op") String op, + @QueryParam("name") String name, + @DefaultValue("base64") @QueryParam("dataformat") String dataformat, + @DefaultValue("false") @QueryParam("null") String setNull, + @DefaultValue("false") @QueryParam("sequence") String sequence, + @DefaultValue("false") @QueryParam("ephemeral") String ephemeral, + @Context UriInfo ui, byte[] data) throws InterruptedException, + KeeperException { + ensurePathNotNull(path); + + if (path.equals("/")) { + path += name; + } else { + path += "/" + name; + } + + if (!op.equals("create")) { + throw new WebApplicationException(Response.status( + Response.Status.BAD_REQUEST).entity( + new ZError(ui.getRequestUri().toString(), path + + " bad operaton " + op)).build()); + } + + if (setNull.equals("true")) { + data = null; + } + + CreateMode createMode; + if (sequence.equals("true")) { + if (ephemeral.equals("false")) { + createMode = CreateMode.PERSISTENT_SEQUENTIAL; + } else { + createMode = CreateMode.EPHEMERAL_SEQUENTIAL; + } + } else if (ephemeral.equals("false")) { + createMode = CreateMode.PERSISTENT; + } else { + createMode = CreateMode.EPHEMERAL; + } + + String newPath = zk.create(path, data, Ids.OPEN_ACL_UNSAFE, createMode); + + URI uri = ui.getAbsolutePathBuilder().path(newPath).build(); + + return Response.created(uri).entity( + new JSONWithPadding(new ZPath(newPath, ui.getAbsolutePath() + .toString()))).build(); + } + + @POST + @Produces(MediaType.APPLICATION_OCTET_STREAM) + @Consumes(MediaType.APPLICATION_OCTET_STREAM) + public Response createZNodeAsOctet(@PathParam("path") String path, + @DefaultValue("create") @QueryParam("op") String op, + @QueryParam("name") String name, + @DefaultValue("false") @QueryParam("null") String setNull, + @DefaultValue("false") @QueryParam("sequence") String sequence, + @Context UriInfo ui, byte[] data) throws InterruptedException, + KeeperException { + ensurePathNotNull(path); + + if (path.equals("/")) { + path += name; + } else { + path += "/" + name; + } + + if (!op.equals("create")) { + throw new WebApplicationException(Response.status( + Response.Status.BAD_REQUEST).entity( + new ZError(ui.getRequestUri().toString(), path + + " bad operaton " + op)).build()); + } + + if (setNull.equals("true")) { + data = null; + } + + CreateMode createMode; + if (sequence.equals("true")) { + createMode = CreateMode.PERSISTENT_SEQUENTIAL; + } else { + createMode = CreateMode.PERSISTENT; + } + + String newPath = zk.create(path, data, Ids.OPEN_ACL_UNSAFE, createMode); + + URI uri = ui.getAbsolutePathBuilder().path(newPath).build(); + + return Response.created(uri).entity( + new ZPath(newPath, ui.getAbsolutePath().toString())).build(); + } + + @DELETE + @Produces( { MediaType.APPLICATION_JSON, "application/javascript", + MediaType.APPLICATION_XML, MediaType.APPLICATION_OCTET_STREAM }) + public void deleteZNode(@PathParam("path") String path, + @DefaultValue("-1") @QueryParam("version") String versionParam, + @Context UriInfo ui) throws InterruptedException, KeeperException { + ensurePathNotNull(path); + + int version; + try { + version = Integer.parseInt(versionParam); + } catch (NumberFormatException e) { + throw new WebApplicationException(Response.status( + Response.Status.BAD_REQUEST).entity( + new ZError(ui.getRequestUri().toString(), path + + " bad version " + versionParam)).build()); + } + + zk.delete(path, version); + } + + private static void throwNotFound(String path, UriInfo ui) + throws WebApplicationException { + throw new WebApplicationException(Response.status( + Response.Status.NOT_FOUND).entity( + new ZError(ui.getRequestUri().toString(), path + " not found")) + .build()); + } + +} http://git-wip-us.apache.org/repos/asf/zookeeper/blob/eab8c1eb/zookeeper-contrib/zookeeper-contrib-rest/src/python/README.txt ---------------------------------------------------------------------- diff --git a/zookeeper-contrib/zookeeper-contrib-rest/src/python/README.txt b/zookeeper-contrib/zookeeper-contrib-rest/src/python/README.txt new file mode 100644 index 0000000..acc8ffb --- /dev/null +++ b/zookeeper-contrib/zookeeper-contrib-rest/src/python/README.txt @@ -0,0 +1,9 @@ +Some basic python scripts which use the REST interface: + +zkrest.py -- basic REST ZooKeeper client +demo_master_election.py -- shows how to implement master election +demo_queue.py -- basic queue +zk_dump_tree.py -- dumps the nodes & data of a znode hierarchy + +Generally these scripts require: + * simplejson http://git-wip-us.apache.org/repos/asf/zookeeper/blob/eab8c1eb/zookeeper-contrib/zookeeper-contrib-rest/src/python/demo_master_election.py ---------------------------------------------------------------------- diff --git a/zookeeper-contrib/zookeeper-contrib-rest/src/python/demo_master_election.py b/zookeeper-contrib/zookeeper-contrib-rest/src/python/demo_master_election.py new file mode 100644 index 0000000..c0317c7 --- /dev/null +++ b/zookeeper-contrib/zookeeper-contrib-rest/src/python/demo_master_election.py @@ -0,0 +1,90 @@ +#! /usr/bin/env python + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys +import threading +import time + +from zkrest import ZooKeeper + +class Agent(threading.Thread): + """ A basic agent that wants to become a master and exit """ + + root = '/election' + + def __init__(self, id): + super(Agent, self).__init__() + self.zk = ZooKeeper() + self.id = id + + def run(self): + print 'Starting #%s' % self.id + with self.zk.session(expire=5): + + # signal agent presence + r = self.zk.create("%s/agent-" % self.root, + sequence=True, ephemeral=True) + self.me = r['path'] + + while True: + children = sorted([el['path'] \ + for el in self.zk.get_children(self.root)]) + master, previous = children[0], None + try: + index = children.index(self.me) + if index != 0: + previous = children[index-1] + except ValueError: + break + + if previous is None: + self.do_master_work() + # and don't forget to send heartbeat messages + break + else: + # do slave work in another thread + pass + + # wait for the previous agent or current master to exit / finish + while self.zk.exists(previous) or self.zk.exists(master): + time.sleep(0.5) + self.zk.heartbeat() + + # TODO signal the slave thread to exit and wait for it + # and rerun the election loop + + def do_master_work(self): + print "#%s: I'm the master: %s" % (self.id, self.me) + +def main(): + zk = ZooKeeper() + + # create the root node used for master election + if not zk.exists('/election'): + zk.create('/election') + + print 'Starting 10 agents ...' + agents = [Agent(id) for id in range(0,15)] + + map(Agent.start, agents) + map(Agent.join, agents) + + zk.delete('/election') + +if __name__ == '__main__': + sys.exit(main()) http://git-wip-us.apache.org/repos/asf/zookeeper/blob/eab8c1eb/zookeeper-contrib/zookeeper-contrib-rest/src/python/demo_queue.py ---------------------------------------------------------------------- diff --git a/zookeeper-contrib/zookeeper-contrib-rest/src/python/demo_queue.py b/zookeeper-contrib/zookeeper-contrib-rest/src/python/demo_queue.py new file mode 100644 index 0000000..9ca4c64 --- /dev/null +++ b/zookeeper-contrib/zookeeper-contrib-rest/src/python/demo_queue.py @@ -0,0 +1,99 @@ +#! /usr/bin/env python + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +# This is a simple message queue built on top of ZooKeeper. In order +# to be used in production it needs better error handling but it's +# still useful as a proof-of-concept. + +# Why use ZooKeeper as a queue? Highly available by design and has +# great performance. + +import sys +import threading +import time + +from zkrest import ZooKeeper + +class Queue(object): + def __init__(self, root, zk): + self.root = root + + self.zk = zk + + def put(self, data): + self.zk.create("%s/el-" % self.root, str(data), sequence=True, ephemeral=True) + + # creating ephemeral nodes for easy cleanup + # in a real world scenario you should create + # normal sequential znodes + + def fetch(self): + """ Pull an element from the queue + + This function is not blocking if the queue is empty, it will + just return None. + """ + children = sorted(self.zk.get_children(self.root), \ + lambda a, b: cmp(a['path'], b['path'])) + + if not children: + return None + + try: + first = children[0] + self.zk.delete(first['path'], version=first['version']) + if 'data64' not in first: + return '' + else: + return first['data64'].decode('base64') + + except (ZooKeeper.WrongVersion, ZooKeeper.NotFound): + # someone changed the znode between the get and delete + # this should not happen + # in practice you should retry the fetch + raise + + +def main(): + zk = ZooKeeper() + zk.start_session(expire=60) + + if not zk.exists('/queue'): + zk.create('/queue') + q = Queue('/queue', zk) + + print 'Pushing to queue 1 ... 5' + map(q.put, [1,2,3,4,5]) + + print 'Extracting ...' + while True: + el = q.fetch() + if el is None: + break + print el + + zk.close_session() + zk.delete('/queue') + + print 'Done.' + + +if __name__ == '__main__': + sys.exit(main()) + http://git-wip-us.apache.org/repos/asf/zookeeper/blob/eab8c1eb/zookeeper-contrib/zookeeper-contrib-rest/src/python/test.py ---------------------------------------------------------------------- diff --git a/zookeeper-contrib/zookeeper-contrib-rest/src/python/test.py b/zookeeper-contrib/zookeeper-contrib-rest/src/python/test.py new file mode 100644 index 0000000..363747a --- /dev/null +++ b/zookeeper-contrib/zookeeper-contrib-rest/src/python/test.py @@ -0,0 +1,163 @@ +#! /usr/bin/env python + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import time +import unittest + +from zkrest import ZooKeeper + +class ZooKeeperREST_TestCase(unittest.TestCase): + + BASE_URI = 'http://localhost:9998' + + def setUp(self): + self.zk = ZooKeeper(self.BASE_URI) + + def tearDown(self): + try: + self.zk.delete('/test') + except ZooKeeper.NotFound: + pass + + def test_get_root_node(self): + assert self.zk.get('/') is not None + + def test_get_node_not_found(self): + self.assertRaises(ZooKeeper.NotFound, \ + self.zk.get, '/dummy-node') + + def test_exists_node(self): + assert self.zk.exists('/zookeeper') is True + + def test_get_children(self): + assert any([child['path'] == '/zookeeper/quota' \ + for child in self.zk.get_children('/zookeeper')]) + + def test_create_znode(self): + try: + self.zk.create('/test') + except ZooKeeper.ZNodeExists: + pass # it's ok if already exists + assert self.zk.exists('/test') is True + + def test_create_hierarchy(self): + try: + self.zk.delete(['/a/b', '/a']) + except ZooKeeper.NotFound: + pass + + self.zk.create('/a') + self.zk.create('/a/b') + + self.zk.delete(['/a/b', '/a']) + + def test_create_with_data(self): + self.zk.create('/test', 'some-data') + + zn = self.zk.get('/test') + self.assertEqual(zn.get('data64', None), \ + 'some-data'.encode('base64').strip()) + + def test_delete_znode(self): + self.zk.create('/test') + + self.zk.delete('/test') + assert not self.zk.exists('/test') + + def test_delete_older_version(self): + self.zk.create('/test') + + zn = self.zk.get('/test') + # do one more modification in order to increase the version number + self.zk.set('/test', 'dummy-data') + + self.assertRaises(ZooKeeper.WrongVersion, \ + self.zk.delete, '/test', version=zn['version']) + + def test_delete_raise_not_found(self): + self.zk.create('/test') + + zn = self.zk.get('/test') + self.zk.delete('/test') + + self.assertRaises(ZooKeeper.NotFound, \ + self.zk.delete, '/test', version=zn['version']) + + def test_set(self): + self.zk.create('/test') + + self.zk.set('/test', 'dummy') + + self.assertEqual(self.zk.get('/test')['data64'], \ + 'dummy'.encode('base64').strip()) + + def test_set_with_older_version(self): + if not self.zk.exists('/test'): + self.zk.create('/test', 'random-data') + + zn = self.zk.get('/test') + self.zk.set('/test', 'new-data') + self.assertRaises(ZooKeeper.WrongVersion, self.zk.set, \ + '/test', 'older-version', version=zn['version']) + + def test_set_null(self): + if not self.zk.exists('/test'): + self.zk.create('/test', 'random-data') + self.zk.set('/test', 'data') + assert 'data64' in self.zk.get('/test') + + self.zk.set('/test', null=True) + assert 'data64' not in self.zk.get('/test') + + def test_create_ephemeral_node(self): + with self.zk.session(): + if self.zk.exists('/ephemeral-test'): + self.zk.delete('/ephemeral-test') + + self.zk.create('/ephemeral-test', ephemeral=True) + zn = self.zk.get('/ephemeral-test') + + assert zn['ephemeralOwner'] != 0 + + def test_create_session(self): + with self.zk.session() as sid: + self.assertEqual(len(sid), 36) # UUID + + def test_session_invalidation(self): + self.zk.start_session(expire=1) + self.zk.create('/ephemeral-test', ephemeral=True) + + # keep the session alive by sending heartbeat requests + for _ in range(1,2): + self.zk.heartbeat() + time.sleep(0.9) + + time.sleep(2) # wait for the session to expire + self.assertRaises(ZooKeeper.InvalidSession, \ + self.zk.create, '/ephemeral-test', ephemeral=True) + + def test_presence_signaling(self): + with self.zk.session(expire=1): + self.zk.create('/i-am-online', ephemeral=True) + assert self.zk.exists('/i-am-online') + assert not self.zk.exists('/i-am-online') + + +if __name__ == '__main__': + unittest.main() + http://git-wip-us.apache.org/repos/asf/zookeeper/blob/eab8c1eb/zookeeper-contrib/zookeeper-contrib-rest/src/python/zk_dump_tree.py ---------------------------------------------------------------------- diff --git a/zookeeper-contrib/zookeeper-contrib-rest/src/python/zk_dump_tree.py b/zookeeper-contrib/zookeeper-contrib-rest/src/python/zk_dump_tree.py new file mode 100755 index 0000000..517d23b --- /dev/null +++ b/zookeeper-contrib/zookeeper-contrib-rest/src/python/zk_dump_tree.py @@ -0,0 +1,108 @@ +#!/usr/bin/python + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import getopt +import sys +import simplejson +import urllib2 +from base64 import b64decode + +printdata = False +fullpath = False + +def dump_node(url, depth): + """Dump the node, then dump children recursively + + Arguments: + - `url`: + - `depth`: + """ + req = urllib2.urlopen(url) + resp = simplejson.load(req) + if 'Error' in resp: + raise resp['Error'] + + if fullpath: + name = resp['path'] + else: + name = '/' + resp['path'].split('/')[-1] + + data64 = resp.get('data64') + dataUtf8 = resp.get('dataUtf8') + if data64 and printdata: + data = b64decode(data64) + print '%(indent)s%(name)s = b64(%(data64)s) str(%(data)s)' % \ + {'indent':' '*2*depth, 'name':name, 'data64':data64, 'data':data} + elif dataUtf8 and printdata: + print '%(indent)s%(name)s = %(data)s' % \ + {'indent':' '*2*depth, 'name':name, 'data':dataUtf8} + else: + print '%(indent)s%(name)s' % {'indent':' '*2*depth, 'name':name} + + req = urllib2.urlopen(resp['uri'] + '?view=children') + resp = simplejson.load(req) + + for child in resp.get('children', []): + dump_node(resp['child_uri_template'] + .replace("{child}", urllib2.quote(child)), + depth + 1) + +def zk_dump_tree(url, root): + """Dump the tree starting at the roota + + Arguments: + - `root`: + """ + dump_node(url + '/znodes/v1' + root, 0) + +def usage(): + """Usage + """ + print 'Usage: zk_dump_tree.py [-h|--help -u|--url=url -d|--data -f|--fullpath -r|--root=root]' + print ' where url is the url of the rest server, data is whether to' + print ' to include node data on output, root is the znode root' + print ' fullpath prints the full node path (useful for copy/paste)' + +if __name__ == '__main__': + try: + opts, args = getopt.getopt(sys.argv[1:], + "hu:dfr:", ["help", "url=", "data", "fullpath", "root="]) + except getopt.GetoptError, err: + # print help information and exit: + print str(err) # will print something like "option -a not recognized" + usage() + sys.exit(2) + url ='http://localhost:9998' + root = '/' + for o, a in opts: + if o in ("-d", "--data"): + printdata = True + elif o in ("-h", "--help"): + usage() + sys.exit() + elif o in ("-u", "--url"): + url = a + elif o in ("-r", "--root"): + root = a + elif o in ("-f", "--fullpath"): + fullpath = True + else: + assert False, "unhandled option" + + print 'Accessing REST server at ' + url + zk_dump_tree(url, root) http://git-wip-us.apache.org/repos/asf/zookeeper/blob/eab8c1eb/zookeeper-contrib/zookeeper-contrib-rest/src/python/zkrest.py ---------------------------------------------------------------------- diff --git a/zookeeper-contrib/zookeeper-contrib-rest/src/python/zkrest.py b/zookeeper-contrib/zookeeper-contrib-rest/src/python/zkrest.py new file mode 100644 index 0000000..c009d5d --- /dev/null +++ b/zookeeper-contrib/zookeeper-contrib-rest/src/python/zkrest.py @@ -0,0 +1,218 @@ + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import urllib2 +import urllib +import simplejson + +from contextlib import contextmanager + +class RequestWithMethod(urllib2.Request): + """ Request class that know how to set the method name """ + def __init__(self, *args, **kwargs): + urllib2.Request.__init__(self, *args, **kwargs) + self._method = None + + def get_method(self): + return self._method or \ + urllib2.Request.get_method(self) + + def set_method(self, method): + self._method = method + +class ZooKeeper(object): + + class Error(Exception): pass + + class NotFound(Error): pass + + class ZNodeExists(Error): pass + + class InvalidSession(Error): pass + + class WrongVersion(Error): pass + + def __init__(self, uri = 'http://localhost:9998'): + self._base = uri + self._session = None + + def start_session(self, expire=5, id=None): + """ Create a session and return the ID """ + if id is None: + url = "%s/sessions/v1/?op=create&expire=%d" % (self._base, expire) + self._session = self._do_post(url)['id'] + else: + self._session = id + return self._session + + def close_session(self): + """ Close the session on the server """ + if self._session is not None: + url = '%s/sessions/v1/%s' % (self._base, self._session) + self._do_delete(url) + self._session = None + + def heartbeat(self): + """ Send a heartbeat request. This is needed in order to keep a session alive """ + if self._session is not None: + url = '%s/sessions/v1/%s' % (self._base, self._session) + self._do_put(url, '') + + @contextmanager + def session(self, *args, **kwargs): + """ Session handling using a context manager """ + yield self.start_session(*args, **kwargs) + self.close_session() + + def get(self, path): + """ Get a node """ + url = "%s/znodes/v1%s" % (self._base, path) + return self._do_get(url) + + def get_children(self, path): + """ Get all the children for a given path. This function creates a generator """ + url = "%s/znodes/v1%s?view=children" % (self._base, path) + resp = self._do_get(url) + for child in resp.get('children', []): + try: + yield self._do_get(resp['child_uri_template']\ + .replace('{child}', urllib2.quote(child))) + except ZooKeeper.NotFound: + continue + + def create(self, path, data=None, sequence=False, ephemeral=False): + """ Create a new node. By default this call creates a persistent znode. + + You can also create an ephemeral or a sequential znode. + """ + ri = path.rindex('/') + head, name = path[:ri+1], path[ri+1:] + if head != '/': head = head[:-1] + + flags = { + 'null': 'true' if data is None else 'false', + 'ephemeral': 'true' if ephemeral else 'false', + 'sequence': 'true' if sequence else 'false' + } + if ephemeral: + if self._session: + flags['session'] = self._session + else: + raise ZooKeeper.Error, 'You need a session '\ + 'to create an ephemeral node' + flags = urllib.urlencode(flags) + + url = "%s/znodes/v1%s?op=create&name=%s&%s" % \ + (self._base, head, name, flags) + + return self._do_post(url, data) + + def set(self, path, data=None, version=-1, null=False): + """ Set the value of node """ + url = "%s/znodes/v1%s?%s" % (self._base, path, \ + urllib.urlencode({ + 'version': version, + 'null': 'true' if null else 'false' + })) + return self._do_put(url, data) + + def delete(self, path, version=-1): + """ Delete a znode """ + if type(path) is list: + map(lambda el: self.delete(el, version), path) + return + + url = '%s/znodes/v1%s?%s' % (self._base, path, \ + urllib.urlencode({ + 'version':version + })) + try: + return self._do_delete(url) + except urllib2.HTTPError, e: + if e.code == 412: + raise ZooKeeper.WrongVersion(path) + elif e.code == 404: + raise ZooKeeper.NotFound(path) + raise + + def exists(self, path): + """ Do a znode exists """ + try: + self.get(path) + return True + except ZooKeeper.NotFound: + return False + + def _do_get(self, uri): + """ Send a GET request and convert errors to exceptions """ + try: + req = urllib2.urlopen(uri) + resp = simplejson.load(req) + + if 'Error' in resp: + raise ZooKeeper.Error(resp['Error']) + + return resp + except urllib2.HTTPError, e: + if e.code == 404: + raise ZooKeeper.NotFound(uri) + raise + + def _do_post(self, uri, data=None): + """ Send a POST request and convert errors to exceptions """ + try: + req = urllib2.Request(uri, {}) + req.add_header('Content-Type', 'application/octet-stream') + if data is not None: + req.add_data(data) + + resp = simplejson.load(urllib2.urlopen(req)) + if 'Error' in resp: + raise ZooKeeper.Error(resp['Error']) + return resp + + except urllib2.HTTPError, e: + if e.code == 201: + return True + elif e.code == 409: + raise ZooKeeper.ZNodeExists(uri) + elif e.code == 401: + raise ZooKeeper.InvalidSession(uri) + raise + + def _do_delete(self, uri): + """ Send a DELETE request """ + req = RequestWithMethod(uri) + req.set_method('DELETE') + req.add_header('Content-Type', 'application/octet-stream') + return urllib2.urlopen(req).read() + + def _do_put(self, uri, data): + """ Send a PUT request """ + try: + req = RequestWithMethod(uri) + req.set_method('PUT') + req.add_header('Content-Type', 'application/octet-stream') + if data is not None: + req.add_data(data) + + return urllib2.urlopen(req).read() + except urllib2.HTTPError, e: + if e.code == 412: # precondition failed + raise ZooKeeper.WrongVersion(uri) + raise + http://git-wip-us.apache.org/repos/asf/zookeeper/blob/eab8c1eb/zookeeper-contrib/zookeeper-contrib-rest/src/test/java/org/apache/zookeeper/server/jersey/Base.java ---------------------------------------------------------------------- diff --git a/zookeeper-contrib/zookeeper-contrib-rest/src/test/java/org/apache/zookeeper/server/jersey/Base.java b/zookeeper-contrib/zookeeper-contrib-rest/src/test/java/org/apache/zookeeper/server/jersey/Base.java new file mode 100644 index 0000000..924f796 --- /dev/null +++ b/zookeeper-contrib/zookeeper-contrib-rest/src/test/java/org/apache/zookeeper/server/jersey/Base.java @@ -0,0 +1,91 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.zookeeper.server.jersey; + +import java.io.ByteArrayInputStream; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.apache.zookeeper.CreateMode; +import org.apache.zookeeper.JUnit4ZKTestRunner; +import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.ZooDefs.Ids; +import org.apache.zookeeper.server.jersey.SetTest.MyWatcher; +import org.apache.zookeeper.server.jersey.cfg.RestCfg; +import org.junit.After; +import org.junit.Before; +import org.junit.runner.RunWith; + +import com.sun.jersey.api.client.Client; +import com.sun.jersey.api.client.WebResource; + +/** + * Test stand-alone server. + * + */ +@RunWith(JUnit4ZKTestRunner.class) +public class Base { + protected static final Logger LOG = LoggerFactory.getLogger(Base.class); + + protected static final String CONTEXT_PATH = "/zk"; + protected static final int GRIZZLY_PORT = 10104; + protected static final String BASEURI = String.format( + "http://localhost:%d%s", GRIZZLY_PORT, CONTEXT_PATH); + protected static final String ZKHOSTPORT = "localhost:22182"; + protected Client client; + protected WebResource znodesr, sessionsr; + + protected ZooKeeper zk; + + private RestMain rest; + + @Before + public void setUp() throws Exception { + RestCfg cfg = new RestCfg(new ByteArrayInputStream(String.format( + "rest.port=%s\n" + + "rest.endpoint.1=%s;%s\n", + GRIZZLY_PORT, CONTEXT_PATH, ZKHOSTPORT).getBytes())); + + rest = new RestMain(cfg); + rest.start(); + + zk = new ZooKeeper(ZKHOSTPORT, 30000, new MyWatcher()); + + client = Client.create(); + znodesr = client.resource(BASEURI).path("znodes/v1"); + sessionsr = client.resource(BASEURI).path("sessions/v1/"); + } + + @After + public void tearDown() throws Exception { + client.destroy(); + zk.close(); + rest.stop(); + } + + protected static String createBaseZNode() throws Exception { + ZooKeeper zk = new ZooKeeper(ZKHOSTPORT, 30000, new MyWatcher()); + + String baseZnode = zk.create("/test-", null, Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT_SEQUENTIAL); + zk.close(); + + return baseZnode; + } +} http://git-wip-us.apache.org/repos/asf/zookeeper/blob/eab8c1eb/zookeeper-contrib/zookeeper-contrib-rest/src/test/java/org/apache/zookeeper/server/jersey/CreateTest.java ---------------------------------------------------------------------- diff --git a/zookeeper-contrib/zookeeper-contrib-rest/src/test/java/org/apache/zookeeper/server/jersey/CreateTest.java b/zookeeper-contrib/zookeeper-contrib-rest/src/test/java/org/apache/zookeeper/server/jersey/CreateTest.java new file mode 100644 index 0000000..018c54b --- /dev/null +++ b/zookeeper-contrib/zookeeper-contrib-rest/src/test/java/org/apache/zookeeper/server/jersey/CreateTest.java @@ -0,0 +1,162 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.zookeeper.server.jersey; + +import java.util.Arrays; +import java.util.Collection; + +import javax.ws.rs.core.MediaType; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.apache.zookeeper.WatchedEvent; +import org.apache.zookeeper.Watcher; +import org.apache.zookeeper.data.Stat; +import org.apache.zookeeper.server.jersey.jaxb.ZPath; +import org.junit.Test; +import org.junit.Assert; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +import com.sun.jersey.api.client.ClientResponse; +import com.sun.jersey.api.client.WebResource; +import com.sun.jersey.api.client.WebResource.Builder; + + +/** + * Test stand-alone server. + * + */ +@RunWith(Parameterized.class) +public class CreateTest extends Base { + protected static final Logger LOG = LoggerFactory.getLogger(CreateTest.class); + + private String accept; + private String path; + private String name; + private String encoding; + private ClientResponse.Status expectedStatus; + private ZPath expectedPath; + private byte[] data; + private boolean sequence; + + public static class MyWatcher implements Watcher { + public void process(WatchedEvent event) { + // FIXME ignore for now + } + } + + @Parameters + public static Collection<Object[]> data() throws Exception { + String baseZnode = Base.createBaseZNode(); + + return Arrays.asList(new Object[][] { + {MediaType.APPLICATION_JSON, + baseZnode, "foo bar", "utf8", + ClientResponse.Status.CREATED, + new ZPath(baseZnode + "/foo bar"), null, + false }, + {MediaType.APPLICATION_JSON, baseZnode, "c-t1", "utf8", + ClientResponse.Status.CREATED, new ZPath(baseZnode + "/c-t1"), + null, false }, + {MediaType.APPLICATION_JSON, baseZnode, "c-t1", "utf8", + ClientResponse.Status.CONFLICT, null, null, false }, + {MediaType.APPLICATION_JSON, baseZnode, "c-t2", "utf8", + ClientResponse.Status.CREATED, new ZPath(baseZnode + "/c-t2"), + "".getBytes(), false }, + {MediaType.APPLICATION_JSON, baseZnode, "c-t2", "utf8", + ClientResponse.Status.CONFLICT, null, null, false }, + {MediaType.APPLICATION_JSON, baseZnode, "c-t3", "utf8", + ClientResponse.Status.CREATED, new ZPath(baseZnode + "/c-t3"), + "foo".getBytes(), false }, + {MediaType.APPLICATION_JSON, baseZnode, "c-t3", "utf8", + ClientResponse.Status.CONFLICT, null, null, false }, + {MediaType.APPLICATION_JSON, baseZnode, "c-t4", "base64", + ClientResponse.Status.CREATED, new ZPath(baseZnode + "/c-t4"), + "foo".getBytes(), false }, + {MediaType.APPLICATION_JSON, baseZnode, "c-", "utf8", + ClientResponse.Status.CREATED, new ZPath(baseZnode + "/c-"), null, + true }, + {MediaType.APPLICATION_JSON, baseZnode, "c-", "utf8", + ClientResponse.Status.CREATED, new ZPath(baseZnode + "/c-"), null, + true } + }); + } + + public CreateTest(String accept, String path, String name, String encoding, + ClientResponse.Status status, ZPath expectedPath, byte[] data, + boolean sequence) + { + this.accept = accept; + this.path = path; + this.name = name; + this.encoding = encoding; + this.expectedStatus = status; + this.expectedPath = expectedPath; + this.data = data; + this.sequence = sequence; + } + + @Test + public void testCreate() throws Exception { + WebResource wr = znodesr.path(path).queryParam("dataformat", encoding) + .queryParam("name", name); + if (data == null) { + wr = wr.queryParam("null", "true"); + } + if (sequence) { + wr = wr.queryParam("sequence", "true"); + } + + Builder builder = wr.accept(accept); + + ClientResponse cr; + if (data == null) { + cr = builder.post(ClientResponse.class); + } else { + cr = builder.post(ClientResponse.class, data); + } + Assert.assertEquals(expectedStatus, cr.getClientResponseStatus()); + + if (expectedPath == null) { + return; + } + + ZPath zpath = cr.getEntity(ZPath.class); + if (sequence) { + Assert.assertTrue(zpath.path.startsWith(expectedPath.path)); + Assert.assertTrue(zpath.uri.startsWith(znodesr.path(path).toString())); + } else { + Assert.assertEquals(expectedPath, zpath); + Assert.assertEquals(znodesr.path(path).toString(), zpath.uri); + } + + // use out-of-band method to verify + byte[] data = zk.getData(zpath.path, false, new Stat()); + if (data == null && this.data == null) { + return; + } else if (data == null || this.data == null) { + Assert.assertEquals(data, this.data); + } else { + Assert.assertTrue(new String(data) + " == " + new String(this.data), + Arrays.equals(data, this.data)); + } + } +} http://git-wip-us.apache.org/repos/asf/zookeeper/blob/eab8c1eb/zookeeper-contrib/zookeeper-contrib-rest/src/test/java/org/apache/zookeeper/server/jersey/DeleteTest.java ---------------------------------------------------------------------- diff --git a/zookeeper-contrib/zookeeper-contrib-rest/src/test/java/org/apache/zookeeper/server/jersey/DeleteTest.java b/zookeeper-contrib/zookeeper-contrib-rest/src/test/java/org/apache/zookeeper/server/jersey/DeleteTest.java new file mode 100644 index 0000000..495f93b --- /dev/null +++ b/zookeeper-contrib/zookeeper-contrib-rest/src/test/java/org/apache/zookeeper/server/jersey/DeleteTest.java @@ -0,0 +1,95 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.zookeeper.server.jersey; + +import java.util.Arrays; +import java.util.Collection; + +import javax.ws.rs.core.MediaType; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.apache.zookeeper.CreateMode; +import org.apache.zookeeper.WatchedEvent; +import org.apache.zookeeper.Watcher; +import org.apache.zookeeper.ZooDefs.Ids; +import org.apache.zookeeper.data.Stat; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +import com.sun.jersey.api.client.ClientResponse; + + +/** + * Test stand-alone server. + * + */ +@RunWith(Parameterized.class) +public class DeleteTest extends Base { + protected static final Logger LOG = LoggerFactory.getLogger(DeleteTest.class); + + private String zpath; + private ClientResponse.Status expectedStatus; + + public static class MyWatcher implements Watcher { + public void process(WatchedEvent event) { + // FIXME ignore for now + } + } + + @Parameters + public static Collection<Object[]> data() throws Exception { + String baseZnode = Base.createBaseZNode(); + + return Arrays.asList(new Object[][] { + {baseZnode, baseZnode, ClientResponse.Status.NO_CONTENT }, + {baseZnode, baseZnode, ClientResponse.Status.NO_CONTENT } + }); + } + + public DeleteTest(String path, String zpath, ClientResponse.Status status) { + this.zpath = zpath; + this.expectedStatus = status; + } + + public void verify(String type) throws Exception { + if (expectedStatus != ClientResponse.Status.NOT_FOUND) { + zpath = zk.create(zpath, null, Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT_SEQUENTIAL); + } + + ClientResponse cr = znodesr.path(zpath).accept(type).type(type) + .delete(ClientResponse.class); + Assert.assertEquals(expectedStatus, cr.getClientResponseStatus()); + + // use out-of-band method to verify + Stat stat = zk.exists(zpath, false); + Assert.assertNull(stat); + } + + @Test + public void testDelete() throws Exception { + verify(MediaType.APPLICATION_OCTET_STREAM); + verify(MediaType.APPLICATION_JSON); + verify(MediaType.APPLICATION_XML); + } +} http://git-wip-us.apache.org/repos/asf/zookeeper/blob/eab8c1eb/zookeeper-contrib/zookeeper-contrib-rest/src/test/java/org/apache/zookeeper/server/jersey/ExistsTest.java ---------------------------------------------------------------------- diff --git a/zookeeper-contrib/zookeeper-contrib-rest/src/test/java/org/apache/zookeeper/server/jersey/ExistsTest.java b/zookeeper-contrib/zookeeper-contrib-rest/src/test/java/org/apache/zookeeper/server/jersey/ExistsTest.java new file mode 100644 index 0000000..68b40f0 --- /dev/null +++ b/zookeeper-contrib/zookeeper-contrib-rest/src/test/java/org/apache/zookeeper/server/jersey/ExistsTest.java @@ -0,0 +1,80 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.zookeeper.server.jersey; + +import java.util.Arrays; +import java.util.Collection; + +import javax.ws.rs.core.MediaType; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +import com.sun.jersey.api.client.ClientResponse; + + +/** + * Test stand-alone server. + * + */ +@RunWith(Parameterized.class) +public class ExistsTest extends Base { + protected static final Logger LOG = LoggerFactory.getLogger(ExistsTest.class); + + private String path; + private ClientResponse.Status expectedStatus; + + @Parameters + public static Collection<Object[]> data() throws Exception { + String baseZnode = Base.createBaseZNode(); + + return Arrays.asList(new Object[][] { + {baseZnode, ClientResponse.Status.OK }, + {baseZnode + "dkdk38383", ClientResponse.Status.NOT_FOUND } + }); + } + + public ExistsTest(String path, ClientResponse.Status status) { + this.path = path; + this.expectedStatus = status; + } + + private void verify(String type) { + ClientResponse cr = znodesr.path(path).accept(type).type(type).head(); + if (type.equals(MediaType.APPLICATION_OCTET_STREAM) + && expectedStatus == ClientResponse.Status.OK) { + Assert.assertEquals(ClientResponse.Status.NO_CONTENT, + cr.getClientResponseStatus()); + } else { + Assert.assertEquals(expectedStatus, cr.getClientResponseStatus()); + } + } + + @Test + public void testExists() throws Exception { + verify(MediaType.APPLICATION_OCTET_STREAM); + verify(MediaType.APPLICATION_JSON); + verify(MediaType.APPLICATION_XML); + } +} http://git-wip-us.apache.org/repos/asf/zookeeper/blob/eab8c1eb/zookeeper-contrib/zookeeper-contrib-rest/src/test/java/org/apache/zookeeper/server/jersey/GetChildrenTest.java ---------------------------------------------------------------------- diff --git a/zookeeper-contrib/zookeeper-contrib-rest/src/test/java/org/apache/zookeeper/server/jersey/GetChildrenTest.java b/zookeeper-contrib/zookeeper-contrib-rest/src/test/java/org/apache/zookeeper/server/jersey/GetChildrenTest.java new file mode 100644 index 0000000..8f7fc98 --- /dev/null +++ b/zookeeper-contrib/zookeeper-contrib-rest/src/test/java/org/apache/zookeeper/server/jersey/GetChildrenTest.java @@ -0,0 +1,138 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.zookeeper.server.jersey; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import javax.ws.rs.core.MediaType; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.apache.zookeeper.CreateMode; +import org.apache.zookeeper.ZooDefs.Ids; +import org.apache.zookeeper.server.jersey.jaxb.ZChildren; +import org.apache.zookeeper.server.jersey.jaxb.ZChildrenJSON; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +import com.sun.jersey.api.client.ClientResponse; + + +/** + * Test stand-alone server. + * + */ +@RunWith(Parameterized.class) +public class GetChildrenTest extends Base { + protected static final Logger LOG = LoggerFactory.getLogger(GetChildrenTest.class); + + private String accept; + private String path; + private ClientResponse.Status expectedStatus; + private String expectedPath; + private List<String> expectedChildren; + + @Parameters + public static Collection<Object[]> data() throws Exception { + String baseZnode = Base.createBaseZNode(); + String baseZnode2 = Base.createBaseZNode(); + String baseZnode3 = Base.createBaseZNode(); + String baseZnode4 = Base.createBaseZNode(); + String baseZnode5 = Base.createBaseZNode(); + String baseZnode6 = Base.createBaseZNode(); + + return Arrays.asList(new Object[][] { + {MediaType.APPLICATION_JSON, baseZnode + "abddkdkd", + ClientResponse.Status.NOT_FOUND, null, null }, + {MediaType.APPLICATION_XML, baseZnode + "abddkdkd", + ClientResponse.Status.NOT_FOUND, null, null }, + {MediaType.APPLICATION_JSON, baseZnode, ClientResponse.Status.OK, + baseZnode, Arrays.asList(new String[] {}) }, + {MediaType.APPLICATION_XML, baseZnode, ClientResponse.Status.OK, + baseZnode, Arrays.asList(new String[] {}) }, + {MediaType.APPLICATION_JSON, baseZnode, ClientResponse.Status.OK, + baseZnode, Arrays.asList(new String[] {"c1"}) }, + {MediaType.APPLICATION_XML, baseZnode4, ClientResponse.Status.OK, + baseZnode4, Arrays.asList(new String[] {"c1"}) }, + {MediaType.APPLICATION_JSON, baseZnode2, ClientResponse.Status.OK, + baseZnode2, Arrays.asList(new String[] {"c1", "c2"}) }, + {MediaType.APPLICATION_XML, baseZnode5, ClientResponse.Status.OK, + baseZnode5, Arrays.asList(new String[] {"c1", "c2"}) }, + {MediaType.APPLICATION_JSON, baseZnode3, ClientResponse.Status.OK, + baseZnode3, Arrays.asList(new String[] {"c1", "c2", "c3", "c4"}) }, + {MediaType.APPLICATION_XML, baseZnode6, ClientResponse.Status.OK, + baseZnode6, Arrays.asList(new String[] {"c1", "c2", "c3", "c4"}) } + + }); + } + + public GetChildrenTest(String accept, String path, ClientResponse.Status status, + String expectedPath, List<String> expectedChildren) + { + this.accept = accept; + this.path = path; + this.expectedStatus = status; + this.expectedPath = expectedPath; + this.expectedChildren = expectedChildren; + } + + @Test + public void testGetChildren() throws Exception { + if (expectedChildren != null) { + for(String child : expectedChildren) { + zk.create(expectedPath + "/" + child, null, + Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); + } + } + + ClientResponse cr = znodesr.path(path).queryParam("view", "children") + .accept(accept).get(ClientResponse.class); + Assert.assertEquals(expectedStatus, cr.getClientResponseStatus()); + + if (expectedChildren == null) { + return; + } + + if (accept.equals(MediaType.APPLICATION_JSON)) { + ZChildrenJSON zchildren = cr.getEntity(ZChildrenJSON.class); + Collections.sort(expectedChildren); + Collections.sort(zchildren.children); + Assert.assertEquals(expectedChildren, zchildren.children); + Assert.assertEquals(znodesr.path(path).toString(), zchildren.uri); + Assert.assertEquals(znodesr.path(path).toString() + "/{child}", + zchildren.child_uri_template); + } else if (accept.equals(MediaType.APPLICATION_XML)) { + ZChildren zchildren = cr.getEntity(ZChildren.class); + Collections.sort(expectedChildren); + Collections.sort(zchildren.children); + Assert.assertEquals(expectedChildren, zchildren.children); + Assert.assertEquals(znodesr.path(path).toString(), zchildren.uri); + Assert.assertEquals(znodesr.path(path).toString() + "/{child}", + zchildren.child_uri_template); + } else { + Assert.fail("unknown accept type"); + } + } +} http://git-wip-us.apache.org/repos/asf/zookeeper/blob/eab8c1eb/zookeeper-contrib/zookeeper-contrib-rest/src/test/java/org/apache/zookeeper/server/jersey/GetTest.java ---------------------------------------------------------------------- diff --git a/zookeeper-contrib/zookeeper-contrib-rest/src/test/java/org/apache/zookeeper/server/jersey/GetTest.java b/zookeeper-contrib/zookeeper-contrib-rest/src/test/java/org/apache/zookeeper/server/jersey/GetTest.java new file mode 100644 index 0000000..8ee1dc6 --- /dev/null +++ b/zookeeper-contrib/zookeeper-contrib-rest/src/test/java/org/apache/zookeeper/server/jersey/GetTest.java @@ -0,0 +1,122 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.zookeeper.server.jersey; + +import java.util.Arrays; +import java.util.Collection; + +import javax.ws.rs.core.MediaType; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.apache.zookeeper.server.jersey.jaxb.ZStat; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +import com.sun.jersey.api.client.ClientResponse; + + +/** + * Test stand-alone server. + * + */ +@RunWith(Parameterized.class) +public class GetTest extends Base { + protected static final Logger LOG = LoggerFactory.getLogger(GetTest.class); + + private String accept; + private String path; + private String encoding; + private ClientResponse.Status expectedStatus; + private ZStat expectedStat; + + @Parameters + public static Collection<Object[]> data() throws Exception { + String baseZnode = Base.createBaseZNode(); + + return Arrays.asList(new Object[][] { + {MediaType.APPLICATION_JSON, baseZnode, "utf8", + ClientResponse.Status.OK, new ZStat(baseZnode, null, null) }, + {MediaType.APPLICATION_JSON, baseZnode, "utf8", + ClientResponse.Status.OK, new ZStat(baseZnode, null, "") }, + {MediaType.APPLICATION_JSON, baseZnode, "utf8", + ClientResponse.Status.OK, new ZStat(baseZnode, null, "foo") }, + {MediaType.APPLICATION_JSON, baseZnode, "base64", + ClientResponse.Status.OK, new ZStat(baseZnode, null, null) }, + {MediaType.APPLICATION_JSON, baseZnode, "base64", + ClientResponse.Status.OK, new ZStat(baseZnode, "".getBytes(), null) }, + {MediaType.APPLICATION_JSON, baseZnode, "base64", + ClientResponse.Status.OK, new ZStat(baseZnode, "".getBytes(), null) }, + {MediaType.APPLICATION_JSON, baseZnode, "base64", + ClientResponse.Status.OK, new ZStat(baseZnode, "foo".getBytes(), null) }, + {MediaType.APPLICATION_JSON, baseZnode + "abaddkdk", "utf8", + ClientResponse.Status.NOT_FOUND, null }, + {MediaType.APPLICATION_JSON, baseZnode + "abaddkdk", "base64", + ClientResponse.Status.NOT_FOUND, null }, + + {MediaType.APPLICATION_XML, baseZnode, "utf8", + ClientResponse.Status.OK, new ZStat(baseZnode, null, "foo") }, + {MediaType.APPLICATION_XML, baseZnode, "base64", + ClientResponse.Status.OK, + new ZStat(baseZnode, "foo".getBytes(), null) }, + {MediaType.APPLICATION_XML, baseZnode + "abaddkdk", "utf8", + ClientResponse.Status.NOT_FOUND, null }, + {MediaType.APPLICATION_XML, baseZnode + "abaddkdk", "base64", + ClientResponse.Status.NOT_FOUND, null } + + }); + } + + public GetTest(String accept, String path, String encoding, + ClientResponse.Status status, ZStat stat) + { + this.accept = accept; + this.path = path; + this.encoding = encoding; + this.expectedStatus = status; + this.expectedStat = stat; + } + + @Test + public void testGet() throws Exception { + if (expectedStat != null) { + if (expectedStat.data64 != null || expectedStat.dataUtf8 == null) { + zk.setData(expectedStat.path, expectedStat.data64, -1); + } else { + zk.setData(expectedStat.path, + expectedStat.dataUtf8.getBytes(), -1); + } + } + + ClientResponse cr = znodesr.path(path).queryParam("dataformat", encoding) + .accept(accept).get(ClientResponse.class); + Assert.assertEquals(expectedStatus, cr.getClientResponseStatus()); + + if (expectedStat == null) { + return; + } + + ZStat zstat = cr.getEntity(ZStat.class); + Assert.assertEquals(expectedStat, zstat); + Assert.assertEquals(znodesr.path(path).toString(), zstat.uri); + } +} http://git-wip-us.apache.org/repos/asf/zookeeper/blob/eab8c1eb/zookeeper-contrib/zookeeper-contrib-rest/src/test/java/org/apache/zookeeper/server/jersey/RestTestSuite.java ---------------------------------------------------------------------- diff --git a/zookeeper-contrib/zookeeper-contrib-rest/src/test/java/org/apache/zookeeper/server/jersey/RestTestSuite.java b/zookeeper-contrib/zookeeper-contrib-rest/src/test/java/org/apache/zookeeper/server/jersey/RestTestSuite.java new file mode 100644 index 0000000..fc69caf --- /dev/null +++ b/zookeeper-contrib/zookeeper-contrib-rest/src/test/java/org/apache/zookeeper/server/jersey/RestTestSuite.java @@ -0,0 +1,42 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.zookeeper.server.jersey; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; +import org.junit.runners.Suite.SuiteClasses; + +@RunWith(Suite.class) +@SuiteClasses({WadlTest.class, GetTest.class, GetChildrenTest.class, + CreateTest.class, SetTest.class, ExistsTest.class, DeleteTest.class }) +public class RestTestSuite { + + @BeforeClass + public static void setUp() { + // suite setup + } + + @AfterClass + public static void tearDown() { + // suite setup + } + +} http://git-wip-us.apache.org/repos/asf/zookeeper/blob/eab8c1eb/zookeeper-contrib/zookeeper-contrib-rest/src/test/java/org/apache/zookeeper/server/jersey/RootTest.java ---------------------------------------------------------------------- diff --git a/zookeeper-contrib/zookeeper-contrib-rest/src/test/java/org/apache/zookeeper/server/jersey/RootTest.java b/zookeeper-contrib/zookeeper-contrib-rest/src/test/java/org/apache/zookeeper/server/jersey/RootTest.java new file mode 100644 index 0000000..af8f9cf --- /dev/null +++ b/zookeeper-contrib/zookeeper-contrib-rest/src/test/java/org/apache/zookeeper/server/jersey/RootTest.java @@ -0,0 +1,66 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.zookeeper.server.jersey; + +import java.util.Arrays; + +import javax.ws.rs.core.MediaType; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.apache.zookeeper.data.Stat; +import org.apache.zookeeper.server.jersey.jaxb.ZPath; +import org.junit.Assert; +import org.junit.Test; + +import com.sun.jersey.api.client.ClientResponse; +import com.sun.jersey.api.client.WebResource; +import com.sun.jersey.api.client.WebResource.Builder; + +/** + * Test stand-alone server. + * + */ +public class RootTest extends Base { + protected static final Logger LOG = LoggerFactory.getLogger(RootTest.class); + + @Test + public void testCreate() throws Exception { + String path = "/"; + String name = "roottest-create"; + byte[] data = "foo".getBytes(); + + WebResource wr = znodesr.path(path).queryParam("dataformat", "utf8") + .queryParam("name", name); + Builder builder = wr.accept(MediaType.APPLICATION_JSON); + + ClientResponse cr; + cr = builder.post(ClientResponse.class, data); + Assert.assertEquals(ClientResponse.Status.CREATED, cr.getClientResponseStatus()); + + ZPath zpath = cr.getEntity(ZPath.class); + Assert.assertEquals(new ZPath(path + name), zpath); + Assert.assertEquals(znodesr.path(path).toString(), zpath.uri); + + // use out-of-band method to verify + byte[] rdata = zk.getData(zpath.path, false, new Stat()); + Assert.assertTrue(new String(rdata) + " == " + new String(data), + Arrays.equals(rdata, data)); + } +}
