http://git-wip-us.apache.org/repos/asf/zookeeper/blob/b0df8fe1/zookeeper-contrib/zookeeper-contrib-rest/SPEC.txt ---------------------------------------------------------------------- diff --git a/zookeeper-contrib/zookeeper-contrib-rest/SPEC.txt b/zookeeper-contrib/zookeeper-contrib-rest/SPEC.txt new file mode 100644 index 0000000..8c5f701 --- /dev/null +++ b/zookeeper-contrib/zookeeper-contrib-rest/SPEC.txt @@ -0,0 +1,355 @@ +# 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. + +A REST HTTP gateway for ZooKeeper +================================= + +Specification Version: 2 + +ZooKeeper is meant to enable distributed coordination and also store +system configuration and other relatively small amounts of information +that must be stored in a persistent and consistent manner. The +information stored in ZooKeeper is meant to be highly available to a +large number of nodes in a distributed-computing cluster. + +ZooKeeper offers a client-side library that supports rich semantics +that include strict ordering guarantees on operations, the creation of +ephemeral znodes, and the ability to watch for changes to state. +However, where clients need simple "CRUD" (create, read, update, +delete) operations, the ZooKeeper libraries can be cumbersome, both to +the programmers who have to use them (who are increasingly used to +REST-style APIs), and to the operators who have to deploy and update +them (for whom deploying and updating client libraries can be very +painful). + +It turns out that most languages comes with client libraries for HTTP +that are easy and familiar to program against, and deployed as part of +the language runtime. Thus, for simple CRUD clients, an HTTP gateway +would be a less cumbersome interface than the ZooKeeper library. + +This document describes a gatway for using HTTP to interact with a +ZooKeeper repository. + +Binding ZooKeeper to HTTP +------------------------- + +Encoding +-------- + +UTF-8 unless otherwise noted + +Paths +----- + +A ZooKeeper paths are mapped to IRIs and URIs as follows. ZK paths +are converted to IRIs by simply percent-encoding any characters in the +ZK path that are not allowed in IRI paths. ZK paths are converted to +URIs by mapping them first to IRIs, then converting to URIs in the +standard way. + +Going from URIs and IRIs is the reverse of the above but for one +difference: any "." and ".." segments in an IRI or URI must be folded +before conversion. (Fortunately, ZK does not allow "." and ".." +segments in its paths.) + +ZK and IRIs recommend the same practices when it comes to Unicode +normalization: ultimately, normalization is left to application +designers, but both recommend that application designers use NFC as a +best practice. + +Root +---- + +The following examples assume that the ZooKeeper znode heirarchy is +bound to the root of the HTTP servers namespace. This may not be the +case in practice however, the gateway may bind to some prefix, for +example the URL for accessing /a/b/c may be: + + http://localhost/zookeeper/znodes/v1/a/b/c + +This is perfectly valid. Users of the REST service should be aware of +this fact and code their clients to support any root (in this case +"/zookeeper" on the server localhost). + + +Basics: GET, PUT, HEAD, and DELETE +---------------------------------- + +HTTP's GET, PUT, HEAD, and DELETE operations map naturally to +ZooKeeper's "get," "set," "exists," and "delete" operations. + +ZooKeeper znodes have a version number that changes each time the +znode's value is updated. This number is returned by "get," "set," and +"exists" operations. The "set" and "delete" operations optionally take +a version number. If one is supplied, then "set" or "delete" will fail +if the current version of the znode doesn't match the version-number +supplied in the call. This mechanism supports atomic read-modify-write +cycles. Set/delete requests may include an optional parameter +"version" which defaults to no version check. + + +Getting ZooKeeper children +-------------------------- + +We overload the GET method to return the children of a ZooKeeper. In +particular, the GET method takes an optional parameter "view" which +could be set to one of type values, either "data" or "children". The +default is "data". Thus, to get the children of a znode named +"/a/b/c", then the GET request should start: + + GET /znodes/v1/a/b/c?view=children HTTP/1.1 + +If the requested view is "data", then the data of a znode is returned +as described in the previous section. If the requested view is +"children", then a list of children is returned in either an XML +document, or in a JSON object. (The default is JSON, but this can be +controlled changed by setting the Accept header.) + + +Creating a ZooKeeper session +---------------------------- + +In order to be able to create ephemeral nodes you first need to start +a new session. + + POST /sessions/v1?op=create&expire=<SECONDS> HTTP/1.1 + +If the session creation is successful, then a 201 code will be returned. + +A session is just an UUID that you can pass around as a parameter and +the REST server will foward your request on the attached persistent +connection. + +Keeping a session alive +----------------------- + +To keep a session alive you must send hearbeat requests: + + PUT /sessions/v1/<SESSION-UUID> HTTP/1.1 + +Closing a ZooKeeper session +--------------------------- + +You can close a connection by sending a DELETE request. + + DELETE /sessions/v1/<SESSION-UUID> HTTP/1.1 + +If you don't close a session it will automatically expire after +the amount of time you specified on creation. + +Creating a ZooKeeper znode +-------------------------- + +We use the POST method to create a ZooKeeper znode. For example, to +create a znode named "c" under a parent named "/a/b", then the POST +request should start: + + POST /znodes/v1/a/b?op=create&name=c HTTP/1.1 + +If the creation is successful, then a 201 code will be returned. If +it fails, then a number of different codes might be returned +(documented in a later subsection). + +ZooKeeper's create operation has a flag that tells the server to +append a sequence-number to the client-supplied znode-name in order to +make the znode-name unique. If you set this flag and ask to create a +znode named "/a/b/c", and a znode named "/a/b" already exists, then +"create" will create a znode named "/a/b/c-#" instead, where "#" is and +integer required to generate a unique name in for format %10d. + +To obtain this behavior, an additional "sequence=true" parameter +should be added to the parameters of the POST. (Note that "sequence" +is an optional parameter, that defaults to "false"; this default may +be provided explicitly if desired.) + +On success the actual path of the created znode will be returned. + +If you want to create an ephemeral node you need to specify an +additional "ephemeral=true" parameter. (Note that "ephemeral" is an optional +parameter, that defaults to "false") + +(Note: ZooKeeper also allows the client to set ACLs for the +newly-created znode. This feature is not currently supported by the +HTTP gateway to ZooKeeper.) + + +Content types and negotiation +----------------------------- + +ZooKeeper REST gateway implementations may support three content-types +for request and response messages: + +* application/octet-stream + + HEAD - returns nothing (note below: status = 204) + GET - returns the znode data as an octet-stream + PUT - send binary data, returns nothing + POST - send binary data, returns the name of the znode + DELETE - returns nothing + + For PUT and HEAD some other content-type (i.e. JSON or XML) must be + used to access the Stat information of a znode. + +* application/json, application/javascript & application/xml + + HEAD - returns nothing + GET - returns a STAT or CHILD structure + PUT - send binary data, returns a STAT structure (sans data field) + POST - send binary data, returns a PATH structure + DELETE - returns nothing + + (structures defined below) + + Results returning DATA may include an optional "dataformat" + parameter which has two possible values; base64 (default) or + utf8. This allows the caller to control the format of returned data + and may simplify usage -- for example cat'ing results to the command + line with something like curl, or accessing a url through a browser. + Care should be exercised however, if utf8 is used on non character + data errors may result. + + "application/javascript" requests may include an optional "callback" + parameter. The response is wrapped in a callback method of your + choice. e.g. appending &callback=foo to your request will result in + a response body of: foo(...). Callbacks may only contain + alphanumeric characters and underscores. + +PATH + path : string + uri: string + + path is the full path to the znode as seen by ZooKeeper + + uri is the full URI of the znode as seen by the REST server, does not + include any query parameters (i.e. it's the path to the REST resource) + +SESSION + id : string UUID + uri : string + +CHILD + PATH + child_uri_template: string + children : [ string* ] + + The children list of strings contains only the name of the child + znodes, not the full path. + + child_uri_template is a template for URI of child znodes as seen by the + REST server. e.g. "http://localhost:9998/znodes/v1/foo/{child}", where + foo is the parent node, and {child} can be substituted with the name + of each child in the children array in order to access that resource. + This template is provided to simplify child access. + +STAT + PATH + encoding : value of "base64" or "utf8" + data : base64 or utf8 encoded string + stat : + czxid : number + mzxid : number + ctime : number + mtime : number + version : number + cversion : number + aversion : number + ephemeralOwner : number + datalength : number + numChildren : number + pzxid : number + + +Error Codes +----------- + +The ZooKeeper gateway uses HTTP response codes as follows: + + * 200 (Success) - ZOK for "get" "set" "delete", "yes" case of "exists" (json/xml) + * 201 (Created) - ZOK for "create" + * 204 (No Content) - ZOK for "yes" case of "exists" (octet) + * 400 (Bad Request) - ZINVALIDACL, ZBADARGUMENTS, version param not a number + * 401 (Unauthorized) - ZAUTHFAILED + * 404 (Not Found) - ZOK for "no" case of "exists;" ZNONODE for "get," "set," and "delete" + * 409 (Conflict) - ZNODEEXISTS, ZNONODE for "create," ZNOTEMPTY, + * 412 (Precondition Failed) - ZBADVERSION + * 415 (Unsupported Media Type) - if content-type of PUT or POST is not "application/octet-stream" + * 500 (Internal Server Error) - Failure in gateway code + * 501 (Not Implemented) - HTTP method other than GET, PUT, HEAD, DELETE + * 502 (Bad Gateway) - All other ZooKeeper error codes + * 503 (Service Unavailable) - ZSESSIONEXPIRED, ZCONNECTIONLOSS, (gateway will try to reestablish the connection, but will not hold the request waiting...) + * 504 (Gateway Timeout) - ZOPERATIONTIMEOUT, or ZooKeeper does not return in a timely manner + +Note that these are the codes used by the HTTP-to-Gateway software +itself. Depending on how this software is configured into a Web +server, the resulting Web Server might behave differently, e.g., it +might do redirection, check other headers, etc. + +Error Messages +-------------- + +Error messages are returned to the caller, format is dependent on the +format requested in the call. + +* application/octet-stream + + A string containing the error message. It should include the request + and information detailing the reason for the error. + +* application/json + + { "request":"GET /a/b/c", "message":"Node doesn't exist" } + +* application/xml + +<?xml version="1.0" encoding="UTF-8"?> +<error> + <request>GET /a/b/c</request> + <message>Node doesn't exist</message> +</error> + + +Binding ZooKeeper to an HTTP server +----------------------------------- + +It might be sage to assume that everyone is happy to run an Apache +server, and thus write a "mod_zookeeper" for Apache that works only +for the Apache Web Server. However, different operational +environments prefer different Web Servers, and it would be nice to +support more than one Web server. + +Issues: + + * Configuration. + + * Defining a root: Need to provide a URL alias and associate it + with a server. Need to be able to map different aliases to + different servers (implemented via multiple ZK connections). + + * Sharing connection across multiple processes. + + * Asynchronous. + + * Adaptors. + + * Code re-use. + + +Authentication -- TBD, not currently supported + +...the config file should contain authentication material for the gateway + +...the config file should contain an ACL list to be passed along to "create" + +...would we ever want to authenticate each request to ZooKeeper?...
http://git-wip-us.apache.org/repos/asf/zookeeper/blob/b0df8fe1/zookeeper-contrib/zookeeper-contrib-rest/build.xml ---------------------------------------------------------------------- diff --git a/zookeeper-contrib/zookeeper-contrib-rest/build.xml b/zookeeper-contrib/zookeeper-contrib-rest/build.xml new file mode 100644 index 0000000..5097182 --- /dev/null +++ b/zookeeper-contrib/zookeeper-contrib-rest/build.xml @@ -0,0 +1,189 @@ +<?xml version="1.0"?> + +<!-- + 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. +--> + +<project name="rest" default="jar"> + <import file="../build-contrib.xml"/> + + <property name="test.build.dir" value="${build.test}" /> + <property name="test.src.dir" value="src/test/java"/> + <property name="test.log.dir" value="${test.build.dir}/logs" /> + <property name="test.data.dir" value="${test.build.dir}/data" /> + <property name="test.data.upgrade.dir" value="${test.data.dir}/upgrade" /> + <property name="test.tmp.dir" value="${test.build.dir}/tmp" /> + <property name="test.output" value="no" /> + <property name="test.timeout" value="900000" /> + <property name="test.junit.output.format" value="plain" /> + <property name="test.junit.fork.mode" value="perTest" /> + <property name="test.junit.printsummary" value="yes" /> + <property name="test.junit.haltonfailure" value="no" /> + <property name="test.junit.maxmem" value="512m" /> + + <!-- ====================================================== --> + <!-- Macro definitions --> + <!-- ====================================================== --> + <macrodef name="macro_tar" description="Worker Macro for tar"> + <attribute name="param.destfile"/> + <element name="param.listofitems"/> + <sequential> + <tar compression="gzip" longfile="gnu" + destfile="@{param.destfile}"> + <param.listofitems/> + </tar> + </sequential> + </macrodef> + + <target name="package" depends="jar" unless="skip.contrib"> + <echo message="contrib: ${name}"/> + <mkdir dir="${dist.dir}${package.share}/contrib/${name}"/> + <copy todir="${dist.dir}${package.share}/contrib/${name}"> + <fileset dir="${build.dir}"> + <include name="zookeeper-${version}-${name}.jar" /> + </fileset> + </copy> + <copy todir="${dist.dir}${package.share}/contrib/${name}/lib"> + <fileset dir="${build.dir}/lib" /> + </copy> + <copy todir="${dist.dir}${package.share}/contrib/${name}/conf"> + <fileset dir="conf" /> + </copy> + </target> + + <target name="setjarname"> + <property name="jarname" + value="${build.dir}/zookeeper-${version}-${name}.jar"/> + </target> + + <target name="compile" depends="ivy-retrieve,zookeeperbuildcontrib.compile"/> + + <target name="test" + depends="compile-test,test-init,test-category,test-start,junit.run,test-stop" /> + + <target name="compile-test" depends="ivy-retrieve-test,compile"> + <property name="target.jdk" value="${ant.java.version}" /> + <property name="src.test.local" location="${basedir}/src/test" /> + <mkdir dir="${build.test}"/> + <javac srcdir="${src.test.local}" + destdir="${build.test}" + target="${target.jdk}" + debug="on" encoding="${build.encoding}"> + <classpath refid="classpath" /> + <classpath> + <pathelement location="${zk.root}/build/test/classes"/> + </classpath> + </javac> + </target> + + <target name="test-init" depends="jar,compile-test"> + <delete dir="${test.log.dir}" /> + <delete dir="${test.tmp.dir}" /> + <delete dir="${test.data.dir}" /> + <mkdir dir="${test.log.dir}" /> + <mkdir dir="${test.tmp.dir}" /> + <mkdir dir="${test.data.dir}" /> + </target> + + <target name="test-start"> + <exec executable="${test.src.dir}/zkServer.sh"> + <arg value="startClean"/> + </exec> + </target> + + <target name="test-stop"> + <exec executable="${test.src.dir}/zkServer.sh"> + <arg value="stop"/> + </exec> + </target> + + <target name="test-category"> + <property name="test.category" value=""/> + </target> + + <target name="junit.run"> + <echo message="${test.src.dir}" /> + <junit showoutput="${test.output}" + printsummary="${test.junit.printsummary}" + haltonfailure="${test.junit.haltonfailure}" + fork="yes" + forkmode="${test.junit.fork.mode}" + maxmemory="${test.junit.maxmem}" + dir="${basedir}" timeout="${test.timeout}" + errorProperty="tests.failed" failureProperty="tests.failed"> + <sysproperty key="build.test.dir" value="${test.tmp.dir}" /> + <sysproperty key="test.data.dir" value="${test.data.dir}" /> + <sysproperty key="log4j.configuration" + value="file:${basedir}/conf/log4j.properties" /> + <classpath refid="classpath"/> + <classpath> + <pathelement path="${build.test}" /> + </classpath> + <classpath> + <pathelement location="${zk.root}/build/test/classes"/> + </classpath> + <formatter type="${test.junit.output.format}" /> + <batchtest todir="${test.log.dir}" unless="testcase"> + <fileset dir="${test.src.dir}" + includes="**/*${test.category}Test.java"/> + </batchtest> + <batchtest todir="${test.log.dir}" if="testcase"> + <fileset dir="${test.src.dir}" includes="**/${testcase}.java"/> + </batchtest> + </junit> + <fail if="tests.failed">Tests failed!</fail> + </target> + + <target name="jar" depends="checkMainCompiled, setjarname, compile"> + <echo message="contrib: ${name}"/> + <jar jarfile="${jarname}"> + <fileset file="${zk.root}/LICENSE.txt" /> + <fileset dir="${build.classes}"/> + <fileset dir="${build.test}"/> + </jar> + </target> + + <target name="run" depends="jar"> + <echo message="contrib: ${name}"/> + <java classname="org.apache.zookeeper.server.jersey.RestMain" fork="true"> + <classpath> + <pathelement path="${jarname}" /> + <fileset dir="${build.dir}/lib" includes="*.jar"/> + <fileset dir="${zk.root}/build" includes="zookeeper-*.jar"/> + <pathelement path="${zk.root}/src/contrib/${name}/conf" /> + <fileset dir="${zk.root}/src/java/lib"> + <include name="**/*.jar" /> + </fileset> + </classpath> + </java> + </target> + + <target name="tar" depends="clean, jar"> + <echo message="building tar.gz: ${name}" /> + <macro_tar param.destfile="${build.dir}/zookeeper-${version}-${name}.tar.gz"> + <param.listofitems> + <tarfileset dir="${build.dir}/lib" prefix="lib" includes="**/*.jar" /> + <tarfileset file="${build.dir}/zookeeper-*-rest.jar" /> + <tarfileset dir="${zk.root}/build" includes="zookeeper-*.jar" prefix="lib" /> + <tarfileset dir="${zk.root}/src/contrib/${name}/conf" prefix="conf" /> + <tarfileset dir="${zk.root}/src/java/lib" prefix="lib" includes="**/*.jar" /> + <tarfileset file="${zk.root}/src/contrib/${name}/rest.sh" /> + </param.listofitems> + </macro_tar> + </target> + +</project> + http://git-wip-us.apache.org/repos/asf/zookeeper/blob/b0df8fe1/zookeeper-contrib/zookeeper-contrib-rest/conf/keys/README ---------------------------------------------------------------------- diff --git a/zookeeper-contrib/zookeeper-contrib-rest/conf/keys/README b/zookeeper-contrib/zookeeper-contrib-rest/conf/keys/README new file mode 100644 index 0000000..085810a --- /dev/null +++ b/zookeeper-contrib/zookeeper-contrib-rest/conf/keys/README @@ -0,0 +1,8 @@ + +In order to generate .jks (java keystore files) you need to use keytool. + +The password for the existing .jks is "123456" (without quotes). + +Some tutorials: + - http://www.mobilefish.com/tutorials/java/java_quickguide_keytool.html + http://git-wip-us.apache.org/repos/asf/zookeeper/blob/b0df8fe1/zookeeper-contrib/zookeeper-contrib-rest/conf/keys/rest.cer ---------------------------------------------------------------------- diff --git a/zookeeper-contrib/zookeeper-contrib-rest/conf/keys/rest.cer b/zookeeper-contrib/zookeeper-contrib-rest/conf/keys/rest.cer new file mode 100644 index 0000000..13e5aab Binary files /dev/null and b/zookeeper-contrib/zookeeper-contrib-rest/conf/keys/rest.cer differ http://git-wip-us.apache.org/repos/asf/zookeeper/blob/b0df8fe1/zookeeper-contrib/zookeeper-contrib-rest/conf/keys/rest.jks ---------------------------------------------------------------------- diff --git a/zookeeper-contrib/zookeeper-contrib-rest/conf/keys/rest.jks b/zookeeper-contrib/zookeeper-contrib-rest/conf/keys/rest.jks new file mode 100644 index 0000000..539e8be Binary files /dev/null and b/zookeeper-contrib/zookeeper-contrib-rest/conf/keys/rest.jks differ http://git-wip-us.apache.org/repos/asf/zookeeper/blob/b0df8fe1/zookeeper-contrib/zookeeper-contrib-rest/conf/log4j.properties ---------------------------------------------------------------------- diff --git a/zookeeper-contrib/zookeeper-contrib-rest/conf/log4j.properties b/zookeeper-contrib/zookeeper-contrib-rest/conf/log4j.properties new file mode 100644 index 0000000..21ba7e4 --- /dev/null +++ b/zookeeper-contrib/zookeeper-contrib-rest/conf/log4j.properties @@ -0,0 +1,68 @@ +# +# +# 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. +# +# + +# +# ZooKeeper Logging Configuration +# + +# Format is "<default threshold> (, <appender>)+ + +# DEFAULT: console appender only +log4j.rootLogger=INFO, CONSOLE + +# Example with rolling log file +#log4j.rootLogger=DEBUG, CONSOLE, ROLLINGFILE + +# Example with rolling log file and tracing +#log4j.rootLogger=TRACE, CONSOLE, ROLLINGFILE, TRACEFILE + +# +# Log INFO level and above messages to the console +# +log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender +log4j.appender.CONSOLE.Threshold=INFO +log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout +log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} - %-5p - [%t:%C{1}@%L] - %m%n + +# +# Add ROLLINGFILE to rootLogger to get log file output +# Log DEBUG level and above messages to a log file +log4j.appender.ROLLINGFILE=org.apache.log4j.ConsoleAppender +log4j.appender.ROLLINGFILE.Threshold=DEBUG +log4j.appender.ROLLINGFILE.File=bookkeeper.log +log4j.appender.ROLLINGFILE.layout=org.apache.log4j.PatternLayout +log4j.appender.ROLLINGFILE.layout.ConversionPattern=%d{ISO8601} - %-5p - [%t:%C{1}@%L] - %m%n + +# Max log file size of 10MB +log4j.appender.ROLLINGFILE.MaxFileSize=10MB +# uncomment the next line to limit number of backup files +#log4j.appender.ROLLINGFILE.MaxBackupIndex=10 + +# +# Add TRACEFILE to rootLogger to get log file output +# Log DEBUG level and above messages to a log file +log4j.appender.TRACEFILE=org.apache.log4j.FileAppender +log4j.appender.TRACEFILE.Threshold=TRACE +log4j.appender.TRACEFILE.File=bookkeeper_trace.log + +log4j.appender.TRACEFILE.layout=org.apache.log4j.PatternLayout +### Notice we are including log4j's NDC here (%x) +log4j.appender.TRACEFILE.layout.ConversionPattern=%d{ISO8601} - %-5p [%t:%C{1}@%L][%x] - %m%n http://git-wip-us.apache.org/repos/asf/zookeeper/blob/b0df8fe1/zookeeper-contrib/zookeeper-contrib-rest/conf/rest.properties ---------------------------------------------------------------------- diff --git a/zookeeper-contrib/zookeeper-contrib-rest/conf/rest.properties b/zookeeper-contrib/zookeeper-contrib-rest/conf/rest.properties new file mode 100644 index 0000000..f0abb45 --- /dev/null +++ b/zookeeper-contrib/zookeeper-contrib-rest/conf/rest.properties @@ -0,0 +1,70 @@ +# +# +# 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. +# +# + +# +# ZooKeeper REST Gateway Configuration file +# + +rest.port = 9998 + +# +# Endpoint definition +# + +# plain configuration <context-path>;<host-port> +rest.endpoint.1 = /;localhost:2181,localhost:2182 + +# ... or chrooted to /zookeeper +# rest.endpoint.1 = /;localhost:2181,localhost:2182/zookeeper + +# HTTP Basic authentication for this endpoint +# rest.endpoint.1.http.auth = root:root1 + +# create -e /a data digest:'demo:ojnHEyje6F33LLzGVzg+yatf4Fc=':cdrwa +# any session on this endpoint will use authentication +# rest.endpoint.1.zk.digest = demo:test + +# you can easily generate the ACL using Python: +# import sha; sha.sha('demo:test').digest().encode('base64').strip() + +# +# ... you can define as many endpoints as you wish +# + +# rest.endpoint.2 = /restricted;localhost:2181 +# rest.endpoint.2.http.auth = admin:pass + +# rest.endpoint.3 = /cluster1;localhost:2181,localhost:2182 +# ** you should configure one end-point for each ZooKeeper cluster +# etc. + +# Global HTTP Basic Authentication +# You should also enable HTTPS-only access +# The authentication credentials are sent as plain text + +# rest.http.auth = guest:guest1 + +# Uncomment the lines bellow to allow https-only access + +# rest.ssl = true +# rest.ssl.jks = keys/rest.jks +# rest.ssl.jks.pass = 123456 + \ No newline at end of file http://git-wip-us.apache.org/repos/asf/zookeeper/blob/b0df8fe1/zookeeper-contrib/zookeeper-contrib-rest/ivy.xml ---------------------------------------------------------------------- diff --git a/zookeeper-contrib/zookeeper-contrib-rest/ivy.xml b/zookeeper-contrib/zookeeper-contrib-rest/ivy.xml new file mode 100644 index 0000000..5ed8a9e --- /dev/null +++ b/zookeeper-contrib/zookeeper-contrib-rest/ivy.xml @@ -0,0 +1,48 @@ +<!-- + 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. +--> + +<ivy-module version="2.0" + xmlns:e="http://ant.apache.org/ivy/extra"> + + <info organisation="org.apache.zookeeper" + module="${name}" revision="${version}"> + <license name="Apache 2.0"/> + <ivyauthor name="Apache ZooKeeper" url="http://zookeeper.apache.org"/> + <description>ZooKeeper REST</description> + </info> + + <configurations defaultconfmapping="default"> + <conf name="default"/> + <conf name="test"/> + </configurations> + + <dependencies> + <dependency org="org.slf4j" name="slf4j-api" rev="1.7.5"/> + <dependency org="org.slf4j" name="slf4j-log4j12" rev="1.7.5" transitive="false"/> + + <!-- transitive false turns off dependency checking, log4j deps seem borked --> + <dependency org="log4j" name="log4j" rev="1.2.17" transitive="false"/> + <dependency org="asm" name="asm" rev="3.1" /> + <dependency org="com.sun.grizzly" name="grizzly-servlet-webserver" rev="1.9.8" /> + <dependency org="com.sun.jersey" name="jersey-server" rev="1.1.5.1" /> + <dependency org="com.sun.jersey" name="jersey-json" rev="1.1.5.1" /> + + <dependency org="junit" name="junit" rev="4.12" conf="test->default"/> + <dependency org="com.sun.jersey" name="jersey-client" rev="1.1.5.1" conf="test->default"/> + </dependencies> + +</ivy-module> http://git-wip-us.apache.org/repos/asf/zookeeper/blob/b0df8fe1/zookeeper-contrib/zookeeper-contrib-rest/rest.sh ---------------------------------------------------------------------- diff --git a/zookeeper-contrib/zookeeper-contrib-rest/rest.sh b/zookeeper-contrib/zookeeper-contrib-rest/rest.sh new file mode 100644 index 0000000..daa8198 --- /dev/null +++ b/zookeeper-contrib/zookeeper-contrib-rest/rest.sh @@ -0,0 +1,90 @@ +#!/bin/sh + +# 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. + +# +# If this scripted is run out of /usr/bin or some other system bin directory +# it should be linked to and not copied. Things like java jar files are found +# relative to the canonical path of this script. +# + +# Only follow symlinks if readlink supports it +if readlink -f "$0" > /dev/null 2>&1 +then + ZKREST=`readlink -f "$0"` +else + ZKREST="$0" +fi +ZKREST_HOME=`dirname "$ZKREST"` + +if $cygwin +then + # cygwin has a "kill" in the shell itself, gets confused + KILL=/bin/kill +else + KILL=kill +fi + +if [ -z $ZKREST_PIDFILE ] + then ZKREST_PIDFILE=$ZKREST_HOME/server.pid +fi + +ZKREST_MAIN=org.apache.zookeeper.server.jersey.RestMain + +ZKREST_CONF=$ZKREST_HOME/conf +ZKREST_LOG=$ZKREST_HOME/zkrest.log + +CLASSPATH="$ZKREST_CONF:$CLASSPATH" + +for i in "$ZKREST_HOME"/lib/*.jar +do + CLASSPATH="$i:$CLASSPATH" +done + +for i in "$ZKREST_HOME"/zookeeper-*.jar +do + CLASSPATH="$i:$CLASSPATH" +done + +case $1 in +start) + echo "Starting ZooKeeper REST Gateway ... " + java -cp "$CLASSPATH" $JVMFLAGS $ZKREST_MAIN >$ZKREST_LOG 2>&1 & + /bin/echo -n $! > "$ZKREST_PIDFILE" + echo STARTED + ;; +stop) + echo "Stopping ZooKeeper REST Gateway ... " + if [ ! -f "$ZKREST_PIDFILE" ] + then + echo "error: could not find file $ZKREST_PIDFILE" + exit 1 + else + $KILL -9 $(cat "$ZKREST_PIDFILE") + rm "$ZKREST_PIDFILE" + echo STOPPED + fi + ;; +restart) + shift + "$0" stop ${@} + sleep 3 + "$0" start ${@} + ;; +*) + echo "Usage: $0 {start|stop|restart}" >&2 + +esac http://git-wip-us.apache.org/repos/asf/zookeeper/blob/b0df8fe1/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/RestMain.java ---------------------------------------------------------------------- diff --git a/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/RestMain.java b/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/RestMain.java new file mode 100644 index 0000000..954ad04 --- /dev/null +++ b/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/RestMain.java @@ -0,0 +1,151 @@ +/** + * 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.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.apache.zookeeper.server.jersey.cfg.Credentials; +import org.apache.zookeeper.server.jersey.cfg.Endpoint; +import org.apache.zookeeper.server.jersey.cfg.RestCfg; +import org.apache.zookeeper.server.jersey.filters.HTTPBasicAuth; + +import com.sun.grizzly.SSLConfig; +import com.sun.grizzly.http.embed.GrizzlyWebServer; +import com.sun.grizzly.http.servlet.ServletAdapter; +import com.sun.jersey.spi.container.servlet.ServletContainer; + +/** + * Demonstration of how to run the REST service using Grizzly + */ +public class RestMain { + + private static Logger LOG = LoggerFactory.getLogger(RestMain.class); + + private GrizzlyWebServer gws; + private RestCfg cfg; + + public RestMain(RestCfg cfg) { + this.cfg = cfg; + } + + public void start() throws IOException { + System.out.println("Starting grizzly ..."); + + boolean useSSL = cfg.useSSL(); + gws = new GrizzlyWebServer(cfg.getPort(), "/tmp/23cxv45345/2131xc2/", useSSL); + // BUG: Grizzly needs a doc root if you are going to register multiple adapters + + for (Endpoint e : cfg.getEndpoints()) { + ZooKeeperService.mapContext(e.getContext(), e); + gws.addGrizzlyAdapter(createJerseyAdapter(e), new String[] { e + .getContext() }); + } + + if (useSSL) { + System.out.println("Starting SSL ..."); + String jks = cfg.getJKS("keys/rest.jks"); + String jksPassword = cfg.getJKSPassword(); + + SSLConfig sslConfig = new SSLConfig(); + URL resource = getClass().getClassLoader().getResource(jks); + if (resource == null) { + LOG.error("Unable to find the keystore file: " + jks); + System.exit(2); + } + try { + sslConfig.setKeyStoreFile(new File(resource.toURI()) + .getAbsolutePath()); + } catch (URISyntaxException e1) { + LOG.error("Unable to load keystore: " + jks, e1); + System.exit(2); + } + sslConfig.setKeyStorePass(jksPassword); + gws.setSSLConfig(sslConfig); + } + + gws.start(); + } + + public void stop() { + gws.stop(); + ZooKeeperService.closeAll(); + } + + private ServletAdapter createJerseyAdapter(Endpoint e) { + ServletAdapter jersey = new ServletAdapter(); + + jersey.setServletInstance(new ServletContainer()); + jersey.addInitParameter("com.sun.jersey.config.property.packages", + "org.apache.zookeeper.server.jersey.resources"); + jersey.setContextPath(e.getContext()); + + Credentials c = Credentials.join(e.getCredentials(), cfg + .getCredentials()); + if (!c.isEmpty()) { + jersey.addFilter(new HTTPBasicAuth(c), e.getContext() + + "-basic-auth", null); + } + + return jersey; + } + + /** + * The entry point for starting the server + * + */ + public static void main(String[] args) throws Exception { + RestCfg cfg = new RestCfg("rest.properties"); + + final RestMain main = new RestMain(cfg); + main.start(); + + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + main.stop(); + System.out.println("Got exit request. Bye."); + } + }); + + printEndpoints(cfg); + System.out.println("Server started."); + } + + private static void printEndpoints(RestCfg cfg) { + int port = cfg.getPort(); + + for (Endpoint e : cfg.getEndpoints()) { + + String context = e.getContext(); + if (context.charAt(context.length() - 1) != '/') { + context += "/"; + } + + System.out.println(String.format( + "Started %s - WADL: http://localhost:%d%sapplication.wadl", + context, port, context)); + } + } + +} http://git-wip-us.apache.org/repos/asf/zookeeper/blob/b0df8fe1/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/ZooKeeperService.java ---------------------------------------------------------------------- diff --git a/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/ZooKeeperService.java b/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/ZooKeeperService.java new file mode 100644 index 0000000..21d27a9 --- /dev/null +++ b/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/ZooKeeperService.java @@ -0,0 +1,242 @@ +/** + * 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.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.Timer; +import java.util.TimerTask; +import java.util.TreeSet; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.apache.zookeeper.WatchedEvent; +import org.apache.zookeeper.Watcher; +import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.Watcher.Event.KeeperState; +import org.apache.zookeeper.server.jersey.cfg.Endpoint; + +/** + * Singleton which provides JAX-RS resources access to the ZooKeeper client. + * There's a single session for each base uri (so usually just one). + */ +public class ZooKeeperService { + + private static Logger LOG = LoggerFactory.getLogger(ZooKeeperService.class); + + /** Map base uri to ZooKeeper host:port parameters */ + private static Map<String, Endpoint> contextMap = new HashMap<String, Endpoint>(); + + /** Map base uri to ZooKeeper session */ + private static Map<String, ZooKeeper> zkMap = new HashMap<String, ZooKeeper>(); + + /** Session timers */ + private static Map<String, SessionTimerTask> zkSessionTimers = new HashMap<String, SessionTimerTask>(); + private static Timer timer = new Timer(); + + /** Track the status of the ZooKeeper session */ + private static class MyWatcher implements Watcher { + final String contextPath; + + /** Separate watcher for each base uri */ + public MyWatcher(String contextPath) { + this.contextPath = contextPath; + } + + /** + * Track state - in particular watch for expiration. if it happens for + * re-creation of the ZK client session + */ + synchronized public void process(WatchedEvent event) { + if (event.getState() == KeeperState.Expired) { + close(contextPath); + } + } + } + + /** ZooKeeper session timer */ + private static class SessionTimerTask extends TimerTask { + + private int delay; + private String contextPath, session; + private Timer timer; + + public SessionTimerTask(int delayInSeconds, String session, + String contextPath, Timer timer) { + delay = delayInSeconds * 1000; // convert to milliseconds + this.contextPath = contextPath; + this.session = session; + this.timer = timer; + reset(); + } + + public SessionTimerTask(SessionTimerTask t) { + this(t.delay / 1000, t.session, t.contextPath, t.timer); + } + + @Override + public void run() { + if (LOG.isInfoEnabled()) { + LOG.info(String.format("Session '%s' expired after " + + "'%d' milliseconds.", session, delay)); + } + ZooKeeperService.close(contextPath, session); + } + + public void reset() { + timer.schedule(this, delay); + } + + } + + /** + * Specify ZooKeeper host:port for a particular context path. The host:port + * string is passed to the ZK client, so this can be formatted with more + * than a single host:port pair. + */ + synchronized public static void mapContext(String contextPath, Endpoint e) { + contextMap.put(contextPath, e); + } + + /** + * Reset timer for a session + */ + synchronized public static void resetTimer(String contextPath, + String session) { + if (session != null) { + String uri = concat(contextPath, session); + + SessionTimerTask t = zkSessionTimers.remove(uri); + t.cancel(); + + zkSessionTimers.put(uri, new SessionTimerTask(t)); + } + } + + /** + * Close the ZooKeeper session and remove it from the internal maps + */ + public static void close(String contextPath) { + close(contextPath, null); + } + + /** + * Close the ZooKeeper session and remove it + */ + synchronized public static void close(String contextPath, String session) { + String uri = concat(contextPath, session); + + TimerTask t = zkSessionTimers.remove(uri); + if (t != null) { + t.cancel(); + } + + ZooKeeper zk = zkMap.remove(uri); + if (zk == null) { + return; + } + try { + zk.close(); + } catch (InterruptedException e) { + LOG.error("Interrupted while closing ZooKeeper connection.", e); + } + } + + /** + * Close all the ZooKeeper sessions and remove them from the internal maps + */ + synchronized public static void closeAll() { + Set<String> sessions = new TreeSet<String>(zkMap.keySet()); + for (String key : sessions) { + close(key); + } + } + + /** + * Is there an active connection for this session? + */ + synchronized public static boolean isConnected(String contextPath, + String session) { + return zkMap.containsKey(concat(contextPath, session)); + } + + /** + * Return a ZooKeeper client not tied to a specific session. + */ + public static ZooKeeper getClient(String contextPath) throws IOException { + return getClient(contextPath, null); + } + + /** + * Return a ZooKeeper client for a session with a default expire time + * + * @throws IOException + */ + public static ZooKeeper getClient(String contextPath, String session) + throws IOException { + return getClient(contextPath, session, 5); + } + + /** + * Return a ZooKeeper client which may or may not be connected, but it will + * not be expired. This method can be called multiple times, the same object + * will be returned except in the case where the session expires (at which + * point a new session will be returned) + */ + synchronized public static ZooKeeper getClient(String contextPath, + String session, int expireTime) throws IOException { + final String connectionId = concat(contextPath, session); + + ZooKeeper zk = zkMap.get(connectionId); + if (zk == null) { + + if (LOG.isInfoEnabled()) { + LOG.info(String.format("creating new " + + "connection for : '%s'", connectionId)); + } + Endpoint e = contextMap.get(contextPath); + zk = new ZooKeeper(e.getHostPort(), 30000, new MyWatcher( + connectionId)); + + for (Map.Entry<String, String> p : e.getZooKeeperAuthInfo().entrySet()) { + zk.addAuthInfo("digest", String.format("%s:%s", p.getKey(), + p.getValue()).getBytes()); + } + + zkMap.put(connectionId, zk); + + // a session should automatically expire after an amount of time + if (session != null) { + zkSessionTimers.put(connectionId, new SessionTimerTask( + expireTime, session, contextPath, timer)); + } + } + return zk; + } + + private static String concat(String contextPath, String session) { + if (session != null) { + return String.format("%s@%s", contextPath, session); + } + return contextPath; + } + +} http://git-wip-us.apache.org/repos/asf/zookeeper/blob/b0df8fe1/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/cfg/Credentials.java ---------------------------------------------------------------------- diff --git a/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/cfg/Credentials.java b/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/cfg/Credentials.java new file mode 100644 index 0000000..0730be5 --- /dev/null +++ b/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/cfg/Credentials.java @@ -0,0 +1,47 @@ +/** + * 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.cfg; + +import java.util.HashMap; + +public class Credentials extends HashMap<String, String> { + + public static Credentials join(Credentials a, Credentials b) { + Credentials result = new Credentials(); + result.putAll(a); + result.putAll(b); + return result; + } + + public Credentials() { + super(); + } + + public Credentials(String credentials) { + super(); + + if (!credentials.trim().equals("")) { + String[] parts = credentials.split(","); + for(String p : parts) { + String[] userPass = p.split(":"); + put(userPass[0], userPass[1]); + } + } + } +} http://git-wip-us.apache.org/repos/asf/zookeeper/blob/b0df8fe1/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/cfg/Endpoint.java ---------------------------------------------------------------------- diff --git a/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/cfg/Endpoint.java b/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/cfg/Endpoint.java new file mode 100644 index 0000000..2a62782 --- /dev/null +++ b/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/cfg/Endpoint.java @@ -0,0 +1,72 @@ +/** + * 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.cfg; + +public class Endpoint { + + private String context; + private HostPortSet hostPort; + private Credentials credentials; + private Credentials zookeeperAuth; + + public Endpoint(String context, String hostPortList) { + this.context = context; + this.hostPort = new HostPortSet(hostPortList); + } + + public String getContext() { + return context; + } + + public String getHostPort() { + return hostPort.toString(); + } + + public Credentials getCredentials() { + return credentials; + } + + public void setCredentials(String c) { + this.credentials = new Credentials(c); + } + + public void setZooKeeperAuthInfo(String digest) { + zookeeperAuth = new Credentials(digest); + } + + public final Credentials getZooKeeperAuthInfo() { + return zookeeperAuth; + } + + @Override + public boolean equals(Object o) { + Endpoint e = (Endpoint) o; + return context.equals(e.context); + } + + @Override + public int hashCode() { + return context.hashCode(); + } + + @Override + public String toString() { + return String.format("<Endpoint %s %s>", context, hostPort.toString()); + } +} http://git-wip-us.apache.org/repos/asf/zookeeper/blob/b0df8fe1/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/cfg/HostPort.java ---------------------------------------------------------------------- diff --git a/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/cfg/HostPort.java b/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/cfg/HostPort.java new file mode 100644 index 0000000..51a1bdd --- /dev/null +++ b/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/cfg/HostPort.java @@ -0,0 +1,51 @@ +/** + * 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.cfg; + +public class HostPort { + + private String host; + private int port; + + public HostPort(String hostPort) { + String[] parts = hostPort.split(":"); + host = parts[0]; + port = Integer.parseInt(parts[1]); + } + + public String getHost() { + return host; + } + + public int getPort() { + return port; + } + + @Override + public boolean equals(Object o) { + HostPort p = (HostPort) o; + return host.equals(p.host) && port == p.port; + } + + @Override + public int hashCode() { + return String.format("%s:%d", host, port).hashCode(); + } + +} http://git-wip-us.apache.org/repos/asf/zookeeper/blob/b0df8fe1/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/cfg/HostPortSet.java ---------------------------------------------------------------------- diff --git a/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/cfg/HostPortSet.java b/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/cfg/HostPortSet.java new file mode 100644 index 0000000..301a565 --- /dev/null +++ b/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/cfg/HostPortSet.java @@ -0,0 +1,51 @@ +/** + * 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.cfg; + +import java.util.HashSet; +import java.util.Set; + +public class HostPortSet { + + private Set<HostPort> hostPortSet = new HashSet<HostPort>(); + private String original; + + public HostPortSet(String hostPortList) { + original = hostPortList; + + int chrootStart = hostPortList.indexOf('/'); + String hostPortPairs; + if (chrootStart != -1) { + hostPortPairs = hostPortList.substring(0, chrootStart); + } else { + hostPortPairs = hostPortList; + } + + String[] parts = hostPortPairs.split(","); + for(String p : parts) { + hostPortSet.add(new HostPort(p)); + } + } + + @Override + public String toString() { + return original; + } + +} http://git-wip-us.apache.org/repos/asf/zookeeper/blob/b0df8fe1/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/cfg/RestCfg.java ---------------------------------------------------------------------- diff --git a/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/cfg/RestCfg.java b/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/cfg/RestCfg.java new file mode 100644 index 0000000..93dd632 --- /dev/null +++ b/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/cfg/RestCfg.java @@ -0,0 +1,110 @@ +/** + * 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.cfg; + +import java.io.IOException; +import java.io.InputStream; +import java.util.HashSet; +import java.util.Properties; +import java.util.Set; + +public class RestCfg { + + private Properties cfg = new Properties(); + + private Set<Endpoint> endpoints = new HashSet<Endpoint>(); + private Credentials credentials = new Credentials(); + + public RestCfg(String resource) throws IOException { + this(RestCfg.class.getClassLoader().getResourceAsStream(resource)); + } + + public RestCfg(InputStream io) throws IOException { + try { + cfg.load(io); + extractEndpoints(); + extractCredentials(); + } finally { + io.close(); + } + } + + private void extractCredentials() { + if (cfg.containsKey("rest.http.auth")) { + credentials = new Credentials(cfg.getProperty("rest.http.auth", "")); + } + } + + private void extractEndpoints() { + int count = 1; + while (true) { + String e = cfg.getProperty( + String.format("rest.endpoint.%d", count), null); + if (e == null) { + break; + } + + String[] parts = e.split(";"); + if (parts.length != 2) { + count++; + continue; + } + Endpoint point = new Endpoint(parts[0], parts[1]); + + String c = cfg.getProperty(String.format( + "rest.endpoint.%d.http.auth", count), ""); + point.setCredentials(c); + + String digest = cfg.getProperty(String.format( + "rest.endpoint.%d.zk.digest", count), ""); + point.setZooKeeperAuthInfo(digest); + + endpoints.add(point); + count++; + } + } + + public int getPort() { + return Integer.parseInt(cfg.getProperty("rest.port", "9998")); + } + + public boolean useSSL() { + return Boolean.valueOf(cfg.getProperty("rest.ssl", "false")); + } + + public final Set<Endpoint> getEndpoints() { + return endpoints; + } + + public final Credentials getCredentials() { + return credentials; + } + + public String getJKS() { + return cfg.getProperty("rest.ssl.jks"); + } + + public String getJKS(String def) { + return cfg.getProperty("rest.ssl.jks", def); + } + + public String getJKSPassword() { + return cfg.getProperty("rest.ssl.jks.pass"); + } +} http://git-wip-us.apache.org/repos/asf/zookeeper/blob/b0df8fe1/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/filters/HTTPBasicAuth.java ---------------------------------------------------------------------- diff --git a/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/filters/HTTPBasicAuth.java b/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/filters/HTTPBasicAuth.java new file mode 100644 index 0000000..49640b5 --- /dev/null +++ b/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/filters/HTTPBasicAuth.java @@ -0,0 +1,87 @@ +/** + * 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.filters; + +import java.io.IOException; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.zookeeper.server.jersey.cfg.Credentials; + +import com.sun.jersey.core.util.Base64; + +public class HTTPBasicAuth implements Filter { + + private Credentials credentials; + + public HTTPBasicAuth(Credentials c) { + credentials = c; + } + + @Override + public void doFilter(ServletRequest req0, ServletResponse resp0, + FilterChain chain) throws IOException, ServletException { + + HttpServletRequest request = (HttpServletRequest) req0; + HttpServletResponse response = (HttpServletResponse) resp0; + + String authorization = request.getHeader("Authorization"); + if (authorization != null) { + String c[] = parseAuthorization(authorization); + if (c != null && credentials.containsKey(c[0]) + && credentials.get(c[0]).equals(c[1])) { + chain.doFilter(request, response); + return; + } + } + + response.setHeader("WWW-Authenticate", "Basic realm=\"Restricted\""); + response.sendError(401); + } + + private String[] parseAuthorization(String authorization) { + String parts[] = authorization.split(" "); + if (parts.length == 2 && parts[0].equalsIgnoreCase("Basic")) { + String userPass = Base64.base64Decode(parts[1]); + + int p = userPass.indexOf(":"); + if (p != -1) { + return new String[] { userPass.substring(0, p), + userPass.substring(p + 1) }; + } + } + return null; + } + + @Override + public void init(FilterConfig arg0) throws ServletException { + } + + @Override + public void destroy() { + } + +} http://git-wip-us.apache.org/repos/asf/zookeeper/blob/b0df8fe1/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/jaxb/ZChildren.java ---------------------------------------------------------------------- diff --git a/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/jaxb/ZChildren.java b/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/jaxb/ZChildren.java new file mode 100644 index 0000000..b3fad55 --- /dev/null +++ b/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/jaxb/ZChildren.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.jaxb; + +import java.util.ArrayList; +import java.util.List; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlElementWrapper; +import javax.xml.bind.annotation.XmlRootElement; + + +/** + * Represents the CHILD using JAXB. + * Special JSON version is required to get proper formatting in both + * JSON and XML output. See details in ZNodeResource. + */ +@XmlRootElement(name="child") +public class ZChildren { + public String path; + public String uri; + + public String child_uri_template; + @XmlElementWrapper(name="children") + @XmlElement(name="child") + public List<String> children; + + public ZChildren() { + // needed by jersey + children = new ArrayList<String>(); + } + + public ZChildren(String path, String uri, String child_uri_template, + List<String> children) + { + this.path = path; + this.uri = uri; + this.child_uri_template = child_uri_template; + if (children != null) { + this.children = children; + } else { + this.children = new ArrayList<String>(); + } + } + + @Override + public int hashCode() { + return path.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof ZChildren)) { + return false; + } + ZChildren o = (ZChildren) obj; + return path.equals(o.path) && children.equals(o.children); + } + + @Override + public String toString() { + return "ZChildren(" + path + "," + children + ")"; + } +} http://git-wip-us.apache.org/repos/asf/zookeeper/blob/b0df8fe1/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/jaxb/ZChildrenJSON.java ---------------------------------------------------------------------- diff --git a/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/jaxb/ZChildrenJSON.java b/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/jaxb/ZChildrenJSON.java new file mode 100644 index 0000000..0dcece0 --- /dev/null +++ b/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/jaxb/ZChildrenJSON.java @@ -0,0 +1,76 @@ +/** + * 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.jaxb; + +import java.util.ArrayList; +import java.util.List; + +import javax.xml.bind.annotation.XmlRootElement; + + +/** + * Represents the CHILD using JAXB. + * Special JSON version is required to get proper formatting in both + * JSON and XML output. See details in ZNodeResource. + */ +@XmlRootElement(name="child") +public class ZChildrenJSON { + public String path; + public String uri; + + public String child_uri_template; + public List<String> children; + + public ZChildrenJSON() { + // needed by jersey + children = new ArrayList<String>(); + } + + public ZChildrenJSON(String path, String uri, String child_uri_template, + List<String> children) + { + this.path = path; + this.uri = uri; + this.child_uri_template = child_uri_template; + if (children != null) { + this.children = children; + } else { + this.children = new ArrayList<String>(); + } + } + + @Override + public int hashCode() { + return path.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof ZChildrenJSON)) { + return false; + } + ZChildrenJSON o = (ZChildrenJSON) obj; + return path.equals(o.path) && children.equals(o.children); + } + + @Override + public String toString() { + return "ZChildrenJSON(" + path + "," + children + ")"; + } +} http://git-wip-us.apache.org/repos/asf/zookeeper/blob/b0df8fe1/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/jaxb/ZError.java ---------------------------------------------------------------------- diff --git a/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/jaxb/ZError.java b/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/jaxb/ZError.java new file mode 100644 index 0000000..e976ee0 --- /dev/null +++ b/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/jaxb/ZError.java @@ -0,0 +1,41 @@ +/** + * 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.jaxb; + +import javax.xml.bind.annotation.XmlRootElement; + + +/** + * Represents an ERROR using JAXB. + */ +@XmlRootElement(name="error") +public class ZError { + public String request; + public String message; + + public ZError(){ + // needed by jersey + } + + public ZError(String request, String message) { + this.request = request; + this.message = message; + } + +} http://git-wip-us.apache.org/repos/asf/zookeeper/blob/b0df8fe1/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/jaxb/ZPath.java ---------------------------------------------------------------------- diff --git a/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/jaxb/ZPath.java b/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/jaxb/ZPath.java new file mode 100644 index 0000000..4d83717 --- /dev/null +++ b/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/jaxb/ZPath.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.jaxb; + +import javax.xml.bind.annotation.XmlRootElement; + + +/** + * Represents a PATH using JAXB. + */ +@XmlRootElement(name="path") +public class ZPath { + public String path; + public String uri; + + public ZPath(){ + // needed by jersey + } + + public ZPath(String path) { + this(path, null); + } + + public ZPath(String path, String uri) { + this.path = path; + this.uri = uri; + } + + @Override + public int hashCode() { + return path.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof ZPath)) { + return false; + } + ZPath o = (ZPath) obj; + return path.equals(o.path); + } + + @Override + public String toString() { + return "ZPath(" + path + ")"; + } +} http://git-wip-us.apache.org/repos/asf/zookeeper/blob/b0df8fe1/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/jaxb/ZSession.java ---------------------------------------------------------------------- diff --git a/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/jaxb/ZSession.java b/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/jaxb/ZSession.java new file mode 100644 index 0000000..06ca9e5 --- /dev/null +++ b/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/jaxb/ZSession.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.jaxb; + +import javax.xml.bind.annotation.XmlRootElement; + +@XmlRootElement(name="session") +public class ZSession { + public String id; + public String uri; + + public ZSession() { + // needed by jersey + } + + public ZSession(String id, String uri) { + this.id = id; + this.uri = uri; + } + + @Override + public int hashCode() { + return id.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if(!(obj instanceof ZSession)) { + return false; + } + ZSession s = (ZSession) obj; + return id.equals(s.id); + } + + @Override + public String toString() { + return "ZSession(" + id +")"; + } +} http://git-wip-us.apache.org/repos/asf/zookeeper/blob/b0df8fe1/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/jaxb/ZStat.java ---------------------------------------------------------------------- diff --git a/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/jaxb/ZStat.java b/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/jaxb/ZStat.java new file mode 100644 index 0000000..af70d18 --- /dev/null +++ b/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/jaxb/ZStat.java @@ -0,0 +1,106 @@ +/** + * 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.jaxb; + +import javax.xml.bind.annotation.XmlRootElement; + + +/** + * Represents a STAT using JAXB. + */ +@XmlRootElement(name="stat") +public class ZStat { + public String path; + public String uri; + public byte[] data64; + public String dataUtf8; + + public long czxid; + public long mzxid; + public long ctime; + public long mtime; + public int version; + public int cversion; + public int aversion; + public long ephemeralOwner; + public int dataLength; + public int numChildren; + public long pzxid; + + + public ZStat(){ + // needed by jersey + } + + public ZStat(String path, byte[] data64, String dataUtf8) + { + this.path = path; + this.data64 = data64; + this.dataUtf8 = dataUtf8; + } + + public ZStat(String path, String uri, byte[] data64, String dataUtf8, + long czxid, long mzxid, long ctime, long mtime, int version, + int cversion, int aversion, long ephemeralOwner, int dataLength, + int numChildren, long pzxid) + { + this.path = path; + this.uri = uri; + this.data64 = data64; + this.dataUtf8 = dataUtf8; + + this.czxid = czxid; + this.mzxid = mzxid; + this.ctime = ctime; + this.mtime = mtime; + this.version = version; + this.cversion = cversion; + this.aversion = aversion; + this.ephemeralOwner = ephemeralOwner; + this.dataLength = dataLength; + this.numChildren = numChildren; + this.pzxid = pzxid; + } + + @Override + public int hashCode() { + return path.hashCode(); + } + + /** + * This method considers two ZStats equal if their path, encoding, and + * data match. It does not compare the ZooKeeper + * org.apache.zookeeper.data.Stat class fields. + */ + @Override + public boolean equals(Object obj) { + if (!(obj instanceof ZStat)) { + return false; + } + ZStat o = (ZStat) obj; + return toString().equals(o.toString()); + } + + @Override + public String toString() { + return "ZStat(" + path + "," + "b64[" + + (data64 == null ? null : new String(data64)) + "]," + + dataUtf8 + ")"; + } +} http://git-wip-us.apache.org/repos/asf/zookeeper/blob/b0df8fe1/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/resources/JAXBContextResolver.java ---------------------------------------------------------------------- diff --git a/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/resources/JAXBContextResolver.java b/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/resources/JAXBContextResolver.java new file mode 100644 index 0000000..0893586 --- /dev/null +++ b/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/resources/JAXBContextResolver.java @@ -0,0 +1,72 @@ +/** + * 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.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import javax.ws.rs.ext.ContextResolver; +import javax.ws.rs.ext.Provider; +import javax.xml.bind.JAXBContext; + +import org.apache.zookeeper.server.jersey.jaxb.ZChildrenJSON; +import org.apache.zookeeper.server.jersey.jaxb.ZPath; +import org.apache.zookeeper.server.jersey.jaxb.ZStat; + +import com.sun.jersey.api.json.JSONConfiguration; +import com.sun.jersey.api.json.JSONJAXBContext; + +/** + * Tell Jersey how to resolve JSON formatting. Specifically detail the + * fields which are arrays and which are numbers (not strings). + */ +@Provider +@SuppressWarnings("unchecked") +public final class JAXBContextResolver implements ContextResolver<JAXBContext> { + private final JAXBContext context; + + private final Set<Class> typesSet; + + public JAXBContextResolver() throws Exception { + Class[] typesArr = + new Class[]{ZPath.class, ZStat.class, ZChildrenJSON.class}; + typesSet = new HashSet<Class>(Arrays.asList(typesArr)); + context = new JSONJAXBContext( + JSONConfiguration.mapped() + .arrays("children") + .nonStrings("czxid") + .nonStrings("mzxid") + .nonStrings("ctime") + .nonStrings("mtime") + .nonStrings("version") + .nonStrings("cversion") + .nonStrings("aversion") + .nonStrings("ephemeralOwner") + .nonStrings("dataLength") + .nonStrings("numChildren") + .nonStrings("pzxid") + .build(), + typesArr); + } + + public JAXBContext getContext(Class<?> objectType) { + return (typesSet.contains(objectType)) ? context : null; + } +} http://git-wip-us.apache.org/repos/asf/zookeeper/blob/b0df8fe1/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/resources/KeeperExceptionMapper.java ---------------------------------------------------------------------- diff --git a/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/resources/KeeperExceptionMapper.java b/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/resources/KeeperExceptionMapper.java new file mode 100644 index 0000000..fdfc27b --- /dev/null +++ b/zookeeper-contrib/zookeeper-contrib-rest/src/main/java/org/apache/zookeeper/server/jersey/resources/KeeperExceptionMapper.java @@ -0,0 +1,86 @@ +/** + * 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.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.KeeperException; +import org.apache.zookeeper.server.jersey.jaxb.ZError; + + +/** + * Map KeeperException to HTTP status codes + */ +@Provider +public class KeeperExceptionMapper implements ExceptionMapper<KeeperException> { + private UriInfo ui; + + public KeeperExceptionMapper(@Context UriInfo ui) { + this.ui = ui; + } + + public Response toResponse(KeeperException e) { + Response.Status status; + String message; + + String path = e.getPath(); + + switch(e.code()) { + case AUTHFAILED: + status = Response.Status.UNAUTHORIZED; + message = path + " not authorized"; + break; + case BADARGUMENTS: + status = Response.Status.BAD_REQUEST; + message = path + " bad arguments"; + break; + case BADVERSION: + status = Response.Status.PRECONDITION_FAILED; + message = path + " bad version"; + break; + case INVALIDACL: + status = Response.Status.BAD_REQUEST; + message = path + " invalid acl"; + break; + case NODEEXISTS: + status = Response.Status.CONFLICT; + message = path + " already exists"; + break; + case NONODE: + status = Response.Status.NOT_FOUND; + message = path + " not found"; + break; + case NOTEMPTY: + status = Response.Status.CONFLICT; + message = path + " not empty"; + break; + default: + status = Response.Status.fromStatusCode(502); // bad gateway + message = "Error processing request for " + path + + " : " + e.getMessage(); + } + + return Response.status(status).entity( + new ZError(ui.getRequestUri().toString(), message)).build(); + } +}
