[bintray-client-java] 01/22: Import upstream 0.8.1

Kai-Chung Yan seamlik-guest at moszumanska.debian.org
Thu Jun 4 08:20:02 UTC 2015


This is an automated email from the git hooks/post-receive script.

seamlik-guest pushed a commit to branch master
in repository bintray-client-java.

commit 08f28d4361c394a95e8c3f65a370113b4d304b8e
Author: Kai-Chung Yan <seamlikok at gmail.com>
Date:   Sun May 31 22:06:48 2015 +0800

    Import upstream 0.8.1
---
 .gitignore                                         |   18 +
 CONTRIBUTING.md                                    |    8 +
 LICENSE                                            |  191 ++++
 README.md                                          |    7 +
 .../bintray/client/api/BintrayCallException.java   |   83 ++
 .../client/api/MultipleBintrayCallException.java   |   30 +
 .../bintray/client/api/details/Attribute.java      |  138 +++
 .../client/api/details/ObjectMapperHelper.java     |   31 +
 .../bintray/client/api/details/PackageDetails.java |  375 +++++++
 .../client/api/details/RepositoryDetails.java      |   84 ++
 .../bintray/client/api/details/SubjectDetails.java |  104 ++
 .../bintray/client/api/details/VersionDetails.java |  245 +++++
 .../client/api/handle/AttributesSearchQuery.java   |    9 +
 .../api/handle/AttributesSearchQueryClause.java    |   43 +
 .../jfrog/bintray/client/api/handle/Bintray.java   |   55 +
 .../jfrog/bintray/client/api/handle/Handle.java    |   13 +
 .../bintray/client/api/handle/PackageHandle.java   |   42 +
 .../client/api/handle/RepositoryHandle.java        |   29 +
 .../bintray/client/api/handle/SubjectHandle.java   |   18 +
 .../bintray/client/api/handle/VersionHandle.java   |   50 +
 .../com/jfrog/bintray/client/api/model/Pkg.java    |   41 +
 .../jfrog/bintray/client/api/model/Repository.java |   23 +
 .../jfrog/bintray/client/api/model/Subject.java    |   27 +
 .../jfrog/bintray/client/api/model/Version.java    |   35 +
 build.gradle                                       |  195 ++++
 gradle.properties                                  |    1 +
 gradle/wrapper/gradle-wrapper.jar                  |  Bin 0 -> 51017 bytes
 gradle/wrapper/gradle-wrapper.properties           |    6 +
 gradlew                                            |  164 +++
 gradlew.bat                                        |   90 ++
 .../jfrog/bintray/client/impl/BintrayClient.java   |   80 ++
 .../client/impl/HttpClientConfigurator.java        |  407 ++++++++
 .../handle/AttributesSearchQueryClauseImpl.java    |  127 +++
 .../impl/handle/AttributesSearchQueryImpl.java     |  103 ++
 .../bintray/client/impl/handle/BintrayImpl.java    |  407 ++++++++
 .../client/impl/handle/PackageHandleImpl.java      |  201 ++++
 .../client/impl/handle/RepositoryHandleImpl.java   |  146 +++
 .../client/impl/handle/SubjectHandleImpl.java      |   56 +
 .../client/impl/handle/VersionHandleImpl.java      |  220 ++++
 .../bintray/client/impl/model/PackageImpl.java     |  182 ++++
 .../bintray/client/impl/model/RepositoryImpl.java  |   65 ++
 .../bintray/client/impl/model/SubjectImpl.java     |   80 ++
 .../bintray/client/impl/model/VersionImpl.java     |  162 +++
 .../com/jfrog/bintray/client/impl/util/URI.java    | 1086 ++++++++++++++++++++
 .../jfrog/bintray/client/impl/util/URIUtil.java    |  158 +++
 .../bintray/client/impl/BintrayClientSpec.groovy   |  704 +++++++++++++
 impl/src/test/resources/simplelog.properties       |    5 +
 impl/src/test/resources/testJar1.jar               |  Bin 0 -> 9646 bytes
 impl/src/test/resources/testJar2.jar               |  Bin 0 -> 50976 bytes
 settings.gradle                                    |    6 +
 50 files changed, 6350 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..8040136
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,18 @@
+impl\src\test\resources\bintray-client.properties
+Recursive
+target
+build
+*.iml
+*.ipr
+*.iws
+cobertura.ser
+DS_Store
+.gradle
+.vagrant
+BINTRAY_HOME
+out
+atlassian-*.xml
+.idea/
+.project
+.classpath
+.settings
\ No newline at end of file
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..ed87d46
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,8 @@
+# JFrog welcomes community contribution!
+
+Before we can accept your contribution, process your GitHub pull requests, and thank you full heartedly, we request that you will fill out and submit JFrog's Contributor License Agreement (CLA).
+
+[Click here](https://secure.echosign.com/public/hostedForm?formid=5IYKLZ2RXB543N) to submit the JFrog CLA.
+This should only take a minute to complete and is a one-time process.
+
+*Thanks for Your Contribution to the Community!* :-)
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..37ec93a
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,191 @@
+Apache License
+Version 2.0, January 2004
+http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+"License" shall mean the terms and conditions for use, reproduction, and
+distribution as defined by Sections 1 through 9 of this document.
+
+"Licensor" shall mean the copyright owner or entity authorized by the copyright
+owner that is granting the License.
+
+"Legal Entity" shall mean the union of the acting entity and all other entities
+that control, are controlled by, or are under common control with that entity.
+For the purposes of this definition, "control" means (i) the power, direct or
+indirect, to cause the direction or management of such entity, whether by
+contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
+outstanding shares, or (iii) beneficial ownership of such entity.
+
+"You" (or "Your") shall mean an individual or Legal Entity exercising
+permissions granted by this License.
+
+"Source" form shall mean the preferred form for making modifications, including
+but not limited to software source code, documentation source, and configuration
+files.
+
+"Object" form shall mean any form resulting from mechanical transformation or
+translation of a Source form, including but not limited to compiled object code,
+generated documentation, and conversions to other media types.
+
+"Work" shall mean the work of authorship, whether in Source or Object form, made
+available under the License, as indicated by a copyright notice that is included
+in or attached to the work (an example is provided in the Appendix below).
+
+"Derivative Works" shall mean any work, whether in Source or Object form, that
+is based on (or derived from) the Work and for which the editorial revisions,
+annotations, elaborations, or other modifications represent, as a whole, an
+original work of authorship. For the purposes of this License, Derivative Works
+shall not include works that remain separable from, or merely link (or bind by
+name) to the interfaces of, the Work and Derivative Works thereof.
+
+"Contribution" shall mean any work of authorship, including the original version
+of the Work and any modifications or additions to that Work or Derivative Works
+thereof, that is intentionally submitted to Licensor for inclusion in the Work
+by the copyright owner or by an individual or Legal Entity authorized to submit
+on behalf of the copyright owner. For the purposes of this definition,
+"submitted" means any form of electronic, verbal, or written communication sent
+to the Licensor or its representatives, including but not limited to
+communication on electronic mailing lists, source code control systems, and
+issue tracking systems that are managed by, or on behalf of, the Licensor for
+the purpose of discussing and improving the Work, but excluding communication
+that is conspicuously marked or otherwise designated in writing by the copyright
+owner as "Not a Contribution."
+
+"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
+of whom a Contribution has been received by Licensor and subsequently
+incorporated within the Work.
+
+2. Grant of Copyright License.
+
+Subject to the terms and conditions of this License, each Contributor hereby
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
+irrevocable copyright license to reproduce, prepare Derivative Works of,
+publicly display, publicly perform, sublicense, and distribute the Work and such
+Derivative Works in Source or Object form.
+
+3. Grant of Patent License.
+
+Subject to the terms and conditions of this License, each Contributor hereby
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
+irrevocable (except as stated in this section) patent license to make, have
+made, use, offer to sell, sell, import, and otherwise transfer the Work, where
+such license applies only to those patent claims licensable by such Contributor
+that are necessarily infringed by their Contribution(s) alone or by combination
+of their Contribution(s) with the Work to which such Contribution(s) was
+submitted. If You institute patent litigation against any entity (including a
+cross-claim or counterclaim in a lawsuit) alleging that the Work or a
+Contribution incorporated within the Work constitutes direct or contributory
+patent infringement, then any patent licenses granted to You under this License
+for that Work shall terminate as of the date such litigation is filed.
+
+4. Redistribution.
+
+You may reproduce and distribute copies of the Work or Derivative Works thereof
+in any medium, with or without modifications, and in Source or Object form,
+provided that You meet the following conditions:
+
+You must give any other recipients of the Work or Derivative Works a copy of
+this License; and
+You must cause any modified files to carry prominent notices stating that You
+changed the files; and
+You must retain, in the Source form of any Derivative Works that You distribute,
+all copyright, patent, trademark, and attribution notices from the Source form
+of the Work, excluding those notices that do not pertain to any part of the
+Derivative Works; and
+If the Work includes a "NOTICE" text file as part of its distribution, then any
+Derivative Works that You distribute must include a readable copy of the
+attribution notices contained within such NOTICE file, excluding those notices
+that do not pertain to any part of the Derivative Works, in at least one of the
+following places: within a NOTICE text file distributed as part of the
+Derivative Works; within the Source form or documentation, if provided along
+with the Derivative Works; or, within a display generated by the Derivative
+Works, if and wherever such third-party notices normally appear. The contents of
+the NOTICE file are for informational purposes only and do not modify the
+License. You may add Your own attribution notices within Derivative Works that
+You distribute, alongside or as an addendum to the NOTICE text from the Work,
+provided that such additional attribution notices cannot be construed as
+modifying the License.
+You may add Your own copyright statement to Your modifications and may provide
+additional or different license terms and conditions for use, reproduction, or
+distribution of Your modifications, or for any such Derivative Works as a whole,
+provided Your use, reproduction, and distribution of the Work otherwise complies
+with the conditions stated in this License.
+
+5. Submission of Contributions.
+
+Unless You explicitly state otherwise, any Contribution intentionally submitted
+for inclusion in the Work by You to the Licensor shall be under the terms and
+conditions of this License, without any additional terms or conditions.
+Notwithstanding the above, nothing herein shall supersede or modify the terms of
+any separate license agreement you may have executed with Licensor regarding
+such Contributions.
+
+6. Trademarks.
+
+This License does not grant permission to use the trade names, trademarks,
+service marks, or product names of the Licensor, except as required for
+reasonable and customary use in describing the origin of the Work and
+reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty.
+
+Unless required by applicable law or agreed to in writing, Licensor provides the
+Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
+including, without limitation, any warranties or conditions of TITLE,
+NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
+solely responsible for determining the appropriateness of using or
+redistributing the Work and assume any risks associated with Your exercise of
+permissions under this License.
+
+8. Limitation of Liability.
+
+In no event and under no legal theory, whether in tort (including negligence),
+contract, or otherwise, unless required by applicable law (such as deliberate
+and grossly negligent acts) or agreed to in writing, shall any Contributor be
+liable to You for damages, including any direct, indirect, special, incidental,
+or consequential damages of any character arising as a result of this License or
+out of the use or inability to use the Work (including but not limited to
+damages for loss of goodwill, work stoppage, computer failure or malfunction, or
+any and all other commercial damages or losses), even if such Contributor has
+been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability.
+
+While redistributing the Work or Derivative Works thereof, You may choose to
+offer, and charge a fee for, acceptance of support, warranty, indemnity, or
+other liability obligations and/or rights consistent with this License. However,
+in accepting such obligations, You may act only on Your own behalf and on Your
+sole responsibility, not on behalf of any other Contributor, and only if You
+agree to indemnify, defend, and hold each Contributor harmless for any liability
+incurred by, or claims asserted against, such Contributor by reason of your
+accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work
+
+To apply the Apache License to your work, attach the following boilerplate
+notice, with the fields enclosed by brackets "[]" replaced with your own
+identifying information. (Don't include the brackets!) The text should be
+enclosed in the appropriate comment syntax for the file format. We also
+recommend that a file or class name and description of purpose be included on
+the same "printed page" as the copyright notice for easier identification within
+third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed 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.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..58ae094
--- /dev/null
+++ b/README.md
@@ -0,0 +1,7 @@
+bintray-client-java
+===================
+Java client for working with Bintray.com
+
+[![Download From Bintray](https://www.bintray.com/docs/images/bintray_badge_color.png)](https://bintray.com/jfrog/bintray-tools/bintray-client-java?bdg=1)
+
+[![Build Status](https://drone.io/github.com/bintray/bintray-client-java/status.png)](https://drone.io/github.com/bintray/bintray-client-java/latest)
diff --git a/api/src/main/java/com/jfrog/bintray/client/api/BintrayCallException.java b/api/src/main/java/com/jfrog/bintray/client/api/BintrayCallException.java
new file mode 100644
index 0000000..fdfc711
--- /dev/null
+++ b/api/src/main/java/com/jfrog/bintray/client/api/BintrayCallException.java
@@ -0,0 +1,83 @@
+package com.jfrog.bintray.client.api;
+
+import com.jfrog.bintray.client.api.details.ObjectMapperHelper;
+import org.apache.commons.io.IOUtils;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.client.HttpResponseException;
+import org.codehaus.jackson.JsonNode;
+import org.codehaus.jackson.map.ObjectMapper;
+
+import java.io.IOException;
+
+/**
+ * An exception thrown for failed operations against the Bintray api.
+ *
+ * @author danf
+ */
+public class BintrayCallException extends HttpResponseException {
+
+    private int statusCode;
+    private String reason;
+    private String message;
+
+    public BintrayCallException(int statusCode, String reason, String message) {
+        super(statusCode, reason);
+        this.statusCode = statusCode;
+        this.reason = reason;
+        this.message = message;
+    }
+
+    public BintrayCallException(HttpResponse response) {
+        super(response.getStatusLine().getStatusCode(), response.getStatusLine().getReasonPhrase());
+        String message = " ";
+        String entity = null;
+        int statusCode = response.getStatusLine().getStatusCode();
+        if (response.getEntity() != null && statusCode != 405 && statusCode != 500) {
+            try {
+                entity = IOUtils.toString(response.getEntity().getContent());
+                ObjectMapper mapper = ObjectMapperHelper.objectMapper;
+                JsonNode node = mapper.readTree(entity);
+                message = node.get("message").getTextValue();
+            } catch (IOException | NullPointerException e) {
+                //Null entity?
+                if (entity != null) {
+                    message = entity;
+                }
+            }
+        }
+        this.statusCode = statusCode;
+        this.reason = response.getStatusLine().getReasonPhrase();
+        this.message = message;
+    }
+
+    public BintrayCallException(Exception e) {
+        super(HttpStatus.SC_BAD_REQUEST, e.getMessage());
+        this.statusCode = HttpStatus.SC_BAD_REQUEST;
+        this.reason = e.getMessage();
+        this.message = (e.getCause() == null) ? " " : " : " + e.getCause().getMessage();
+    }
+
+    public int getStatusCode() {
+        return statusCode;
+    }
+
+    public String getReason() {
+        return reason;
+    }
+
+    @Override
+    public String getMessage() {
+        return message;
+    }
+
+    public void setMessage(String newMessage) {
+        this.message = newMessage;
+    }
+
+    @Override
+    public String toString() {
+        return statusCode + ", " + reason + " " + message;
+    }
+
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/jfrog/bintray/client/api/MultipleBintrayCallException.java b/api/src/main/java/com/jfrog/bintray/client/api/MultipleBintrayCallException.java
new file mode 100644
index 0000000..f95f0e2
--- /dev/null
+++ b/api/src/main/java/com/jfrog/bintray/client/api/MultipleBintrayCallException.java
@@ -0,0 +1,30 @@
+package com.jfrog.bintray.client.api;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * An Exception that aggregates multiple BintrayCallExceptions for async operations
+ *
+ * @author danf
+ */
+public class MultipleBintrayCallException extends Exception {
+
+    List<BintrayCallException> exceptions;
+
+    public MultipleBintrayCallException() {
+        super();
+        exceptions = new ArrayList<>();
+    }
+
+    public MultipleBintrayCallException(List<BintrayCallException> exceptions) {
+        super();
+        this.exceptions = exceptions;
+    }
+
+    public List<BintrayCallException> getExceptions() {
+        return exceptions;
+    }
+
+
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/jfrog/bintray/client/api/details/Attribute.java b/api/src/main/java/com/jfrog/bintray/client/api/details/Attribute.java
new file mode 100644
index 0000000..7bc8173
--- /dev/null
+++ b/api/src/main/java/com/jfrog/bintray/client/api/details/Attribute.java
@@ -0,0 +1,138 @@
+package com.jfrog.bintray.client.api.details;
+
+
+import org.codehaus.jackson.annotate.*;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.type.TypeReference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+
+import static java.util.Arrays.asList;
+
+/**
+ * This class represents an attribute (version or package)
+ * NOTE: when serializing this class use getObjectMapper to obtain a suitable mapper for this class
+ *
+ * @author Dan Feldman
+ */
+ at JsonPropertyOrder({"name", "values", "type"})
+ at JsonIgnoreProperties(ignoreUnknown = true)
+public class Attribute<T> {
+    private static final Logger log = LoggerFactory.getLogger(Attribute.class);
+    @JsonProperty("name")
+    private String name;
+    @JsonProperty("values")
+    private List<T> values;
+    @JsonProperty("type")
+    private Type type;
+
+    @SafeVarargs
+    public Attribute(String name, T... values) {
+        this(name, null, asList(values));
+    }
+
+
+    @SafeVarargs
+    public Attribute(String name, Type type, T... values) {
+        this(name, type, asList(values));
+    }
+
+    @JsonCreator
+    public Attribute(@JsonProperty("name") String name, @JsonProperty("type") Type type, @JsonProperty("values") List<T> values) {
+        this.name = name;
+        if (type == null) {
+            type = Type.string;     //Type defaults to string
+        }
+        this.type = type;
+        this.values = values;
+    }
+
+    public static ObjectMapper getObjectMapper() {
+        return ObjectMapperHelper.objectMapper;
+    }
+
+    /**
+     * Produces a json from a list of attributes
+     *
+     * @param attributeDetails List of attributes to serialize
+     * @return A string representing the json
+     * @throws IOException
+     */
+    @JsonIgnore
+    public static String getJsonFromAttributeList(List<Attribute> attributeDetails) throws IOException {
+        ObjectMapper mapper = ObjectMapperHelper.objectMapper;
+        String jsonContent;
+        try {
+            jsonContent = mapper.writeValueAsString(attributeDetails);
+        } catch (IOException e) {
+            log.error("Can't process the json file: " + e.getMessage());
+            throw e;
+        }
+        return jsonContent;
+    }
+
+    @JsonIgnore
+    public static List<Attribute> getAttributeListFromJson(InputStream inputStream) throws IOException {
+        ObjectMapper mapper = ObjectMapperHelper.objectMapper;
+        List<Attribute> attributes;
+        try {
+            attributes = mapper.readValue(inputStream, new TypeReference<List<Attribute>>() {
+            });
+        } catch (IOException e) {
+            log.error("Can't process the json file: " + e.getMessage());
+            throw e;
+        }
+        return attributes;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public List<T> getValues() {
+        return values;
+    }
+
+    public Type getType() {
+        return type;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        Attribute attribute = (Attribute) o;
+
+        if (!name.equals(attribute.name)) return false;
+        if (!values.equals(attribute.values)) return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = name.hashCode();
+        result = 31 * result + type.hashCode();
+        result = 31 * result + values.hashCode();
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "{" +
+                "Name='" + name + '\'' +
+                ", Type=" + type +
+                ", Values=" + values +
+                '}';
+    }
+
+    public enum Type {
+        string, date, number, Boolean
+    }
+}
+
diff --git a/api/src/main/java/com/jfrog/bintray/client/api/details/ObjectMapperHelper.java b/api/src/main/java/com/jfrog/bintray/client/api/details/ObjectMapperHelper.java
new file mode 100644
index 0000000..4bb3e0b
--- /dev/null
+++ b/api/src/main/java/com/jfrog/bintray/client/api/details/ObjectMapperHelper.java
@@ -0,0 +1,31 @@
+package com.jfrog.bintray.client.api.details;
+
+import org.codehaus.jackson.JsonFactory;
+import org.codehaus.jackson.map.DeserializationConfig;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.map.SerializationConfig;
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+
+/**
+ * Helper class that provides the ObjectMapper for all Details classes
+ *
+ * @author Dan Feldman
+ */
+public class ObjectMapperHelper {
+
+    public static final ObjectMapper objectMapper = buildDetailsMapper();
+
+    private static ObjectMapper buildDetailsMapper() {
+        ObjectMapper mapper = new ObjectMapper(new JsonFactory());
+
+        // TODO: when moving to Jackson 2.x these can be replaced with JodaModule
+        mapper.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS, false);
+        mapper.configure(SerializationConfig.Feature.WRITE_DATE_KEYS_AS_TIMESTAMPS, false);
+
+        //Don't include keys with null values implicitly, only explicitly set values should be sent over
+        mapper.configure(DeserializationConfig.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY, false);
+        mapper.configure(SerializationConfig.Feature.WRITE_NULL_MAP_VALUES, false);
+        mapper.setSerializationInclusion(JsonSerialize.Inclusion.NON_NULL);
+        return mapper;
+    }
+}
diff --git a/api/src/main/java/com/jfrog/bintray/client/api/details/PackageDetails.java b/api/src/main/java/com/jfrog/bintray/client/api/details/PackageDetails.java
new file mode 100644
index 0000000..d045d31
--- /dev/null
+++ b/api/src/main/java/com/jfrog/bintray/client/api/details/PackageDetails.java
@@ -0,0 +1,375 @@
+package com.jfrog.bintray.client.api.details;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonIgnore;
+import org.codehaus.jackson.annotate.JsonIgnoreProperties;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.joda.time.DateTime;
+
+import java.util.List;
+
+/**
+ * This class is used to serialize and deserialize the needed json to
+ * pass to or receive from Bintray when performing actions on a package
+ * NOTE: when serializing this class use getObjectMapper to obtain a suitable mapper for this class
+ *
+ * @author Dan Feldman
+ */
+ at JsonIgnoreProperties(ignoreUnknown = true)
+public class PackageDetails {
+
+    //Properties marked with @JsonPropery here are serialized to the create \ update package requests, the rest are
+    // only deserialized when getting the package info
+    @JsonProperty(value = "name")
+    private String name;
+    @JsonIgnore
+    private String repo;
+    @JsonIgnore
+    private String owner;
+    @JsonIgnore
+    private String subject;
+    @JsonProperty(value = "desc")
+    private String description;
+    @JsonProperty(value = "labels")
+    private List<String> labels;
+    @JsonIgnore
+    private List<String> attributeNames;
+    @JsonIgnore
+    private Integer followersCount;
+    @JsonIgnore
+    private DateTime created;
+    @JsonProperty(value = "website_url")
+    private String websiteUrl;
+    @JsonProperty(value = "issue_tracker_url")
+    private String issueTrackerUrl;
+    @JsonProperty(value = "github_repo")
+    private String gitHubRepo;
+    @JsonProperty(value = "github_release_notes_file")
+    private String releaseNotesFile;
+    @JsonProperty(value = "public_download_numbers")
+    private Boolean publicDownloadNumbers;
+    @JsonProperty(value = "public_stats")
+    private Boolean publicStats;
+    @JsonIgnore
+    private List<String> linkedRepos;
+    @JsonIgnore
+    private List<String> versions;
+    @JsonProperty(value = "licenses")
+    private List<String> licenses;
+    @JsonIgnore
+    private String latestVersion;
+    @JsonIgnore
+    private DateTime updated;
+    @JsonIgnore
+    private Integer rating;
+    @JsonIgnore
+    private Integer ratingCount;
+    @JsonIgnore
+    private List<String> systemIds;
+    @JsonProperty(value = "vcs_url")
+    private String vcsUrl;
+
+    @JsonIgnore
+    private List<Attribute> attributes;
+
+    @JsonCreator
+    public PackageDetails() {
+    }
+
+    public PackageDetails(String name) {
+        this.name = name;
+    }
+
+    public static ObjectMapper getObjectMapper() {
+        return ObjectMapperHelper.objectMapper;
+    }
+
+    public PackageDetails name(String name) {
+        this.name = name;
+        return this;
+    }
+
+    public PackageDetails description(String description) {
+        this.description = description;
+        return this;
+    }
+
+    public PackageDetails labels(List<String> labels) {
+        this.labels = labels;
+        return this;
+    }
+
+    public PackageDetails websiteUrl(String websiteUrl) {
+        this.websiteUrl = websiteUrl;
+        return this;
+    }
+
+    public PackageDetails issueTrackerUrl(String issueTrackerUrl) {
+        this.issueTrackerUrl = issueTrackerUrl;
+        return this;
+    }
+
+    public PackageDetails gitHubRepo(String gitHubRepo) {
+        this.gitHubRepo = gitHubRepo;
+        return this;
+    }
+
+    public PackageDetails releaseNotesFile(String releaseNotesFile) {
+        this.releaseNotesFile = releaseNotesFile;
+        return this;
+    }
+
+    public PackageDetails publicDownloadNumbers(Boolean publicDownloadNumbers) {
+        this.publicDownloadNumbers = publicDownloadNumbers;
+        return this;
+    }
+
+    public PackageDetails publicStats(Boolean publicStats) {
+        this.publicStats = publicStats;
+        return this;
+    }
+
+    public PackageDetails licenses(List<String> licenses) {
+        this.licenses = licenses;
+        return this;
+    }
+
+    public PackageDetails vcsUrl(String vcsUrl) {
+        this.vcsUrl = vcsUrl;
+        return this;
+    }
+
+    public PackageDetails attributes(List<Attribute> attributes) {
+        this.attributes = attributes;
+        return this;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    @JsonIgnore
+    public String getRepo() {
+        return repo;
+    }
+
+    @JsonProperty(value = "repo")
+    public void setRepo(String repo) {
+        this.repo = repo;
+    }
+
+    @JsonIgnore
+    public String getOwner() {
+        return owner;
+    }
+
+    @JsonProperty(value = "owner")
+    public void setOwner(String owner) {
+        this.owner = owner;
+    }
+
+    @JsonIgnore
+    public String getSubject() {
+        return subject;
+    }
+
+    @JsonProperty(value = "subject")
+    public void setSubject(String subject) {
+        this.subject = subject;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public List<String> getLabels() {
+        return labels;
+    }
+
+    public void setLabels(List<String> labels) {
+        this.labels = labels;
+    }
+
+    @JsonIgnore
+    public List<String> getAttributeNames() {
+        return attributeNames;
+    }
+
+    @JsonProperty(value = "attribute_names")
+    public void setAttributeNames(List<String> attributeNames) {
+        this.attributeNames = attributeNames;
+    }
+
+    @JsonIgnore
+    public Integer getFollowersCount() {
+        return followersCount;
+    }
+
+    @JsonProperty(value = "followers_count")
+    public void setFollowersCount(Integer followersCount) {
+        this.followersCount = followersCount;
+    }
+
+    @JsonIgnore
+    public DateTime getCreated() {
+        return created;
+    }
+
+    @JsonProperty(value = "created")
+    public void setCreated(DateTime created) {
+        this.created = created;
+    }
+
+    public String getWebsiteUrl() {
+        return websiteUrl;
+    }
+
+    public void setWebsiteUrl(String websiteUrl) {
+        this.websiteUrl = websiteUrl;
+    }
+
+    public String getIssueTrackerUrl() {
+        return issueTrackerUrl;
+    }
+
+    public void setIssueTrackerUrl(String issueTrackerUrl) {
+        this.issueTrackerUrl = issueTrackerUrl;
+    }
+
+    public String getGitHubRepo() {
+        return gitHubRepo;
+    }
+
+    public void setGitHubRepo(String gitHubRepo) {
+        this.gitHubRepo = gitHubRepo;
+    }
+
+    public String getReleaseNotesFile() {
+        return releaseNotesFile;
+    }
+
+    public void setReleaseNotesFile(String releaseNotesFile) {
+        this.releaseNotesFile = releaseNotesFile;
+    }
+
+    public Boolean getPublicDownloadNumbers() {
+        return publicDownloadNumbers;
+    }
+
+    public void setPublicDownloadNumbers(Boolean publicDownloadNumbers) {
+        this.publicDownloadNumbers = publicDownloadNumbers;
+    }
+
+    public Boolean getPublicStats() {
+        return publicStats;
+    }
+
+    public void setPublicStats(Boolean publicStats) {
+        this.publicStats = publicStats;
+    }
+
+    @JsonIgnore
+    public List<String> getLinkedRepos() {
+        return linkedRepos;
+    }
+
+    @JsonProperty("linked_to_repos")
+    public void setLinkedRepos(List<String> linkedRepos) {
+        this.linkedRepos = linkedRepos;
+    }
+
+    @JsonIgnore
+    public List<String> getVersions() {
+        return versions;
+    }
+
+    @JsonProperty("versions")
+    public void setVersions(List<String> versions) {
+        this.versions = versions;
+    }
+
+    public List<String> getLicenses() {
+        return licenses;
+    }
+
+    public void setLicenses(List<String> licenses) {
+        this.licenses = licenses;
+    }
+
+    @JsonIgnore
+    public String getLatestVersion() {
+        return latestVersion;
+    }
+
+    @JsonProperty(value = "latest_version")
+    public void setLatestVersion(String latestVersion) {
+        this.latestVersion = latestVersion;
+    }
+
+    @JsonIgnore
+    public DateTime getUpdated() {
+        return updated;
+    }
+
+    @JsonProperty(value = "updated")
+    public void setUpdated(DateTime updated) {
+        this.updated = updated;
+    }
+
+    @JsonIgnore
+    public Integer getRating() {
+        return rating;
+    }
+
+    @JsonProperty(value = "rating")
+    public void setRating(Integer rating) {
+        this.rating = rating;
+    }
+
+    @JsonIgnore
+    public Integer getRatingCount() {
+        return ratingCount;
+    }
+
+    @JsonProperty(value = "rating_count")
+    public void setRatingCount(Integer ratingCount) {
+        this.ratingCount = ratingCount;
+    }
+
+    @JsonIgnore
+    public List<String> getSystemIds() {
+        return systemIds;
+    }
+
+    @JsonProperty(value = "system_ids")
+    public void setSystemIds(List<String> systemIds) {
+        this.systemIds = systemIds;
+    }
+
+    public String getVcsUrl() {
+        return vcsUrl;
+    }
+
+    public void setVcsUrl(String vcsUrl) {
+        this.vcsUrl = vcsUrl;
+    }
+
+    @JsonIgnore
+    public List<Attribute> getAttributes() {
+        return attributes;
+    }
+
+    @JsonProperty("attributes")
+    public void setAttributes(List<Attribute> attributes) {
+        this.attributes = attributes;
+    }
+}
diff --git a/api/src/main/java/com/jfrog/bintray/client/api/details/RepositoryDetails.java b/api/src/main/java/com/jfrog/bintray/client/api/details/RepositoryDetails.java
new file mode 100644
index 0000000..aea5512
--- /dev/null
+++ b/api/src/main/java/com/jfrog/bintray/client/api/details/RepositoryDetails.java
@@ -0,0 +1,84 @@
+package com.jfrog.bintray.client.api.details;
+
+import org.codehaus.jackson.annotate.JsonIgnoreProperties;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.joda.time.DateTime;
+
+import java.util.List;
+
+/**
+ * This class is used to serialize and deserialize the needed json to
+ * pass to or receive from Bintray when performing actions on a repository
+ * NOTE: when serializing this class use getObjectMapper to obtain a suitable mapper for this class
+ *
+ * @author Dan Feldman
+ */
+ at JsonIgnoreProperties(ignoreUnknown = true)
+public class RepositoryDetails {
+
+    @JsonProperty(value = "name")
+    String name;
+    @JsonProperty(value = "owner")
+    String owner;
+    @JsonProperty(value = "desc")
+    String description;
+    @JsonProperty(value = "created")
+    DateTime created;
+    @JsonProperty(value = "labels")
+    List<String> labels;
+    @JsonProperty(value = "package_count")
+    Integer packageCount;
+
+    public static ObjectMapper getObjectMapper() {
+        return ObjectMapperHelper.objectMapper;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getOwner() {
+        return owner;
+    }
+
+    public void setOwner(String owner) {
+        this.owner = owner;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public DateTime getCreated() {
+        return created;
+    }
+
+    public void setCreated(DateTime created) {
+        this.created = created;
+    }
+
+    public List<String> getLabels() {
+        return labels;
+    }
+
+    public void setLabels(List<String> labels) {
+        this.labels = labels;
+    }
+
+    public Integer getPackageCount() {
+        return packageCount;
+    }
+
+    public void setPackageCount(Integer packageCount) {
+        this.packageCount = packageCount;
+    }
+}
diff --git a/api/src/main/java/com/jfrog/bintray/client/api/details/SubjectDetails.java b/api/src/main/java/com/jfrog/bintray/client/api/details/SubjectDetails.java
new file mode 100644
index 0000000..25eb663
--- /dev/null
+++ b/api/src/main/java/com/jfrog/bintray/client/api/details/SubjectDetails.java
@@ -0,0 +1,104 @@
+package com.jfrog.bintray.client.api.details;
+
+import org.codehaus.jackson.annotate.JsonIgnoreProperties;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.joda.time.DateTime;
+
+import java.util.List;
+
+/**
+ * This class is used to serialize and deserialize the needed json to
+ * pass to or receive from Bintray when performing actions on a subject
+ * NOTE: when serializing this class use getObjectMapper to obtain a suitable mapper for this class
+ *
+ * @author Dan Feldman
+ */
+ at JsonIgnoreProperties(ignoreUnknown = true)
+public class SubjectDetails {
+
+    @JsonProperty("name")
+    String name;
+    @JsonProperty("full_name")
+    String fullName;
+    @JsonProperty("gravatar_id")
+    String gravatarId;
+    @JsonProperty("repos")
+    List<String> repos;
+    @JsonProperty("organizations")
+    List<String> organizations;
+    @JsonProperty("followers_count")
+    Integer followersCount;
+    @JsonProperty("registered")
+    DateTime registered;
+    @JsonProperty("quota_used_bytes")
+    Long quotaUsedBytes;
+
+    public static ObjectMapper getObjectMapper() {
+        return ObjectMapperHelper.objectMapper;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getFullName() {
+        return fullName;
+    }
+
+    public void setFullName(String fullName) {
+        this.fullName = fullName;
+    }
+
+    public String getGravatarId() {
+        return gravatarId;
+    }
+
+    public void setGravatarId(String gravatarId) {
+        this.gravatarId = gravatarId;
+    }
+
+    public List<String> getRepos() {
+        return repos;
+    }
+
+    public void setRepos(List<String> repos) {
+        this.repos = repos;
+    }
+
+    public List<String> getOrganizations() {
+        return organizations;
+    }
+
+    public void setOrganizations(List<String> organizations) {
+        this.organizations = organizations;
+    }
+
+    public Integer getFollowersCount() {
+        return followersCount;
+    }
+
+    public void setFollowersCount(Integer followersCount) {
+        this.followersCount = followersCount;
+    }
+
+    public DateTime getRegistered() {
+        return registered;
+    }
+
+    public void setRegistered(DateTime registered) {
+        this.registered = registered;
+    }
+
+    public Long getQuotaUsedBytes() {
+        return quotaUsedBytes;
+    }
+
+    public void setQuotaUsedBytes(Long quotaUsedBytes) {
+        this.quotaUsedBytes = quotaUsedBytes;
+    }
+}
diff --git a/api/src/main/java/com/jfrog/bintray/client/api/details/VersionDetails.java b/api/src/main/java/com/jfrog/bintray/client/api/details/VersionDetails.java
new file mode 100644
index 0000000..758f7b0
--- /dev/null
+++ b/api/src/main/java/com/jfrog/bintray/client/api/details/VersionDetails.java
@@ -0,0 +1,245 @@
+package com.jfrog.bintray.client.api.details;
+
+import org.codehaus.jackson.annotate.JsonIgnore;
+import org.codehaus.jackson.annotate.JsonIgnoreProperties;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.joda.time.DateTime;
+
+import java.util.List;
+
+/**
+ * This class is used to serialize and deserialize the needed json to
+ * pass to or receive from Bintray when performing actions on a subject
+ * NOTE: when serializing this class use getObjectMapper to obtain a suitable mapper for this class
+ *
+ * @author Dan Feldman
+ */
+ at JsonIgnoreProperties(ignoreUnknown = true)
+public class VersionDetails {
+
+    //Properties marked with @JsonPropery here are serialized to the create \ update version requests, the rest are
+    // only deserialized when getting the version info
+    @JsonProperty("name")
+    String name;
+    @JsonProperty("desc")
+    String description;
+    @JsonIgnore
+    String pkg;
+    @JsonIgnore
+    String repo;
+    @JsonIgnore
+    String owner;
+    @JsonIgnore
+    List<String> labels;
+    @JsonIgnore
+    List<String> attributeNames;
+    @JsonIgnore
+    DateTime created;
+    @JsonIgnore
+    DateTime updated;
+    @JsonProperty("released")
+    DateTime released;
+    @JsonIgnore
+    Integer ordinal;
+    @JsonIgnore
+    List<Attribute> attributes;
+    @JsonIgnore
+    boolean gpgSign;
+    @JsonProperty(value = "github_release_notes_file")
+    private String releaseNotesFile;
+    @JsonProperty(value = "github_use_tag_release_notes")
+    private Boolean useTagReleaseNotes;
+    @JsonProperty("vcs_tag")
+    private String vcsTag;
+
+    public VersionDetails() {
+
+    }
+
+    public VersionDetails(String name) {
+        this.name = name;
+    }
+
+    public static ObjectMapper getObjectMapper() {
+        return ObjectMapperHelper.objectMapper;
+    }
+
+    public VersionDetails description(String description) {
+        this.description = description;
+        return this;
+    }
+
+    public VersionDetails released(DateTime released) {
+        this.released = released;
+        return this;
+    }
+
+    public VersionDetails releaseNotesFile(String releaseNotesFile) {
+        this.releaseNotesFile = releaseNotesFile;
+        return this;
+    }
+
+    public VersionDetails useTagReleaseNotes(Boolean useTagReleaseNotes) {
+        this.useTagReleaseNotes = useTagReleaseNotes;
+        return this;
+    }
+
+    public VersionDetails vcsTag(String vcsTag) {
+        this.vcsTag = vcsTag;
+        return this;
+    }
+
+    public VersionDetails gpgSign(boolean gpgSign) {
+        this.gpgSign = gpgSign;
+        return this;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    @JsonIgnore
+    public String getPkg() {
+        return pkg;
+    }
+
+    @JsonProperty("package")
+    public void setPkg(String pkg) {
+        this.pkg = pkg;
+    }
+
+    @JsonIgnore
+    public String getRepo() {
+        return repo;
+    }
+
+    @JsonProperty("repo")
+    public void setRepo(String repo) {
+        this.repo = repo;
+    }
+
+    @JsonIgnore
+    public String getOwner() {
+        return owner;
+    }
+
+    @JsonProperty("owner")
+    public void setOwner(String owner) {
+        this.owner = owner;
+    }
+
+    @JsonIgnore
+    public List<String> getLabels() {
+        return labels;
+    }
+
+    @JsonProperty("labels")
+    public void setLabels(List<String> labels) {
+        this.labels = labels;
+    }
+
+    @JsonIgnore
+    public List<String> getAttributeNames() {
+        return attributeNames;
+    }
+
+    @JsonProperty("attribute_names")
+    public void setAttributeNames(List<String> attributeNames) {
+        this.attributeNames = attributeNames;
+    }
+
+    @JsonIgnore
+    public DateTime getCreated() {
+        return created;
+    }
+
+    @JsonProperty("created")
+    public void setCreated(DateTime created) {
+        this.created = created;
+    }
+
+    @JsonIgnore
+    public DateTime getUpdated() {
+        return updated;
+    }
+
+    @JsonProperty("updated")
+    public void setUpdated(DateTime updated) {
+        this.updated = updated;
+    }
+
+    public DateTime getReleased() {
+        return released;
+    }
+
+    public void setReleased(DateTime released) {
+        this.released = released;
+    }
+
+    @JsonIgnore
+    public Integer getOrdinal() {
+        return ordinal;
+    }
+
+    @JsonProperty("ordinal")
+    public void setOrdinal(Integer ordinal) {
+        this.ordinal = ordinal;
+    }
+
+    public String getReleaseNotesFile() {
+        return releaseNotesFile;
+    }
+
+    public void setReleaseNotesFile(String releaseNotesFile) {
+        this.releaseNotesFile = releaseNotesFile;
+    }
+
+    public Boolean getUseTagReleaseNotes() {
+        return useTagReleaseNotes;
+    }
+
+    public void setUseTagReleaseNotes(Boolean useTagReleaseNotes) {
+        this.useTagReleaseNotes = useTagReleaseNotes;
+    }
+
+    public String getVcsTag() {
+        return vcsTag;
+    }
+
+    public void setVcsTag(String vcsTag) {
+        this.vcsTag = vcsTag;
+    }
+
+    @JsonIgnore
+    public List<Attribute> getAttributes() {
+        return attributes;
+    }
+
+    @JsonProperty("attributes")
+    public void setAttributes(List<Attribute> attributes) {
+        this.attributes = attributes;
+    }
+
+    @JsonIgnore
+    public boolean isGpgSign() {
+        return gpgSign;
+    }
+
+    @JsonProperty("gpgSign")
+    public void setGpgSign(boolean gpgSign) {
+        this.gpgSign = gpgSign;
+    }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/jfrog/bintray/client/api/handle/AttributesSearchQuery.java b/api/src/main/java/com/jfrog/bintray/client/api/handle/AttributesSearchQuery.java
new file mode 100644
index 0000000..2dd64ea
--- /dev/null
+++ b/api/src/main/java/com/jfrog/bintray/client/api/handle/AttributesSearchQuery.java
@@ -0,0 +1,9 @@
+package com.jfrog.bintray.client.api.handle;
+
+/**
+ * @author jbaruch
+ * @since 13/11/13
+ */
+public interface AttributesSearchQuery {
+    AttributesSearchQueryClause byAttributeName(String name);
+}
diff --git a/api/src/main/java/com/jfrog/bintray/client/api/handle/AttributesSearchQueryClause.java b/api/src/main/java/com/jfrog/bintray/client/api/handle/AttributesSearchQueryClause.java
new file mode 100644
index 0000000..57301df
--- /dev/null
+++ b/api/src/main/java/com/jfrog/bintray/client/api/handle/AttributesSearchQueryClause.java
@@ -0,0 +1,43 @@
+package com.jfrog.bintray.client.api.handle;
+
+import com.jfrog.bintray.client.api.model.Pkg;
+import com.jfrog.bintray.client.api.model.Version;
+import org.joda.time.DateTime;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * @author jbaruch
+ * @since 13/11/13
+ */
+public interface AttributesSearchQueryClause {
+
+    AttributesSearchQueryClause in(String... values);
+
+    AttributesSearchQueryClause equalsVal(Object value);
+
+    AttributesSearchQueryClause greaterThan(int value);
+
+    AttributesSearchQueryClause greaterOrEqualsTo(int value);
+
+    AttributesSearchQueryClause lessThan(int value);
+
+    AttributesSearchQueryClause lessOrEquals(int value);
+
+    AttributesSearchQueryClause before(DateTime value);
+
+    AttributesSearchQueryClause beforeOrAt(DateTime value);
+
+    AttributesSearchQueryClause at(DateTime value);
+
+    AttributesSearchQueryClause after(DateTime value);
+
+    AttributesSearchQueryClause afterOrAt(DateTime value);
+
+    AttributesSearchQuery and();
+
+    List<Pkg> searchPackage() throws IOException;
+
+    List<Version> searchVersion() throws IOException;
+}
diff --git a/api/src/main/java/com/jfrog/bintray/client/api/handle/Bintray.java b/api/src/main/java/com/jfrog/bintray/client/api/handle/Bintray.java
new file mode 100644
index 0000000..0b39666
--- /dev/null
+++ b/api/src/main/java/com/jfrog/bintray/client/api/handle/Bintray.java
@@ -0,0 +1,55 @@
+package com.jfrog.bintray.client.api.handle;
+
+import com.jfrog.bintray.client.api.BintrayCallException;
+import com.jfrog.bintray.client.api.MultipleBintrayCallException;
+import org.apache.http.HttpResponse;
+
+import java.io.Closeable;
+import java.io.InputStream;
+import java.util.Map;
+
+/**
+ * @author Noam Y. Tenne
+ */
+public interface Bintray extends Closeable {
+
+    SubjectHandle subject(String subject);
+
+    RepositoryHandle repository(String repositoryPath);
+
+    PackageHandle pkg(String packagePath);
+
+    VersionHandle version(String versionPath);
+
+    /**
+     * Following are generic HTTP requests you can perform against Bintray's API, as defined in your created client
+     * (so the uri parameter should be the required path after api.bintray.com).
+     * You can also optionally add your own headers to the requests.
+     */
+
+    HttpResponse get(String uri, Map<String, String> headers) throws BintrayCallException;
+
+    HttpResponse head(String uri, Map<String, String> headers) throws BintrayCallException;
+
+    HttpResponse post(String uri, Map<String, String> headers, InputStream elementInputStream) throws BintrayCallException;
+
+    HttpResponse patch(String uri, Map<String, String> headers, InputStream elementInputStream) throws BintrayCallException;
+
+    HttpResponse delete(String uri, Map<String, String> headers) throws BintrayCallException;
+
+    /**
+     * PUT single item
+     */
+    HttpResponse put(String uri, Map<String, String> headers, InputStream elementInputStream) throws BintrayCallException;
+
+    /**
+     * Concurrently executes a list of {@link org.apache.http.client.methods.HttpPut} requests, which are not handled by
+     * the default response handler to avoid any BintrayCallExceptions being thrown before all requests have executed.
+     *
+     * @return A list of all errors thrown while performing the requests or empty list if all requests finished OK
+     */
+    HttpResponse put(Map<String, InputStream> uriAndStreamMap, Map<String, String> headers) throws MultipleBintrayCallException;
+
+    @Override
+    void close();
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/jfrog/bintray/client/api/handle/Handle.java b/api/src/main/java/com/jfrog/bintray/client/api/handle/Handle.java
new file mode 100644
index 0000000..9bd6031
--- /dev/null
+++ b/api/src/main/java/com/jfrog/bintray/client/api/handle/Handle.java
@@ -0,0 +1,13 @@
+package com.jfrog.bintray.client.api.handle;
+
+import java.io.IOException;
+
+/**
+ * @author Noam Y. Tenne
+ */
+public interface Handle<T> {
+
+    String name();
+
+    T get() throws IOException;
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/jfrog/bintray/client/api/handle/PackageHandle.java b/api/src/main/java/com/jfrog/bintray/client/api/handle/PackageHandle.java
new file mode 100644
index 0000000..3ffa8a8
--- /dev/null
+++ b/api/src/main/java/com/jfrog/bintray/client/api/handle/PackageHandle.java
@@ -0,0 +1,42 @@
+package com.jfrog.bintray.client.api.handle;
+
+import com.jfrog.bintray.client.api.BintrayCallException;
+import com.jfrog.bintray.client.api.details.Attribute;
+import com.jfrog.bintray.client.api.details.PackageDetails;
+import com.jfrog.bintray.client.api.details.VersionDetails;
+import com.jfrog.bintray.client.api.model.Pkg;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * @author Noam Y. Tenne
+ */
+public interface PackageHandle extends Handle<Pkg> {
+
+    RepositoryHandle repository();
+
+    VersionHandle version(String versionName);
+
+    Pkg get() throws IOException, BintrayCallException;
+
+    PackageHandle update(PackageDetails packageDetails) throws IOException, BintrayCallException;
+
+    PackageHandle delete() throws BintrayCallException;
+
+    boolean exists() throws BintrayCallException;
+
+    VersionHandle createVersion(VersionDetails versionDetails) throws IOException, BintrayCallException;
+
+    PackageHandle setAttributes(PackageDetails packageDetails) throws IOException, BintrayCallException;
+
+    PackageHandle setAttributes(List<Attribute> attributes) throws IOException, BintrayCallException;
+
+    PackageHandle updateAttributes(PackageDetails packageDetails) throws IOException, BintrayCallException;
+
+    PackageHandle updateAttributes(List<Attribute> attributes) throws IOException, BintrayCallException;
+
+    String name();
+
+    String getCurrentPackageUri();
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/jfrog/bintray/client/api/handle/RepositoryHandle.java b/api/src/main/java/com/jfrog/bintray/client/api/handle/RepositoryHandle.java
new file mode 100644
index 0000000..94a6cb1
--- /dev/null
+++ b/api/src/main/java/com/jfrog/bintray/client/api/handle/RepositoryHandle.java
@@ -0,0 +1,29 @@
+package com.jfrog.bintray.client.api.handle;
+
+import com.jfrog.bintray.client.api.BintrayCallException;
+import com.jfrog.bintray.client.api.details.PackageDetails;
+import com.jfrog.bintray.client.api.model.Repository;
+
+import java.io.IOException;
+
+/**
+ * @author Noam Y. Tenne
+ */
+public interface RepositoryHandle extends Handle<Repository> {
+
+    SubjectHandle owner();
+
+    Repository get() throws IOException, BintrayCallException;
+
+    PackageHandle pkg(String name);
+
+    PackageHandle createPkg(PackageDetails packageDetails) throws IOException, BintrayCallException;
+
+    String name();
+
+    AttributesSearchQuery searchForPackage();
+
+    boolean exists() throws BintrayCallException;
+
+    String getRepositoryUri();
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/jfrog/bintray/client/api/handle/SubjectHandle.java b/api/src/main/java/com/jfrog/bintray/client/api/handle/SubjectHandle.java
new file mode 100644
index 0000000..f84f7eb
--- /dev/null
+++ b/api/src/main/java/com/jfrog/bintray/client/api/handle/SubjectHandle.java
@@ -0,0 +1,18 @@
+package com.jfrog.bintray.client.api.handle;
+
+import com.jfrog.bintray.client.api.BintrayCallException;
+import com.jfrog.bintray.client.api.model.Subject;
+
+import java.io.IOException;
+
+/**
+ * @author Noam Y. Tenne
+ */
+public interface SubjectHandle extends Handle<Subject> {
+
+    String name();
+
+    Subject get() throws IOException, BintrayCallException;
+
+    RepositoryHandle repository(String name);
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/jfrog/bintray/client/api/handle/VersionHandle.java b/api/src/main/java/com/jfrog/bintray/client/api/handle/VersionHandle.java
new file mode 100644
index 0000000..b65bbbb
--- /dev/null
+++ b/api/src/main/java/com/jfrog/bintray/client/api/handle/VersionHandle.java
@@ -0,0 +1,50 @@
+package com.jfrog.bintray.client.api.handle;
+
+import com.jfrog.bintray.client.api.BintrayCallException;
+import com.jfrog.bintray.client.api.MultipleBintrayCallException;
+import com.jfrog.bintray.client.api.details.Attribute;
+import com.jfrog.bintray.client.api.details.VersionDetails;
+import com.jfrog.bintray.client.api.model.Version;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author Noam Y. Tenne
+ */
+public interface VersionHandle extends Handle<Version> {
+
+    PackageHandle pkg();
+
+    Version get() throws IOException, BintrayCallException;
+
+    VersionHandle update(VersionDetails versionDetails) throws IOException, BintrayCallException;
+
+    VersionHandle delete() throws BintrayCallException;
+
+    boolean exists() throws BintrayCallException;
+
+    VersionHandle setAttributes(VersionDetails versionDetails) throws IOException, BintrayCallException;
+
+    VersionHandle setAttributes(List<Attribute> attributes) throws IOException, BintrayCallException;
+
+    VersionHandle updateAttributes(VersionDetails versionDetails) throws IOException, BintrayCallException;
+
+    VersionHandle updateAttributes(List<Attribute> attributes) throws IOException, BintrayCallException;
+
+    VersionHandle upload(Map<String, InputStream> content) throws MultipleBintrayCallException;
+
+    VersionHandle upload(String path, InputStream content) throws BintrayCallException;
+
+    VersionHandle publish() throws BintrayCallException;
+
+    VersionHandle discard() throws BintrayCallException;
+
+    VersionHandle sign(String passphrase, int fileCount) throws BintrayCallException;
+
+    VersionHandle sign(int fileCount) throws BintrayCallException;
+
+    String getVersionUri();
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/jfrog/bintray/client/api/model/Pkg.java b/api/src/main/java/com/jfrog/bintray/client/api/model/Pkg.java
new file mode 100644
index 0000000..356805e
--- /dev/null
+++ b/api/src/main/java/com/jfrog/bintray/client/api/model/Pkg.java
@@ -0,0 +1,41 @@
+package com.jfrog.bintray.client.api.model;
+
+import org.joda.time.DateTime;
+
+import java.util.List;
+
+/**
+ * @author Noam Y. Tenne
+ */
+public interface Pkg {
+
+    String name();
+
+    String repository();
+
+    String owner();
+
+    String description();
+
+    List<String> labels();
+
+    List<String> attributeNames();
+
+    Integer rating();
+
+    Integer ratingCount();
+
+    Integer followersCount();
+
+    DateTime created();
+
+    List<String> versions();
+
+    String latestVersion();
+
+    DateTime updated();
+
+    List<String> linkedToRepos();
+
+    List<String> systemIds();
+}
diff --git a/api/src/main/java/com/jfrog/bintray/client/api/model/Repository.java b/api/src/main/java/com/jfrog/bintray/client/api/model/Repository.java
new file mode 100644
index 0000000..bf692ec
--- /dev/null
+++ b/api/src/main/java/com/jfrog/bintray/client/api/model/Repository.java
@@ -0,0 +1,23 @@
+package com.jfrog.bintray.client.api.model;
+
+import org.joda.time.DateTime;
+
+import java.util.List;
+
+/**
+ * @author Noam Y. Tenne
+ */
+public interface Repository {
+
+    String getName();
+
+    String getOwner();
+
+    String getDesc();
+
+    List<String> getLabels();
+
+    DateTime getCreated();
+
+    Integer getPackageCount();
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/jfrog/bintray/client/api/model/Subject.java b/api/src/main/java/com/jfrog/bintray/client/api/model/Subject.java
new file mode 100644
index 0000000..d4cb893
--- /dev/null
+++ b/api/src/main/java/com/jfrog/bintray/client/api/model/Subject.java
@@ -0,0 +1,27 @@
+package com.jfrog.bintray.client.api.model;
+
+import org.joda.time.DateTime;
+
+import java.util.Collection;
+
+/**
+ * @author Noam Y. Tenne
+ */
+public interface Subject {
+
+    String getName();
+
+    String getFullName();
+
+    String getGravatarId();
+
+    Collection<String> getRepositories();
+
+    Collection<String> getOrganizations();
+
+    Integer getFollowersCount();
+
+    DateTime getRegistered();
+
+    Long getQuotaUsedBytes();
+}
diff --git a/api/src/main/java/com/jfrog/bintray/client/api/model/Version.java b/api/src/main/java/com/jfrog/bintray/client/api/model/Version.java
new file mode 100644
index 0000000..d9239fc
--- /dev/null
+++ b/api/src/main/java/com/jfrog/bintray/client/api/model/Version.java
@@ -0,0 +1,35 @@
+package com.jfrog.bintray.client.api.model;
+
+import org.joda.time.DateTime;
+
+import java.util.List;
+
+/**
+ * @author Noam Y. Tenne
+ */
+public interface Version {
+
+    String name();
+
+    String description();
+
+    String pkg();
+
+    String repository();
+
+    String owner();
+
+    List<String> labels();
+
+    List<String> attributeNames();
+
+    DateTime created();
+
+    DateTime updated();
+
+    DateTime released();
+
+    Integer ordinal();
+
+    String vcsTag();
+}
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..848801a
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,195 @@
+import static java.lang.System.getenv
+
+buildscript {
+    repositories {
+        jcenter()
+    }
+
+    dependencies {
+        classpath(group: 'org.jfrog.buildinfo', name: 'build-info-extractor-gradle', version: '3.0.3')
+    }
+}
+
+allprojects {
+    apply plugin: 'idea'
+    apply plugin: 'maven-publish'
+    apply plugin: 'com.jfrog.artifactory'
+
+    group = 'com.jfrog.bintray.client'
+
+    artifactory {
+        contextUrl = 'https://oss.jfrog.org'
+        resolve {
+            repository {
+                repoKey = 'libs-release'
+            }
+        }
+
+        publish {
+            repository {
+                repoKey = 'oss-release-local'   //The Artifactory repository key to publish to
+                username = project.hasProperty('bintrayUser') ? project.bintrayUser : getenv()['BINTRAY_USER']
+                password = project.hasProperty('bintrayKey') ? project.bintrayKey : getenv()['BINTRAY_KEY']
+            }
+            defaults {
+                publications 'main'
+                properties = ['bintray.repo': 'jfrog/bintray-tools', 'bintray.package': 'bintray-client-java', 'bintray.version': version.toString()]
+            }
+        }
+    }
+}
+
+artifactoryPublish.skip = true
+
+subprojects() {
+    apply plugin: 'java'
+    sourceCompatibility = 1.7
+    targetCompatibility = 1.7
+
+    dependencies {
+        compile addSlf4J('slf4j-api')
+        compile addSlf4J('log4j-over-slf4j')
+        compile addSlf4J('jcl-over-slf4j')
+        compile 'ch.qos.logback:logback-classic:1.0.10'
+        compile 'joda-time:joda-time:2.2'
+        compile 'commons-io:commons-io:2.1'
+        compile 'org.codehaus.jackson:jackson-mapper-asl:1.9.5'
+        compile("org.apache.httpcomponents:httpclient:4.3.5") {
+            exclude group: 'commons-logging' //DO NOT REMOVE
+        }
+    }
+
+    task sourcesJar(type: Jar) {
+        from sourceSets.main.allSource
+        classifier = 'sources'
+    }
+
+    task javadocJar(type: Jar, dependsOn: javadoc) {
+        classifier = 'javadoc'
+        from javadoc.destinationDir
+    }
+
+    task testResultsZip(type: Zip) {
+        classifier = 'testreports'
+        from testReportDir
+
+    }
+
+    test {
+        jvmArgs (['-Dorg.apache.commons.logging.Log=org.apache.commons.logging.impl.SimpleLog',
+                  '-Dorg.apache.commons.logging.simplelog.showdatetime=true',
+                  '-Dorg.apache.commons.logging.simplelog.log.org.apache.http=ERROR',
+                  '-Dorg.apache.commons.logging.simplelog.log.org.apache.http.wire=ERROR'])
+        testLogging {
+            exceptionFormat "full"
+            events "started", "passed", "skipped", "failed", "standardOut", "standardError"
+            minGranularity 0
+        }
+
+        finalizedBy(testResultsZip)
+    }
+
+
+    publishing {
+        publications {
+            main(MavenPublication) {
+                from components.java
+                artifact sourcesJar
+                artifact javadocJar
+                if (testResultsZip.archivePath.exists()) {
+                    artifact testResultsZip
+                }
+
+                pom.withXml {
+                    asNode().with {
+                        appendNode('packaging', 'jar')
+                        appendNode('name', 'Bintray Java client')
+                        appendNode('description', 'Java client for working with Bintray')
+                        appendNode('url', 'https://github.com/bintray/bintray-client-java')
+                        appendNode('licenses').with {
+                            appendNode('license').with {
+                                appendNode('name', 'The Apache Software License, Version 2.0')
+                                appendNode('url', 'http://www.apache.org/licenses/LICENSE-2.0')
+                            }
+                        }
+                        appendNode('developers').with {
+                            appendNode('developer').with {
+                                appendNode('id', 'NoamTenne')
+                                appendNode('name', 'Noam Tenne')
+                                appendNode('email', 'NoamT at jfrog.com')
+                            }
+                            appendNode('developer').with {
+                                appendNode('id', 'jbaruch')
+                                appendNode('name', 'Baruch Sadogursky')
+                                appendNode('email', 'jbaruch at jfrog.com')
+                            }
+                            appendNode('developer').with {
+                                appendNode('id', 'danf')
+                                appendNode('name', 'Dan Feldman')
+                                appendNode('email', 'danf at jfrog.com')
+                            }
+                        }
+                        appendNode('scm').with {
+                            appendNode('connection', 'git at github.com:JFrogDev/bintray/bintray-client-java.git')
+                            appendNode('developerConnection', 'git at github.com:bintray/bintray-client-java.git')
+                            appendNode('url', 'https://github.com/bintray/bintray-client-java')
+                        }
+                    }
+                    asNode().dependencies.'*'.findAll() {
+                        it.scope.text() == 'runtime' && project.configurations.compile.allDependencies.find { dep ->
+                            dep.name == it.artifactId.text()
+                        }
+                    }.each() {
+                        it.scope*.value = 'compile'
+                    }
+                }
+            }
+        }
+    }
+}
+
+project(':bintray-client-java-service') {
+    dependencies {
+        compile project(':bintray-client-java-api')
+        compile 'org.codehaus.groovy:groovy:1.8.0'
+        compile 'commons-lang:commons-lang:2.6'
+        testCompile group: 'com.timgroup', name: 'jgravatar', version: '1.0'
+        testCompile("org.spockframework:spock-core:0.7-groovy-1.8") {
+            exclude module: 'groovy-all'
+        }
+    }
+}
+
+project(':bintray-client-java-api') {
+    apply plugin: 'java'
+    sourceCompatibility = 1.7
+    targetCompatibility = 1.7
+}
+
+project(':bintray-client-java-service') {
+    apply plugin: 'groovy'
+}
+
+def addSlf4J(name) {
+    [group: 'org.slf4j', name: name, version: '1.7.5']
+}
+
+task wrapper(type: Wrapper) {
+    gradleVersion = '2.1'
+}
+
+idea {
+    project {
+        jdkName = '1.7'
+        languageLevel = '1.7'
+        wildcards += '?*.gradle'
+        ipr {
+            withXml { provider ->
+                def node = provider.asNode()
+                // Use git
+                def vcsConfig = node.component.find { it.'@name' == 'VcsDirectoryMappings' }
+                vcsConfig.mapping[0].'@vcs' = 'Git'
+            }
+        }
+    }
+}
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..6ded910
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1 @@
+version=0.8.1
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..b761216
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..042fbb3
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Tue Aug 12 10:28:06 IDT 2014
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.1-all.zip
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..91a7e26
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+    echo "$*"
+}
+
+die ( ) {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+    [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+    JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..8a0b282
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,90 @@
+ at if "%DEBUG%" == "" @echo off
+ at rem ##########################################################################
+ at rem
+ at rem  Gradle startup script for Windows
+ at rem
+ at rem ##########################################################################
+
+ at rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+ at rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+ at rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+ at rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+ at rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+ at rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+ at rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+ at rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+ at rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/impl/src/main/java/com/jfrog/bintray/client/impl/BintrayClient.java b/impl/src/main/java/com/jfrog/bintray/client/impl/BintrayClient.java
new file mode 100644
index 0000000..bd51490
--- /dev/null
+++ b/impl/src/main/java/com/jfrog/bintray/client/impl/BintrayClient.java
@@ -0,0 +1,80 @@
+package com.jfrog.bintray.client.impl;
+
+import com.jfrog.bintray.client.api.handle.Bintray;
+import com.jfrog.bintray.client.impl.handle.BintrayImpl;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.impl.client.CloseableHttpClient;
+
+/**
+ * Creates a client to perform api actions with, can be configured with\without proxy (can be passed as null)
+ * By default, https://api.bintray.com is used, unless specified otherwise (i.e. can be configured to work
+ * with https://dl.bintray.com/).
+ *
+ * @author Dan Feldman
+ */
+public class BintrayClient {
+
+    public static final int DEFAULT_TIMEOUT = 150000;
+    public static final String BINTRAY_API_URL = "https://api.bintray.com";
+    public static final String USER_AGENT = "BintrayJavaClient/0.5"; // TODO: make dynamic
+    private static final int DEFAULT_THREAD_POOL_SIZE = 5; //Don't mess with this - its here for a reason
+    private static final int DEFAULT_SIGN_REQUEST_TIMEOUT_PER_FILE = 90000; //1.5 min per file
+
+    //Mainly used by Artifactory to avoid all of the configuration, but you can specify your own too
+    static public Bintray create(CloseableHttpClient preConfiguredClient, String url, int threadPoolSize,
+                                 int signRequestTimeoutPerFile) {
+        return new BintrayImpl(preConfiguredClient, url, threadPoolSize, signRequestTimeoutPerFile);
+    }
+
+    /**
+     * Username and API key, no proxy
+     */
+    static public Bintray create(String userName, String apiKey) {
+        UsernamePasswordCredentials creds = new UsernamePasswordCredentials(userName, apiKey);
+        return new BintrayImpl(createClient(creds, null, BINTRAY_API_URL), BINTRAY_API_URL, DEFAULT_THREAD_POOL_SIZE,
+                DEFAULT_SIGN_REQUEST_TIMEOUT_PER_FILE);
+    }
+
+    /**
+     * Username, API key, and custom url
+     */
+    static public Bintray create(String url, String userName, String apiKey) {
+        UsernamePasswordCredentials creds = new UsernamePasswordCredentials(userName, apiKey);
+        return new BintrayImpl(createClient(creds, null, url), url, DEFAULT_THREAD_POOL_SIZE,
+                DEFAULT_SIGN_REQUEST_TIMEOUT_PER_FILE);
+    }
+
+    /**
+     * Credentials with proxy
+     */
+    static public Bintray create(UsernamePasswordCredentials creds, HttpClientConfigurator.ProxyConfig proxyConfig) {
+        return new BintrayImpl(createClient(creds, proxyConfig, BINTRAY_API_URL), BINTRAY_API_URL,
+                DEFAULT_THREAD_POOL_SIZE, DEFAULT_SIGN_REQUEST_TIMEOUT_PER_FILE);
+    }
+
+    /**
+     * Username, API key, proxy and custom url
+     */
+    static public Bintray create(String bintrayUserName, String bintrayApiKey,
+                                 HttpClientConfigurator.ProxyConfig proxyConfig, String url) {
+        UsernamePasswordCredentials creds = new UsernamePasswordCredentials(bintrayUserName, bintrayApiKey);
+        return new BintrayImpl(createClient(creds, proxyConfig, url), url, DEFAULT_THREAD_POOL_SIZE,
+                DEFAULT_SIGN_REQUEST_TIMEOUT_PER_FILE);
+    }
+
+
+    private static CloseableHttpClient createClient(UsernamePasswordCredentials creds,
+                                                    HttpClientConfigurator.ProxyConfig proxyConfig, String url) {
+
+        return new HttpClientConfigurator()
+                .hostFromUrl(url)
+                .soTimeout(DEFAULT_TIMEOUT)
+                .connectionTimeout(DEFAULT_TIMEOUT)
+                .noRetry()
+                .proxy(proxyConfig)
+                .authentication(creds)
+                .maxTotalConnections(50)
+                .defaultMaxConnectionsPerHost(30)
+                .getClient();
+    }
+}
diff --git a/impl/src/main/java/com/jfrog/bintray/client/impl/HttpClientConfigurator.java b/impl/src/main/java/com/jfrog/bintray/client/impl/HttpClientConfigurator.java
new file mode 100644
index 0000000..0b67430
--- /dev/null
+++ b/impl/src/main/java/com/jfrog/bintray/client/impl/HttpClientConfigurator.java
@@ -0,0 +1,407 @@
+/*
+ * Artifactory is a binaries repository manager.
+ * Copyright (C) 2014 JFrog Ltd.
+ *
+ * Artifactory is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Artifactory is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Artifactory.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+package com.jfrog.bintray.client.impl;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.http.HttpException;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpRequestInterceptor;
+import org.apache.http.auth.*;
+import org.apache.http.client.CredentialsProvider;
+import org.apache.http.client.config.AuthSchemes;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.protocol.HttpClientContext;
+import org.apache.http.conn.routing.HttpRoute;
+import org.apache.http.conn.routing.RouteInfo;
+import org.apache.http.impl.auth.BasicScheme;
+import org.apache.http.impl.client.*;
+import org.apache.http.impl.conn.DefaultRoutePlanner;
+import org.apache.http.impl.conn.DefaultSchemePortResolver;
+import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
+import org.apache.http.protocol.HttpContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.UnknownHostException;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Builder for HTTP client.
+ *
+ * @author Yossi Shaul
+ */
+ at SuppressWarnings("deprecation")
+public class HttpClientConfigurator {
+    private static final Logger log = LoggerFactory.getLogger(HttpClientConfigurator.class);
+
+    private HttpClientBuilder builder = HttpClients.custom();
+    private RequestConfig.Builder config = RequestConfig.custom();
+    private String host;
+    private BasicCredentialsProvider credsProvider;
+    private int maxConnectionsPerRoute = 30; //Default
+    private int maxTotalConnections = 50;   //Default
+
+    public HttpClientConfigurator() {
+        builder.setUserAgent(BintrayClient.USER_AGENT);
+        credsProvider = new BasicCredentialsProvider();
+    }
+
+    public CloseableHttpClient getClient() {
+        if (hasCredentials()) {
+            builder.setDefaultCredentialsProvider(credsProvider);
+        }
+        PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();   //Threadsafe
+        cm.setDefaultMaxPerRoute(maxConnectionsPerRoute);
+        cm.setMaxTotal(maxTotalConnections);
+        builder.setConnectionManager(cm);
+
+        return builder.setDefaultRequestConfig(config.build()).build();
+    }
+
+    /**
+     * May throw a runtime exception when the given URL is invalid.
+     */
+    public HttpClientConfigurator hostFromUrl(String urlStr) throws IllegalArgumentException {
+        if (StringUtils.isNotBlank(urlStr)) {
+            try {
+                URL url = new URL(urlStr);
+                host(url.getHost());
+            } catch (MalformedURLException e) {
+                throw new IllegalArgumentException("Cannot parse the url " + urlStr, e);
+            }
+        }
+        return this;
+    }
+
+    /**
+     * Ignores blank getValues
+     */
+    public HttpClientConfigurator host(String host) {
+        if (StringUtils.isNotBlank(host)) {
+            this.host = host;
+            builder.setRoutePlanner(new DefaultHostRoutePlanner(host));
+        }
+        return this;
+    }
+
+    public HttpClientConfigurator defaultMaxConnectionsPerHost(int maxConnectionsPerHost) {
+        //Actually this is overridden by the ConnectionManager's configuration (set by maxConnectionsPerRoute), test if it can be removed
+        builder.setMaxConnPerRoute(maxConnectionsPerHost);
+        this.maxConnectionsPerRoute = maxConnectionsPerHost;
+        return this;
+    }
+
+    public HttpClientConfigurator maxTotalConnections(int maxTotalConnections) {
+        //Actually this is overridden by the ConnectionManager's configuration (set by maxTotalConnections), test if it can be removed
+        builder.setMaxConnTotal(maxTotalConnections);
+        this.maxTotalConnections = maxTotalConnections;
+        return this;
+    }
+
+    public HttpClientConfigurator connectionTimeout(int connectionTimeout) {
+        config.setConnectTimeout(connectionTimeout);
+        return this;
+    }
+
+    public HttpClientConfigurator soTimeout(int soTimeout) {
+        config.setSocketTimeout(soTimeout);
+        return this;
+    }
+
+    public HttpClientConfigurator noCookies() {
+        builder.disableCookieManagement();
+        return this;
+    }
+
+    /**
+     * see {@link org.apache.http.client.config.RequestConfig#isStaleConnectionCheckEnabled()}
+     */
+    public HttpClientConfigurator staleCheckingEnabled(boolean staleCheckingEnabled) {
+        config.setStaleConnectionCheckEnabled(staleCheckingEnabled);
+        return this;
+    }
+
+    /**
+     * Disable request retries on service unavailability.
+     */
+    public HttpClientConfigurator noRetry() {
+        return retry(0, false);
+    }
+
+    /**
+     * Number of retry attempts. Default is 3 retries.
+     *
+     * @param retryCount Number of retry attempts. 0 means no retries.
+     */
+    public HttpClientConfigurator retry(int retryCount, boolean requestSentRetryEnabled) {
+        if (retryCount == 0) {
+            builder.disableAutomaticRetries();
+        } else {
+            builder.setRetryHandler(new DefaultHttpRequestRetryHandler(retryCount, requestSentRetryEnabled));
+        }
+        return this;
+    }
+
+    /**
+     * Ignores blank or invalid input
+     */
+    public HttpClientConfigurator localAddress(String localAddress) {
+        if (StringUtils.isNotBlank(localAddress)) {
+            try {
+                InetAddress address = InetAddress.getByName(localAddress);
+                config.setLocalAddress(address);
+            } catch (UnknownHostException e) {
+                throw new IllegalArgumentException("Invalid local address: " + localAddress, e);
+            }
+        }
+        return this;
+    }
+
+    /**
+     * Ignores null credentials
+     */
+    public HttpClientConfigurator authentication(UsernamePasswordCredentials creds) {
+        if (creds != null) {
+            authentication(creds.getUserName(), creds.getPassword());
+        }
+
+        return this;
+    }
+
+    /**
+     * Ignores blank username input
+     */
+    public HttpClientConfigurator authentication(String username, String password) {
+        if (StringUtils.isNotBlank(username)) {
+            if (StringUtils.isBlank(host)) {
+                throw new IllegalStateException("Cannot configure authentication when host is not set.");
+            }
+            credsProvider.setCredentials(
+                    new AuthScope(host, AuthScope.ANY_PORT, AuthScope.ANY_REALM),
+                    new UsernamePasswordCredentials(username, password));
+
+            builder.addInterceptorFirst(new PreemptiveAuthInterceptor());
+        }
+        return this;
+    }
+
+    public HttpClientConfigurator proxy(ProxyConfig proxyConfig) {
+        configureProxy(proxyConfig);
+        return this;
+    }
+
+    private void configureProxy(ProxyConfig proxy) {
+        if (proxy != null) {
+            config.setProxy(new HttpHost(proxy.getHost(), proxy.getPort()));
+            if (proxy.getUserName() != null) {
+                Credentials creds = null;
+                if (proxy.getNtDomain() == null) {
+                    creds = new UsernamePasswordCredentials(proxy.getUserName(), proxy.getPassword());
+                    List<String> authPrefs = Arrays.asList(AuthSchemes.DIGEST, AuthSchemes.BASIC, AuthSchemes.NTLM);
+                    config.setProxyPreferredAuthSchemes(authPrefs);
+
+                    // preemptive proxy authentication
+                    builder.addInterceptorFirst(new ProxyPreemptiveAuthInterceptor());
+                } else {
+                    try {
+                        String ntHost =
+                                StringUtils.isBlank(proxy.getNtHost()) ? InetAddress.getLocalHost().getHostName() :
+                                        proxy.getNtHost();
+                        creds = new NTCredentials(proxy.getUserName(), proxy.getPassword(), ntHost, proxy.getNtDomain());
+                    } catch (UnknownHostException e) {
+                        log.error("Failed to determine required local hostname for NTLM credentials.", e);
+                    }
+                }
+                if (creds != null) {
+                    credsProvider.setCredentials(
+                            new AuthScope(proxy.getHost(), proxy.getPort(), AuthScope.ANY_REALM), creds);
+                    if (proxy.getRedirectToHosts() != null) {
+                        for (String hostName : proxy.getRedirectToHosts()) {
+                            credsProvider.setCredentials(
+                                    new AuthScope(hostName, AuthScope.ANY_PORT, AuthScope.ANY_REALM), creds);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private boolean hasCredentials() {
+        return credsProvider.getCredentials(AuthScope.ANY) != null;
+    }
+
+
+    static class DefaultHostRoutePlanner extends DefaultRoutePlanner {
+        private final HttpHost defaultHost;
+
+        public DefaultHostRoutePlanner(String defaultHost) {
+            super(DefaultSchemePortResolver.INSTANCE);
+            this.defaultHost = new HttpHost(defaultHost);
+        }
+
+        @Override
+        public HttpRoute determineRoute(HttpHost host, HttpRequest request, HttpContext context) throws HttpException {
+            if (host == null) {
+                host = defaultHost;
+            }
+            return super.determineRoute(host, request, context);
+        }
+
+        public HttpHost getDefaultHost() {
+            return defaultHost;
+        }
+    }
+
+    static class PreemptiveAuthInterceptor implements HttpRequestInterceptor {
+
+        @Override
+        public void process(HttpRequest request, HttpContext context) throws HttpException, IOException {
+            HttpClientContext clientContext = HttpClientContext.adapt(context);
+            AuthState authState = clientContext.getTargetAuthState();
+
+            // If there's no auth scheme available yet, try to initialize it preemptively
+            if (authState.getAuthScheme() == null) {
+                CredentialsProvider credsProvider = clientContext.getCredentialsProvider();
+                HttpHost targetHost = clientContext.getTargetHost();
+                Credentials creds = credsProvider.getCredentials(
+                        new AuthScope(targetHost.getHostName(), targetHost.getPort()));
+                if (creds == null) {
+                    throw new HttpException("No credentials for preemptive authentication");
+                }
+                authState.update(new BasicScheme(), creds);
+            }
+        }
+    }
+
+    static class ProxyPreemptiveAuthInterceptor implements HttpRequestInterceptor {
+
+        @Override
+        public void process(HttpRequest request, HttpContext context) throws HttpException, IOException {
+            HttpClientContext clientContext = HttpClientContext.adapt(context);
+            AuthState proxyAuthState = clientContext.getProxyAuthState();
+
+            // If there's no auth scheme available yet, try to initialize it preemptively
+            if (proxyAuthState.getAuthScheme() == null) {
+                CredentialsProvider credsProvider = clientContext.getCredentialsProvider();
+                RouteInfo route = clientContext.getHttpRoute();
+                if (route == null) {
+                    if (log.isDebugEnabled()) {
+                        log.debug("No route found for {}", clientContext.getTargetHost());
+                    }
+                    return;
+                }
+
+                HttpHost proxyHost = route.getProxyHost();
+                if (proxyHost == null) {
+                    log.warn("No proxy host found in route {} for host {}", route, clientContext.getTargetHost());
+                    return;
+                }
+
+                Credentials creds = credsProvider.getCredentials(
+                        new AuthScope(proxyHost.getHostName(), proxyHost.getPort()));
+                if (creds == null) {
+                    log.info("No credentials found for proxy: " + proxyHost);
+                    return;
+                }
+                proxyAuthState.update(new BasicScheme(ChallengeState.PROXY), creds);
+            }
+        }
+    }
+
+
+    public static class ProxyConfig {
+        String host;
+        int port;
+        String userName;
+        String password;
+        String ntHost;
+        String ntDomain;
+        List<String> redirectToHosts;
+
+        public String getHost() {
+            return host;
+        }
+
+        public ProxyConfig host(String host) {
+            this.host = host;
+            return this;
+        }
+
+        public int getPort() {
+            return port;
+        }
+
+        public ProxyConfig port(int port) {
+            this.port = port;
+            return this;
+        }
+
+        public String getUserName() {
+            return userName;
+        }
+
+        public ProxyConfig userName(String userName) {
+            this.userName = userName;
+            return this;
+        }
+
+        public String getPassword() {
+            return password;
+        }
+
+        public ProxyConfig password(String password) {
+            this.password = password;
+            return this;
+        }
+
+        public String getNtHost() {
+            return ntHost;
+        }
+
+        public ProxyConfig ntHost(String ntHost) {
+            this.ntHost = ntHost;
+            return this;
+        }
+
+        public String getNtDomain() {
+            return ntDomain;
+        }
+
+        public ProxyConfig ntDomain(String ntDomain) {
+            this.ntDomain = ntDomain;
+            return this;
+        }
+
+        public List<String> getRedirectToHosts() {
+            return redirectToHosts;
+        }
+
+        public ProxyConfig redirectToHosts(List<String> redirectToHosts) {
+            this.redirectToHosts = redirectToHosts;
+            return this;
+        }
+    }
+}
diff --git a/impl/src/main/java/com/jfrog/bintray/client/impl/handle/AttributesSearchQueryClauseImpl.java b/impl/src/main/java/com/jfrog/bintray/client/impl/handle/AttributesSearchQueryClauseImpl.java
new file mode 100644
index 0000000..076868e
--- /dev/null
+++ b/impl/src/main/java/com/jfrog/bintray/client/impl/handle/AttributesSearchQueryClauseImpl.java
@@ -0,0 +1,127 @@
+package com.jfrog.bintray.client.impl.handle;
+
+import com.jfrog.bintray.client.api.details.Attribute;
+import com.jfrog.bintray.client.api.handle.AttributesSearchQuery;
+import com.jfrog.bintray.client.api.handle.AttributesSearchQueryClause;
+import com.jfrog.bintray.client.api.model.Pkg;
+import com.jfrog.bintray.client.api.model.Version;
+import org.codehaus.jackson.annotate.JsonIgnore;
+import org.joda.time.DateTime;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * @author jbaruch
+ * @author Dan Feldman
+ * @since 13/11/13
+ */
+class AttributesSearchQueryClauseImpl implements AttributesSearchQueryClause {
+
+    @JsonIgnore
+    private final AttributesSearchQueryImpl query;
+    @JsonIgnore
+    private Object clauseValue;
+    @JsonIgnore
+    private Attribute.Type type;
+
+
+    AttributesSearchQueryClauseImpl(AttributesSearchQueryImpl query) {
+        this.query = query;
+    }
+
+    public Object getClauseValue() {
+        return clauseValue;
+    }
+
+    public Attribute.Type getType() {
+        return type;
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public AttributesSearchQueryClause equalsVal(Object value) {
+        Attribute attribute = new Attribute("TypeTest", value); //This validates proper value type
+        type = attribute.getType();
+        clauseValue = value;
+        return this;
+    }
+
+    @Override
+    public AttributesSearchQuery and() {
+        query.addQueryClause(this);
+        return query;
+    }
+
+    @Override
+    public List<Pkg> searchPackage() throws IOException {
+        query.addQueryClause(this);
+        return query.searchPackage();
+    }
+
+    @Override
+    public List<Version> searchVersion() throws IOException {
+        query.addQueryClause(this);
+        return query.searchVersion();
+    }
+
+    @Override
+    public AttributesSearchQueryClause lessThan(int value) {
+        //TODO implement
+        return this;
+    }
+
+    @Override
+    public AttributesSearchQueryClause lessOrEquals(int value) {
+        //TODO implement
+        return this;
+    }
+
+    @Override
+    public AttributesSearchQueryClause before(DateTime value) {
+        //TODO implement
+        return this;
+    }
+
+    @Override
+    public AttributesSearchQueryClause beforeOrAt(DateTime value) {
+        //TODO implement
+        return this;
+    }
+
+    @Override
+    public AttributesSearchQueryClause at(DateTime value) {
+        //TODO implement
+        return this;
+    }
+
+    @Override
+    public AttributesSearchQueryClause after(DateTime value) {
+        //TODO implement
+        return this;
+    }
+
+    @Override
+    public AttributesSearchQueryClause afterOrAt(DateTime value) {
+        //TODO implement
+        return this;
+    }
+
+    @Override
+    public AttributesSearchQueryClause greaterThan(int value) {
+        //TODO implement
+        return this;
+    }
+
+    @Override
+    public AttributesSearchQueryClause greaterOrEqualsTo(int value) {
+        //TODO implement
+        return this;
+    }
+
+    @Override
+    public AttributesSearchQueryClause in(String... values) {
+        //TODO implement
+        return this;
+    }
+}
diff --git a/impl/src/main/java/com/jfrog/bintray/client/impl/handle/AttributesSearchQueryImpl.java b/impl/src/main/java/com/jfrog/bintray/client/impl/handle/AttributesSearchQueryImpl.java
new file mode 100644
index 0000000..9526781
--- /dev/null
+++ b/impl/src/main/java/com/jfrog/bintray/client/impl/handle/AttributesSearchQueryImpl.java
@@ -0,0 +1,103 @@
+package com.jfrog.bintray.client.impl.handle;
+
+import com.jfrog.bintray.client.api.BintrayCallException;
+import com.jfrog.bintray.client.api.details.Attribute;
+import com.jfrog.bintray.client.api.handle.AttributesSearchQuery;
+import com.jfrog.bintray.client.api.handle.AttributesSearchQueryClause;
+import com.jfrog.bintray.client.api.model.Pkg;
+import com.jfrog.bintray.client.api.model.Version;
+import org.codehaus.jackson.JsonGenerator;
+import org.codehaus.jackson.JsonProcessingException;
+import org.codehaus.jackson.map.JsonSerializer;
+import org.codehaus.jackson.map.SerializerProvider;
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author jbaruch
+ * @author Dan Feldman
+ * @since 13/11/13
+ */
+ at JsonSerialize(using = AttributesSearchQueryImpl.AttributeSearchQuerySerializer.class)
+class AttributesSearchQueryImpl implements AttributesSearchQuery {
+
+    private final RepositoryHandleImpl repositoryHandle;
+    private final PackageHandleImpl packageHandle;
+    private String attributeName;
+    private List<AttributesSearchQueryClauseImpl> queryClauses = new ArrayList<>();
+
+    /**
+     * Creates an AttributesSearchQuery object that will search for a version
+     *
+     * @param repositoryHandle repository to search in
+     */
+    public AttributesSearchQueryImpl(RepositoryHandleImpl repositoryHandle) {
+        this.repositoryHandle = repositoryHandle;
+        this.packageHandle = null;
+    }
+
+    /**
+     * Creates an AttributesSearchQuery object that will search for a version
+     *
+     * @param packageHandle version to search in
+     */
+    public AttributesSearchQueryImpl(PackageHandleImpl packageHandle) {
+        this.packageHandle = packageHandle;
+        this.repositoryHandle = null;
+    }
+
+    public void addQueryClause(AttributesSearchQueryClauseImpl clause) {
+        queryClauses.add(clause);
+    }
+
+    public List<AttributesSearchQueryClauseImpl> getQueryClauses() {
+        return queryClauses;
+    }
+
+    @Override
+    public AttributesSearchQueryClause byAttributeName(String attributeName) {
+        this.attributeName = attributeName;
+        return new AttributesSearchQueryClauseImpl(this);
+    }
+
+    public List<Pkg> searchPackage() throws IOException, BintrayCallException {
+        repositoryHandle.addQuery(this);
+        return repositoryHandle.attributeSearch();
+    }
+
+    public List<Version> searchVersion() throws IOException, BintrayCallException {
+        packageHandle.addQuery(this);
+        return packageHandle.attributeSearch();
+    }
+
+    public static class AttributeSearchQuerySerializer extends JsonSerializer<AttributesSearchQueryImpl> {
+
+        // TODO: add support for other search methods (greater, less than etc.) with Bintray's search syntax
+        @Override
+        public void serialize(AttributesSearchQueryImpl value, JsonGenerator jgen, SerializerProvider provider)
+                throws IOException, JsonProcessingException {
+
+            jgen.writeStartArray();
+            jgen.writeStartObject();
+            jgen.writeArrayFieldStart(value.attributeName);
+
+            @SuppressWarnings("unchecked")
+            List<AttributesSearchQueryClauseImpl> clauses = value.getQueryClauses();
+            for (AttributesSearchQueryClauseImpl clause : clauses) {
+                if (clause.getType().equals(Attribute.Type.Boolean)) {
+                    jgen.writeBoolean((Boolean) clause.getClauseValue());
+                } else if (clause.getType().equals(Attribute.Type.number)) {
+                    jgen.writeNumber(String.valueOf(clause.getClauseValue()));
+                } else {  //String or Date
+                    jgen.writeString((String) clause.getClauseValue());
+                }
+            }
+            jgen.writeEndArray();
+            jgen.writeEndObject();
+            jgen.writeEndArray();
+        }
+    }
+}
diff --git a/impl/src/main/java/com/jfrog/bintray/client/impl/handle/BintrayImpl.java b/impl/src/main/java/com/jfrog/bintray/client/impl/handle/BintrayImpl.java
new file mode 100644
index 0000000..965577e
--- /dev/null
+++ b/impl/src/main/java/com/jfrog/bintray/client/impl/handle/BintrayImpl.java
@@ -0,0 +1,407 @@
+package com.jfrog.bintray.client.impl.handle;
+
+import com.jfrog.bintray.client.api.BintrayCallException;
+import com.jfrog.bintray.client.api.MultipleBintrayCallException;
+import com.jfrog.bintray.client.api.handle.*;
+import com.jfrog.bintray.client.impl.util.URIUtil;
+import org.apache.commons.io.IOUtils;
+import org.apache.http.*;
+import org.apache.http.client.ResponseHandler;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.*;
+import org.apache.http.client.protocol.HttpClientContext;
+import org.apache.http.client.utils.HttpClientUtils;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.InputStreamEntity;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.DefaultHttpResponseFactory;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.Charset;
+import java.util.*;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+
+/**
+ * @author Dan Feldman
+ */
+public class BintrayImpl implements Bintray {
+    private static final Logger log = LoggerFactory.getLogger(BintrayImpl.class);
+    ExecutorService executorService;
+    private CloseableHttpClient client;
+    private ResponseHandler<HttpResponse> responseHandler = new BintrayResponseHandler();
+    private String baseUrl;
+    private int signRequestTimeoutPerFile;
+
+
+    public BintrayImpl(CloseableHttpClient client, String baseUrl, int threadPoolSize, int signRequestTimeoutPerFile) {
+        this.client = client;
+        this.baseUrl = baseUrl;
+        this.executorService = Executors.newFixedThreadPool(threadPoolSize);
+        this.signRequestTimeoutPerFile = signRequestTimeoutPerFile;
+    }
+
+    static public void addContentTypeJsonHeader(Map<String, String> headers) {
+        headers.put(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.getMimeType());
+    }
+
+    static public void addContentTypeBinaryHeader(Map<String, String> headers) {
+        headers.put(HttpHeaders.CONTENT_TYPE, ContentType.DEFAULT_BINARY.getMimeType());
+    }
+
+    private static boolean statusNotOk(int statusCode) {
+        return statusCode != HttpStatus.SC_OK
+                && statusCode != HttpStatus.SC_CREATED
+                && statusCode != HttpStatus.SC_ACCEPTED;
+    }
+
+    @Override
+    public SubjectHandle subject(String subject) {
+        return new SubjectHandleImpl(this, subject);
+    }
+
+    @Override
+    public RepositoryHandle repository(String repositoryPath) {
+        // TODO: implement full path resolution that receives /subject/repo/
+        throw new UnsupportedOperationException("Not yet supported");
+    }
+
+    @Override
+    public PackageHandle pkg(String packagePath) {
+        // TODO: implement full path resolution that receives /subject/repo/pkg
+        throw new UnsupportedOperationException("Not yet supported");
+    }
+
+    @Override
+    public VersionHandle version(String versionPath) {
+        // TODO: implement full path resolution that receives /subject/repo/pkg/version
+        throw new UnsupportedOperationException("Not yet supported");
+    }
+
+    @Override
+    public void close() {
+        executorService.shutdown();
+        HttpClientUtils.closeQuietly(client);
+    }
+
+    public HttpResponse get(String uri, Map<String, String> headers) throws BintrayCallException {
+        HttpGet getRequest = new HttpGet(createUrl(uri));
+        return setHeadersAndExecute(getRequest, headers);
+    }
+
+    public HttpResponse head(String uri, Map<String, String> headers) throws BintrayCallException {
+        HttpHead headRequest = new HttpHead(createUrl(uri));
+        return setHeadersAndExecute(headRequest, headers);
+    }
+
+    /**
+     * Executes a sign request using the ExecutorService and uses the file count to set a timeout to avoid timing out
+     * on long requests
+     *
+     * @throws BintrayCallException
+     */
+    public HttpResponse sign(String uri, Map<String, String> headers, int fileCount) throws BintrayCallException {
+        HttpPost signRequest = new HttpPost(createUrl(uri));
+        setHeaders(signRequest, headers);
+        signRequest.setConfig(RequestConfig.custom().setSocketTimeout(signRequestTimeoutPerFile * fileCount)
+                .setConnectionRequestTimeout(signRequestTimeoutPerFile * fileCount)
+                .setConnectTimeout(signRequestTimeoutPerFile * fileCount).build());
+        RequestRunner runner = new RequestRunner(signRequest, client, responseHandler);
+        Future<String> signResponse = executorService.submit(runner);
+        try {
+            signResponse.get();
+        } catch (Exception e) {
+            BintrayCallException bce;
+            if (e.getCause() instanceof BintrayCallException) {
+                bce = (BintrayCallException) e.getCause();
+            } else {
+                bce = new BintrayCallException(409, e.getMessage(), (e.getCause() == null) ? ""
+                        : ", " + e.getCause().toString() + " : " + e.getCause().getMessage());
+            }
+            log.error(bce.toString());
+            log.debug("{}", e.getMessage(), e);
+            throw bce;
+        }
+
+        //Return ok
+        String entity = "Signing the version was successful";
+        HttpResponse response = DefaultHttpResponseFactory.INSTANCE.newHttpResponse(
+                new ProtocolVersion("HTTP", 1, 1), HttpStatus.SC_CREATED, new HttpClientContext());
+        response.setEntity(new StringEntity(entity, Charset.forName("UTF-8")));
+        return response;
+    }
+
+    public HttpResponse post(String uri, Map<String, String> headers) throws BintrayCallException {
+        HttpPost postRequest = new HttpPost(createUrl(uri));
+        return setHeadersAndExecute(postRequest, headers);
+    }
+
+    public HttpResponse post(String uri, Map<String, String> headers, InputStream elementInputStream) throws BintrayCallException {
+        HttpPost postRequest = new HttpPost(createUrl(uri));
+        HttpEntity requestEntity = new InputStreamEntity(elementInputStream);
+        postRequest.setEntity(requestEntity);
+        return setHeadersAndExecute(postRequest, headers);
+    }
+
+    public HttpResponse patch(String uri, Map<String, String> headers, InputStream elementInputStream) throws BintrayCallException {
+        HttpPatch patchRequest = new HttpPatch(createUrl(uri));
+        HttpEntity requestEntity = new InputStreamEntity(elementInputStream);
+        patchRequest.setEntity(requestEntity);
+        return setHeadersAndExecute(patchRequest, headers);
+    }
+
+    public HttpResponse delete(String uri, Map<String, String> headers) throws BintrayCallException {
+        HttpDelete deleteRequest = new HttpDelete(createUrl(uri));
+        return setHeadersAndExecute(deleteRequest, headers);
+    }
+
+    public HttpResponse putBinary(String uri, Map<String, String> headers, InputStream elementInputStream) throws BintrayCallException {
+        if (headers == null) {
+            headers = new HashMap<>();
+        }
+        addContentTypeBinaryHeader(headers);
+        return put(uri, headers, elementInputStream);
+    }
+
+    public HttpResponse putBinary(Map<String, InputStream> uriAndStreamMap, Map<String, String> headers) throws MultipleBintrayCallException {
+        if (headers == null) {
+            headers = new HashMap<>();
+        }
+        addContentTypeBinaryHeader(headers);
+        return put(uriAndStreamMap, headers);
+    }
+
+    public HttpResponse put(String uri, Map<String, String> headers, InputStream elementInputStream) throws BintrayCallException {
+        HttpPut putRequest = new HttpPut(createUrl(uri));
+        HttpEntity requestEntity = new InputStreamEntity(elementInputStream);
+        putRequest.setEntity(requestEntity);
+        return setHeadersAndExecute(putRequest, headers);
+    }
+
+    public HttpResponse put(Map<String, InputStream> uriAndStreamMap, Map<String, String> headers) throws MultipleBintrayCallException {
+        List<Future<String>> executions = new ArrayList<>();
+        List<BintrayCallException> errors = new ArrayList<>();
+        List<RequestRunner> runners = createPutRequestRunners(uriAndStreamMap, headers, errors);
+        try {
+            executions = executorService.invokeAll(runners);
+        } catch (InterruptedException e) {
+            BintrayCallException bce = new BintrayCallException(409, e.getMessage(), (e.getCause() == null) ? ""
+                    : e.getCause().toString() + " : " + e.getCause().getMessage());
+            log.error(bce.toString());
+            log.debug("{}", e.getMessage(), e);
+            errors.add(bce);
+        }
+        collectResults(executions, errors);
+
+        //Return ok or throw errors
+        if (errors.isEmpty()) {
+            String entity = "Operation Successful";
+            HttpResponse response = DefaultHttpResponseFactory.INSTANCE.newHttpResponse(
+                    new ProtocolVersion("HTTP", 1, 1), HttpStatus.SC_CREATED, new HttpClientContext());
+            response.setEntity(new StringEntity(entity, Charset.forName("UTF-8")));
+            return response;
+        } else {
+            throw new MultipleBintrayCallException(errors);
+        }
+    }
+
+    private List<RequestRunner> createPutRequestRunners(Map<String, InputStream> uriAndStreamMap, Map<String, String> headers, List<BintrayCallException> errors) {
+        List<RequestRunner> runners = new ArrayList<>();
+        List<HttpPut> requests = new ArrayList<>();
+        log.debug("Creating PUT requests and RequestRunners for execution");
+        for (String apiPath : uriAndStreamMap.keySet()) {
+            HttpPut putRequest;
+            try {
+                putRequest = new HttpPut(createUrl(apiPath));
+            } catch (BintrayCallException bce) {
+                errors.add(bce);
+                continue;
+            }
+            HttpEntity requestEntity = new InputStreamEntity(uriAndStreamMap.get(apiPath));
+            putRequest.setEntity(requestEntity);
+            setHeaders(putRequest, headers);
+            requests.add(putRequest);
+        }
+
+        for (HttpPut request : requests) {
+            RequestRunner runner = new RequestRunner(request, client, responseHandler);
+            runners.add(runner);
+        }
+        return runners;
+    }
+
+    private void collectResults(List<Future<String>> executions, List<BintrayCallException> errors) {
+        //Wait until all executions are done
+        while (!executions.isEmpty()) {
+            log.debug("Querying execution Futures for results");
+            for (Iterator<Future<String>> executionIter = executions.iterator(); executionIter.hasNext(); ) {
+                Future<String> execution = executionIter.next();
+                if (execution.isDone()) {
+                    try {
+                        String response = execution.get();
+                        log.debug("Got complete execution: {}", response);
+                    } catch (Exception e) {
+                        BintrayCallException bce;
+                        if (e.getCause() instanceof BintrayCallException) {
+                            bce = (BintrayCallException) e.getCause();
+                        } else {
+                            bce = new BintrayCallException(400, e.getMessage(), (e.getCause() == null) ? ""
+                                    : e.getCause().getMessage());
+                        }
+                        log.error(bce.toString());
+                        log.debug("{}", e.getMessage(), e);
+                        errors.add(bce);
+                    } finally {
+                        executionIter.remove();     //Remove completed execution from iteration
+                    }
+                }
+            }
+        }
+    }
+
+    private String createUrl(String queryPath) throws BintrayCallException {
+        log.debug("Trying to encode uri: '{}' with base url: {}", queryPath, baseUrl);
+        try {
+            return URIUtil.encodeQuery(baseUrl + "/" + queryPath);
+        } catch (HttpException e) {
+            throw new BintrayCallException(HttpStatus.SC_BAD_REQUEST, "Malformed url, request will not be sent: ",
+                    e.getMessage());
+        }
+    }
+
+    private void setHeaders(HttpUriRequest request, Map<String, String> headers) {
+        if (headers != null && !headers.isEmpty()) {
+            for (String header : headers.keySet()) {
+                request.setHeader(header, headers.get(header));
+            }
+        }
+    }
+
+    private HttpResponse setHeadersAndExecute(HttpUriRequest request, Map<String, String> headers) throws BintrayCallException {
+        setHeaders(request, headers);
+        return execute(request, null);
+    }
+
+    private HttpResponse execute(HttpUriRequest request, HttpClientContext context) throws BintrayCallException {
+        log.debug("Executing {} request to path '{}', with headers: {}", request.getMethod(), request.getURI(),
+                Arrays.toString(request.getAllHeaders()));
+        try {
+            if (context != null) {
+                return client.execute(request, responseHandler, context);
+            } else {
+                return client.execute(request, responseHandler);
+            }
+        } catch (BintrayCallException bce) {
+            log.debug("{}", bce.toString(), bce);
+            throw bce;
+        } catch (IOException ioe) {
+            //Underlying IOException form the client
+            String underlyingCause = (ioe.getCause() == null) ? "" : ioe.toString() + " : " + ioe.getCause().getMessage();
+            log.debug("{}", ioe.getMessage(), ioe);
+            throw new BintrayCallException(400, ioe.getMessage(), underlyingCause);
+        }
+    }
+
+    /**
+     * A callable that executes a single put request, returns a String containing an error or '200' if successful
+     */
+    private static class RequestRunner implements Callable<String> {
+
+        private final HttpRequestBase request;
+        private final CloseableHttpClient client;
+        private final HttpClientContext context;
+        private final ResponseHandler<HttpResponse> responseHandler;
+
+        public RequestRunner(HttpRequestBase request, CloseableHttpClient client, ResponseHandler<HttpResponse> responseHandler) {
+            this.request = request;
+            this.client = client;
+            this.context = HttpClientContext.create();
+            this.responseHandler = responseHandler;
+        }
+
+        @Override
+        public String call() throws BintrayCallException {
+            log.debug("Executing {} request to path '{}', with headers: {}", request.getMethod(), request.getURI(),
+                    Arrays.toString(request.getAllHeaders()));
+            StringBuilder errorResultBuilder;
+            String requestPath;
+            if (request instanceof HttpPut) {
+                requestPath = request.getURI().getPath().substring(9); //Substring cuts the '/content/' part from the URI
+                log.info("Pushing " + requestPath);
+                errorResultBuilder = new StringBuilder(" Pushing " + requestPath + " failed: ");
+            } else {
+                requestPath = request.getURI().getPath();
+                errorResultBuilder = new StringBuilder(request.getMethod() + " " + requestPath + " failed: ");
+            }
+            HttpResponse response;
+            try {
+                response = client.execute(request, responseHandler, context);
+            } catch (BintrayCallException bce) {
+                log.debug("{}", bce.getMessage(), bce);
+                errorResultBuilder.append(bce.getMessage());
+                bce.setMessage(errorResultBuilder.toString());
+                throw bce;
+            } catch (IOException ioe) {
+                log.debug("IOException occurred: '{}'", ioe.getMessage(), ioe);
+                String cause = (ioe.getCause() != null) ? (", caused by: " + ioe.getCause().toString() + " : "
+                        + ioe.getCause().getMessage()) : "";
+                errorResultBuilder.append(ioe.toString()).append(" : ").append(ioe.getMessage()).append(cause);
+                throw new BintrayCallException(HttpStatus.SC_BAD_REQUEST, ioe.getMessage(), errorResultBuilder.toString());
+            } finally {
+                request.releaseConnection();
+            }
+            if (statusNotOk(response.getStatusLine().getStatusCode())) {
+                BintrayCallException bce = new BintrayCallException(response);
+                errorResultBuilder.append(bce.getMessage());
+                bce.setMessage(errorResultBuilder.toString());
+                throw bce;
+            }
+            return request.getMethod() + " " + requestPath + ": " + String.valueOf(response.getStatusLine().getStatusCode());
+        }
+    }
+
+    /**
+     * gets responses from the underlying HttpClient and closes them (so you don't have to) the response body is
+     * buffered in an intermediary byte array.
+     * Will throw a {@link BintrayCallException} if the request failed.
+     */
+    private class BintrayResponseHandler implements ResponseHandler<HttpResponse> {
+
+        @Override
+        public HttpResponse handleResponse(HttpResponse response) throws BintrayCallException {
+            int statusCode = response.getStatusLine().getStatusCode();
+            if (statusNotOk(statusCode)) {
+                BintrayCallException bce = new BintrayCallException(response);
+
+                //We're using CloseableHttpClient so it's ok
+                HttpClientUtils.closeQuietly((CloseableHttpResponse) response);
+                throw bce;
+            }
+
+            //Response entity might be null, 500 and 405 also give the html itself so skip it
+            String entity = "";
+            if (response.getEntity() != null && statusCode != 500 && statusCode != 405) {
+                try {
+                    entity = IOUtils.toString(response.getEntity().getContent());
+                } catch (IOException | NullPointerException e) {
+                    //Null entity - Ignore
+                } finally {
+                    HttpClientUtils.closeQuietly((CloseableHttpResponse) response);
+                }
+            }
+
+            HttpResponse newResponse = DefaultHttpResponseFactory.INSTANCE.newHttpResponse(response.getStatusLine(),
+                    new HttpClientContext());
+            newResponse.setEntity(new StringEntity(entity, Charset.forName("UTF-8")));
+            newResponse.setHeaders(response.getAllHeaders());
+            return newResponse;
+        }
+    }
+}
diff --git a/impl/src/main/java/com/jfrog/bintray/client/impl/handle/PackageHandleImpl.java b/impl/src/main/java/com/jfrog/bintray/client/impl/handle/PackageHandleImpl.java
new file mode 100644
index 0000000..6110c91
--- /dev/null
+++ b/impl/src/main/java/com/jfrog/bintray/client/impl/handle/PackageHandleImpl.java
@@ -0,0 +1,201 @@
+package com.jfrog.bintray.client.impl.handle;
+
+import com.jfrog.bintray.client.api.BintrayCallException;
+import com.jfrog.bintray.client.api.details.Attribute;
+import com.jfrog.bintray.client.api.details.ObjectMapperHelper;
+import com.jfrog.bintray.client.api.details.PackageDetails;
+import com.jfrog.bintray.client.api.details.VersionDetails;
+import com.jfrog.bintray.client.api.handle.PackageHandle;
+import com.jfrog.bintray.client.api.handle.RepositoryHandle;
+import com.jfrog.bintray.client.api.handle.VersionHandle;
+import com.jfrog.bintray.client.api.model.Pkg;
+import com.jfrog.bintray.client.api.model.Version;
+import com.jfrog.bintray.client.impl.model.PackageImpl;
+import com.jfrog.bintray.client.impl.model.VersionImpl;
+import org.apache.commons.io.IOUtils;
+import org.apache.http.HttpHeaders;
+import org.apache.http.HttpResponse;
+import org.apache.http.entity.ContentType;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.type.TypeReference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author Dan Feldman
+ */
+class PackageHandleImpl implements PackageHandle {
+    private static final Logger log = LoggerFactory.getLogger(PackageHandleImpl.class);
+
+    private BintrayImpl bintrayHandle;
+    private RepositoryHandle repositoryHandle;
+    private String name;
+    private AttributesSearchQueryImpl searchQuery = null;
+
+    PackageHandleImpl(BintrayImpl bintrayHandle, RepositoryHandle repositoryHandle, String name) {
+        this.bintrayHandle = bintrayHandle;
+        this.repositoryHandle = repositoryHandle;
+        this.name = name;
+    }
+
+    @Override
+    public RepositoryHandle repository() {
+        return repositoryHandle;
+    }
+
+    @Override
+    public VersionHandle version(String versionName) {
+        return new VersionHandleImpl(bintrayHandle, this, versionName);
+    }
+
+    @Override
+    public Pkg get() throws IOException, BintrayCallException {
+        HttpResponse response = bintrayHandle.get(getCurrentPackageUri(), null);
+        PackageDetails pkgDetails;
+        String jsonContentStream = IOUtils.toString(response.getEntity().getContent());
+        ObjectMapper mapper = ObjectMapperHelper.objectMapper;
+        try {
+            pkgDetails = mapper.readValue(jsonContentStream, PackageDetails.class);
+        } catch (IOException e) {
+            log.error("Can't parse the json file: " + e.getMessage());
+            throw e;
+        }
+        return new PackageImpl(pkgDetails);
+    }
+
+    @Override
+    public PackageHandle update(PackageDetails packageDetails) throws IOException, BintrayCallException {
+        Map<String, String> headers = new HashMap<>();
+        String jsonContent = PackageImpl.getCreateUpdateJson(packageDetails);
+        BintrayImpl.addContentTypeJsonHeader(headers);
+        bintrayHandle.patch(getCurrentPackageUri(), headers, IOUtils.toInputStream(jsonContent));
+
+        return this.updateAttributes(packageDetails);
+    }
+
+    @Override
+    public PackageHandle delete() throws BintrayCallException {
+        bintrayHandle.delete(getCurrentPackageUri(), null);
+        return this;
+    }
+
+    @Override
+    public boolean exists() throws BintrayCallException {
+        try {
+            bintrayHandle.head(getCurrentPackageUri(), null);
+        } catch (BintrayCallException e) {
+            if (e.getStatusCode() == 404) {
+                return false;
+            }
+            throw e;
+        }
+        return true;
+    }
+
+    @Override
+    public VersionHandle createVersion(VersionDetails versionDetails) throws IOException, BintrayCallException {
+        Map<String, String> headers = new HashMap<>();
+        String jsonContent = VersionImpl.getCreateUpdateJson(versionDetails);
+        BintrayImpl.addContentTypeJsonHeader(headers);
+        bintrayHandle.post(getCurrentPackageUri() + "/versions", headers, IOUtils.toInputStream(jsonContent));
+
+        return new VersionHandleImpl(bintrayHandle, this, versionDetails.getName()).setAttributes(versionDetails);
+    }
+
+
+    @Override
+    public PackageHandle setAttributes(PackageDetails packageDetails) throws IOException, BintrayCallException {
+        return setAttributes(packageDetails.getAttributes());
+    }
+
+    @Override
+    public PackageHandle setAttributes(List<Attribute> attributes) throws IOException, BintrayCallException {
+        if (attributes == null) {
+            return this;
+        }
+        Map<String, String> headers = new HashMap<>();
+        String jsonContent = Attribute.getJsonFromAttributeList(attributes);
+        BintrayImpl.addContentTypeJsonHeader(headers);
+        bintrayHandle.post(getCurrentPackageUri() + "/attributes", headers, IOUtils.toInputStream(jsonContent));
+
+        return this;
+    }
+
+    @Override
+    public PackageHandle updateAttributes(PackageDetails packageDetails) throws IOException, BintrayCallException {
+        return updateAttributes(packageDetails.getAttributes());
+    }
+
+    @Override
+    public PackageHandle updateAttributes(List<Attribute> attributes) throws IOException, BintrayCallException {
+        if (attributes == null) {
+            return this;
+        }
+        Map<String, String> headers = new HashMap<>();
+        String jsonContent = Attribute.getJsonFromAttributeList(attributes);
+        BintrayImpl.addContentTypeJsonHeader(headers);
+        bintrayHandle.patch(getCurrentPackageUri() + "/attributes", headers, IOUtils.toInputStream(jsonContent));
+
+        return this;
+    }
+
+    public void addQuery(AttributesSearchQueryImpl query) {
+        searchQuery = query;
+    }
+
+    /**
+     * Searches for versions according to the attribute search query supplied to addQuery
+     *
+     * @return a list of packages that were returned by Bintray's search
+     * @throws BintrayCallException
+     */
+    public List<Version> attributeSearch() throws BintrayCallException {
+        ObjectMapper mapper = ObjectMapperHelper.objectMapper;
+        StringWriter writer = new StringWriter();
+        try {
+            mapper.writeValue(writer, searchQuery);
+        } catch (IOException e) {
+            log.error("Error writing search query to json: ", e);
+            return null;
+        }
+
+        Map<String, String> headers = new HashMap<>();
+        headers.put(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.getMimeType());
+        HttpResponse response = bintrayHandle.post("/search/attributes/" + repositoryHandle.owner().name() +
+                        "/" + repositoryHandle.name() + "/" + name + "/versions", headers,
+                IOUtils.toInputStream(writer.toString()));
+
+        List<VersionDetails> answer = new ArrayList<>();
+
+        try {
+            answer = mapper.readValue(response.getEntity().getContent(), new TypeReference<List<VersionDetails>>() {
+            });
+        } catch (IOException e) {
+            log.error("Error parsing query response");
+        }
+
+        List<Version> packages = new ArrayList<>();
+        for (VersionDetails verDetails : answer) {
+            packages.add(new VersionImpl(verDetails));
+        }
+        searchQuery = null;
+        return packages;
+    }
+
+    @Override
+    public String getCurrentPackageUri() {
+        return String.format("packages/%s/%s/%s", repositoryHandle.owner().name(), repositoryHandle.name(), name);
+    }
+
+    @Override
+    public String name() {
+        return name;
+    }
+}
diff --git a/impl/src/main/java/com/jfrog/bintray/client/impl/handle/RepositoryHandleImpl.java b/impl/src/main/java/com/jfrog/bintray/client/impl/handle/RepositoryHandleImpl.java
new file mode 100644
index 0000000..8505a91
--- /dev/null
+++ b/impl/src/main/java/com/jfrog/bintray/client/impl/handle/RepositoryHandleImpl.java
@@ -0,0 +1,146 @@
+package com.jfrog.bintray.client.impl.handle;
+
+import com.jfrog.bintray.client.api.BintrayCallException;
+import com.jfrog.bintray.client.api.details.ObjectMapperHelper;
+import com.jfrog.bintray.client.api.details.PackageDetails;
+import com.jfrog.bintray.client.api.details.RepositoryDetails;
+import com.jfrog.bintray.client.api.handle.AttributesSearchQuery;
+import com.jfrog.bintray.client.api.handle.PackageHandle;
+import com.jfrog.bintray.client.api.handle.RepositoryHandle;
+import com.jfrog.bintray.client.api.handle.SubjectHandle;
+import com.jfrog.bintray.client.api.model.Pkg;
+import com.jfrog.bintray.client.api.model.Repository;
+import com.jfrog.bintray.client.impl.model.PackageImpl;
+import com.jfrog.bintray.client.impl.model.RepositoryImpl;
+import org.apache.commons.io.IOUtils;
+import org.apache.http.HttpHeaders;
+import org.apache.http.HttpResponse;
+import org.apache.http.entity.ContentType;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.type.TypeReference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author Dan Feldman
+ */
+class RepositoryHandleImpl implements RepositoryHandle {
+    private static final Logger log = LoggerFactory.getLogger(RepositoryHandleImpl.class);
+
+    private BintrayImpl bintrayHandle;
+    private SubjectHandleImpl owner;
+    private String name;
+    private AttributesSearchQueryImpl searchQuery = null;
+
+    public RepositoryHandleImpl(BintrayImpl bintrayHandle, SubjectHandleImpl owner, String repoName) {
+        this.bintrayHandle = bintrayHandle;
+        this.owner = owner;
+        this.name = repoName;
+    }
+
+    @Override
+    public SubjectHandle owner() {
+        return owner;
+    }
+
+    @Override
+    public Repository get() throws IOException, BintrayCallException {
+        HttpResponse response = bintrayHandle.get(getRepositoryUri(), null);
+        RepositoryDetails repoDetails;
+        InputStream jsonContentStream = response.getEntity().getContent();
+        ObjectMapper mapper = ObjectMapperHelper.objectMapper;
+        try {
+            repoDetails = mapper.readValue(jsonContentStream, RepositoryDetails.class);
+        } catch (IOException e) {
+            log.error("Can't parse the json file: " + e.getMessage());
+            throw e;
+        }
+        return new RepositoryImpl(repoDetails);
+    }
+
+    @Override
+    public PackageHandle pkg(String packageName) {
+        return new PackageHandleImpl(bintrayHandle, this, packageName);
+    }
+
+    @Override
+    public PackageHandle createPkg(PackageDetails packageDetails) throws IOException, BintrayCallException {
+        String jsonContent = PackageImpl.getCreateUpdateJson(packageDetails);
+        bintrayHandle.post(String.format("packages/%s/%s", owner.name(), name), null, IOUtils.toInputStream(jsonContent));
+        return new PackageHandleImpl(bintrayHandle, this, packageDetails.getName()).setAttributes(packageDetails);
+    }
+
+    @Override
+    public AttributesSearchQuery searchForPackage() {
+        return new AttributesSearchQueryImpl(this);
+    }
+
+    @Override
+    public String name() {
+        return name;
+    }
+
+    @Override
+    public boolean exists() throws BintrayCallException {
+        try {
+            bintrayHandle.head(getRepositoryUri(), null);
+        } catch (BintrayCallException e) {
+            if (e.getStatusCode() == 404) {
+                return false;
+            }
+            throw e;
+        }
+        return true;
+    }
+
+    @Override
+    public String getRepositoryUri() {
+        return String.format("repos/%s/%s", owner.name(), name);
+    }
+
+    public void addQuery(AttributesSearchQueryImpl query) {
+        searchQuery = query;
+    }
+
+    /**
+     * Searches for packages according to the attribute search query supplied to addQuery
+     *
+     * @return a list of packages that were returned by Bintray's search
+     * @throws IOException
+     * @throws BintrayCallException
+     */
+    public List<Pkg> attributeSearch() throws IOException, BintrayCallException {
+        ObjectMapper mapper = ObjectMapperHelper.objectMapper;
+        StringWriter writer = new StringWriter();
+        try {
+            mapper.writeValue(writer, searchQuery);
+        } catch (IOException e) {
+            log.error("Error writing search query to json: ", e);
+            throw e;
+        }
+
+        Map<String, String> headers = new HashMap<>();
+        headers.put(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.getMimeType());
+        HttpResponse response = bintrayHandle.post("/search/attributes/" + owner.name() + "/" + name, headers,
+                IOUtils.toInputStream(writer.toString()));
+
+        List<PackageDetails> answer;
+        answer = mapper.readValue(response.getEntity().getContent(), new TypeReference<List<PackageDetails>>() {
+        });
+
+        List<Pkg> packages = new ArrayList<>();
+        for (PackageDetails pkgDetails : answer) {
+            packages.add(new PackageImpl(pkgDetails));
+        }
+        searchQuery = null;
+        return packages;
+    }
+}
diff --git a/impl/src/main/java/com/jfrog/bintray/client/impl/handle/SubjectHandleImpl.java b/impl/src/main/java/com/jfrog/bintray/client/impl/handle/SubjectHandleImpl.java
new file mode 100644
index 0000000..a6fc100
--- /dev/null
+++ b/impl/src/main/java/com/jfrog/bintray/client/impl/handle/SubjectHandleImpl.java
@@ -0,0 +1,56 @@
+package com.jfrog.bintray.client.impl.handle;
+
+import com.jfrog.bintray.client.api.BintrayCallException;
+import com.jfrog.bintray.client.api.details.ObjectMapperHelper;
+import com.jfrog.bintray.client.api.details.SubjectDetails;
+import com.jfrog.bintray.client.api.handle.RepositoryHandle;
+import com.jfrog.bintray.client.api.handle.SubjectHandle;
+import com.jfrog.bintray.client.api.model.Subject;
+import com.jfrog.bintray.client.impl.model.SubjectImpl;
+import org.apache.http.HttpResponse;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * @author Dan Feldman
+ */
+class SubjectHandleImpl implements SubjectHandle {
+    private static final Logger log = LoggerFactory.getLogger(SubjectHandleImpl.class);
+
+    private BintrayImpl bintrayHandle;
+    private String subject;
+
+    SubjectHandleImpl(BintrayImpl bintrayHandle, String subject) {
+        this.bintrayHandle = bintrayHandle;
+        this.subject = subject;
+    }
+
+    @Override
+    public String name() {
+        return subject;
+    }
+
+    @Override
+    public RepositoryHandle repository(String repoName) {
+        return new RepositoryHandleImpl(bintrayHandle, this, repoName);
+    }
+
+    @Override
+    public Subject get() throws IOException, BintrayCallException {
+        HttpResponse response = bintrayHandle.get("users/" + subject, null);
+        SubjectDetails subjectDetails;
+        InputStream jsonContentStream = response.getEntity().getContent();
+        ObjectMapper mapper = ObjectMapperHelper.objectMapper;
+        try {
+            subjectDetails = mapper.readValue(jsonContentStream, SubjectDetails.class);
+        } catch (IOException e) {
+            log.error("Can't process the json file: " + e.getMessage());
+            throw e;
+        }
+        return new SubjectImpl(subjectDetails);
+    }
+}
diff --git a/impl/src/main/java/com/jfrog/bintray/client/impl/handle/VersionHandleImpl.java b/impl/src/main/java/com/jfrog/bintray/client/impl/handle/VersionHandleImpl.java
new file mode 100644
index 0000000..51596a0
--- /dev/null
+++ b/impl/src/main/java/com/jfrog/bintray/client/impl/handle/VersionHandleImpl.java
@@ -0,0 +1,220 @@
+package com.jfrog.bintray.client.impl.handle;
+
+import com.jfrog.bintray.client.api.BintrayCallException;
+import com.jfrog.bintray.client.api.MultipleBintrayCallException;
+import com.jfrog.bintray.client.api.details.Attribute;
+import com.jfrog.bintray.client.api.details.ObjectMapperHelper;
+import com.jfrog.bintray.client.api.details.VersionDetails;
+import com.jfrog.bintray.client.api.handle.PackageHandle;
+import com.jfrog.bintray.client.api.handle.VersionHandle;
+import com.jfrog.bintray.client.api.model.Version;
+import com.jfrog.bintray.client.impl.model.VersionImpl;
+import org.apache.commons.io.IOUtils;
+import org.apache.http.HttpResponse;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author Dan Feldman
+ */
+class VersionHandleImpl implements VersionHandle {
+    private static final Logger log = LoggerFactory.getLogger(VersionHandleImpl.class);
+    private static final String GPG_SIGN_HEADER = "X-GPG-PASSPHRASE";
+    private BintrayImpl bintrayHandle;
+    private String name;
+    private PackageHandle packageHandle;
+
+
+    public VersionHandleImpl(BintrayImpl bintrayHandle, PackageHandle packageHandle, String versionName) {
+        this.bintrayHandle = bintrayHandle;
+        this.packageHandle = packageHandle;
+        this.name = versionName;
+    }
+
+    @Override
+    public PackageHandle pkg() {
+        return packageHandle;
+    }
+
+    @Override
+    public String name() {
+        return name;
+    }
+
+    @Override
+    public Version get() throws BintrayCallException, IOException {
+        HttpResponse response = bintrayHandle.get(getVersionUri(), null);
+        VersionDetails versionDetails;
+        ObjectMapper mapper = ObjectMapperHelper.objectMapper;
+        try {
+            InputStream jsonContentStream = response.getEntity().getContent();
+            versionDetails = mapper.readValue(jsonContentStream, VersionDetails.class);
+        } catch (IOException e) {
+            log.debug("{}", e);
+            log.error("Can't process the json file: {}", e.getMessage());
+            throw e;
+        }
+        return new VersionImpl(versionDetails);
+    }
+
+    @Override
+    public VersionHandle update(VersionDetails versionDetails) throws IOException, BintrayCallException {
+        Map<String, String> headers = new HashMap<>();
+        String jsonContent = VersionImpl.getCreateUpdateJson(versionDetails);
+        BintrayImpl.addContentTypeJsonHeader(headers);
+        bintrayHandle.patch(getVersionUri(), headers, IOUtils.toInputStream(jsonContent));
+
+        return this.updateAttributes(versionDetails);
+    }
+
+    @Override
+    public VersionHandle delete() throws BintrayCallException {
+        bintrayHandle.delete(getVersionUri(), null);
+        return this;
+    }
+
+    @Override
+    public boolean exists() throws BintrayCallException {
+        try {
+            bintrayHandle.head(getVersionUri(), null);
+        } catch (BintrayCallException e) {
+            if (e.getStatusCode() == 404) {
+                return false;
+            }
+            throw e;
+        }
+        return true;
+    }
+
+    @Override
+    public VersionHandle setAttributes(VersionDetails versionDetails) throws IOException, BintrayCallException {
+        return setAttributes(versionDetails.getAttributes());
+    }
+
+    @Override
+    public VersionHandle setAttributes(List<Attribute> attributes) throws IOException, BintrayCallException {
+        if (attributes == null) {
+            return this;
+        }
+        Map<String, String> headers = new HashMap<>();
+        String jsonContent = Attribute.getJsonFromAttributeList(attributes);
+        BintrayImpl.addContentTypeJsonHeader(headers);
+        bintrayHandle.post(getVersionUri() + "/attributes", headers, IOUtils.toInputStream(jsonContent));
+        return this;
+    }
+
+    @Override
+    public VersionHandle updateAttributes(VersionDetails versionDetails) throws IOException, BintrayCallException {
+        return updateAttributes(versionDetails.getAttributes());
+    }
+
+    @Override
+    public VersionHandle updateAttributes(List<Attribute> attributes) throws IOException, BintrayCallException {
+        if (attributes == null) {
+            return this;
+        }
+        Map<String, String> headers = new HashMap<>();
+        String jsonContent = Attribute.getJsonFromAttributeList(attributes);
+        BintrayImpl.addContentTypeJsonHeader(headers);
+        bintrayHandle.patch(getVersionUri() + "/attributes", headers, IOUtils.toInputStream(jsonContent));
+        return this;
+    }
+
+    @Override
+    public VersionHandle upload(Map<String, InputStream> content) throws MultipleBintrayCallException {
+        Map<String, InputStream> uriConvertedContent = new HashMap<>();
+        for (String path : content.keySet()) {
+            uriConvertedContent.put(getCurrentVersionContentUri() + path, content.get(path));
+        }
+
+        bintrayHandle.putBinary(uriConvertedContent, null);
+        return this;
+    }
+
+    @Override
+    public VersionHandle upload(String path, InputStream content) throws BintrayCallException {
+        bintrayHandle.putBinary(getCurrentVersionContentUri() + path, null, content);
+        return this;
+    }
+
+
+    @Override
+    public VersionHandle publish() throws BintrayCallException {
+        bintrayHandle.post(getCurrentVersionContentUri() + "/publish", null);
+        return this;
+    }
+
+    @Override
+    public VersionHandle discard() throws BintrayCallException {
+        Map<String, String> headers = new HashMap<>();
+        BintrayImpl.addContentTypeJsonHeader(headers);
+        String discard = "{\n\"discard\":true\n}";
+        bintrayHandle.post(getCurrentVersionContentUri() + "/publish", headers, IOUtils.toInputStream(discard));
+        return this;
+    }
+
+
+    @Override
+    public VersionHandle sign(int fileCount) throws BintrayCallException {
+        return sign(null, fileCount);
+    }
+
+    @Override
+    public VersionHandle sign(String passphrase, int fileCount) throws BintrayCallException {
+        Map<String, String> headers = new HashMap<>();
+        if (!(passphrase == null) && !passphrase.equals("")) {
+            headers.put(GPG_SIGN_HEADER, passphrase);
+        }
+        bintrayHandle.sign(getCurrentVersionGpgUri(), headers, fileCount);
+        return null;
+    }
+
+    /**
+     * @return packages/$owner/$repo/$package/versions/$version/
+     */
+    @Override
+    public String getVersionUri() {
+        return "packages" + getCurrentVersionFullyQualifiedUri();
+    }
+
+    /**
+     * @return gpg/$owner/$repo/$package/versions/$version/
+     */
+    public String getCurrentVersionGpgUri() {
+        return "gpg" + getCurrentVersionFullyQualifiedUri();
+    }
+
+    /**
+     * @return content/$owner/$repo/$package/$version/
+     */
+    public String getCurrentVersionContentUri() {
+        return String.format("content/%s/%s/%s/%s/", packageHandle.repository().owner().name(),
+                packageHandle.repository().name(), packageHandle.name(), name);
+    }
+
+    /**
+     * @return $owner/$repo/$package/versions/$version/
+     */
+    private String getCurrentVersionFullyQualifiedUri() {
+        return String.format("/%s/%s/%s/versions/%s/", packageHandle.repository().owner().name(),
+                packageHandle.repository().name(), packageHandle.name(), name);
+    }
+
+    private VersionHandle upload(List<File> content, boolean recursive) {
+        // TODO: implement upload of files
+        throw new UnsupportedOperationException("Not yet implemented");
+    }
+
+    private VersionHandle upload(File directory, boolean recursive) {
+        // TODO: implement upload of directories
+        throw new UnsupportedOperationException("Not yet implemented");
+    }
+}
diff --git a/impl/src/main/java/com/jfrog/bintray/client/impl/model/PackageImpl.java b/impl/src/main/java/com/jfrog/bintray/client/impl/model/PackageImpl.java
new file mode 100644
index 0000000..feda710
--- /dev/null
+++ b/impl/src/main/java/com/jfrog/bintray/client/impl/model/PackageImpl.java
@@ -0,0 +1,182 @@
+package com.jfrog.bintray.client.impl.model;
+
+import com.jfrog.bintray.client.api.details.ObjectMapperHelper;
+import com.jfrog.bintray.client.api.details.PackageDetails;
+import com.jfrog.bintray.client.api.model.Pkg;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.joda.time.DateTime;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * @author Noam Y. Tenne
+ */
+public class PackageImpl implements Pkg {
+    private static final Logger log = LoggerFactory.getLogger(PackageImpl.class);
+    private String name;
+    private String repository;
+    private String owner;
+    private String description;
+    private List<String> labels;
+    private List<String> attributeNames;
+    private Integer rating;
+    private Integer ratingCount;
+    private Integer followersCount;
+    private DateTime created;
+    private List<String> versions;
+    private String latestVersion;
+    private DateTime updated;
+    private List<String> linkedToRepos;
+    private List<String> systemIds;
+
+    public PackageImpl(PackageDetails packageDetails) {
+        this.name = packageDetails.getName();
+        this.repository = packageDetails.getRepo();
+        this.owner = packageDetails.getOwner();
+        this.description = packageDetails.getDescription();
+        this.labels = packageDetails.getLabels();
+        this.attributeNames = packageDetails.getAttributeNames();
+        this.ratingCount = packageDetails.getRatingCount();
+        this.followersCount = packageDetails.getFollowersCount();
+        this.created = packageDetails.getCreated();
+        this.versions = packageDetails.getVersions();
+        this.latestVersion = packageDetails.getLatestVersion();
+        this.updated = packageDetails.getUpdated();
+        this.linkedToRepos = packageDetails.getLinkedRepos();
+        this.systemIds = packageDetails.getSystemIds();
+    }
+
+    public PackageImpl(String name, String repository, String owner, String description, List<String> labels,
+                       List<String> attributeNames, Integer rating, Integer ratingCount, Integer followersCount,
+                       DateTime created, List<String> versions, String latestVersion, DateTime updated,
+                       List<String> linkedToRepos, List<String> systemIds) {
+        this.name = name;
+        this.repository = repository;
+        this.owner = owner;
+        this.description = description;
+        this.labels = labels;
+        this.attributeNames = attributeNames;
+        this.rating = rating;
+        this.ratingCount = ratingCount;
+        this.followersCount = followersCount;
+        this.created = created;
+        this.versions = versions;
+        this.latestVersion = latestVersion;
+        this.updated = updated;
+        this.linkedToRepos = linkedToRepos;
+        this.systemIds = systemIds;
+    }
+
+    public static String getCreateUpdateJson(PackageDetails packageDetails) throws IOException {
+        ObjectMapper mapper = ObjectMapperHelper.objectMapper;
+        String jsonContent;
+        try {
+            jsonContent = mapper.writeValueAsString(packageDetails);
+        } catch (IOException e) {
+            log.error("Can't process the json file: " + e.getMessage());
+            throw e;
+        }
+        return jsonContent;
+    }
+
+    public String name() {
+        return name;
+    }
+
+    public String repository() {
+        return repository;
+    }
+
+    public String owner() {
+        return owner;
+    }
+
+    public String description() {
+        return description;
+    }
+
+    public List<String> labels() {
+        return labels;
+    }
+
+    public List<String> attributeNames() {
+        return attributeNames;
+    }
+
+    public Integer rating() {
+        return rating;
+    }
+
+    public Integer ratingCount() {
+        return ratingCount;
+    }
+
+    public Integer followersCount() {
+        return followersCount;
+    }
+
+    public DateTime created() {
+        return created;
+    }
+
+    public List<String> versions() {
+        return versions;
+    }
+
+    public String latestVersion() {
+        return latestVersion;
+    }
+
+    public DateTime updated() {
+        return updated;
+    }
+
+    public List<String> linkedToRepos() {
+        return linkedToRepos;
+    }
+
+    public List<String> systemIds() {
+        return systemIds;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        PackageImpl aPackage = (PackageImpl) o;
+
+        if (!name.equals(aPackage.name)) return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return name.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return "Package{" +
+                "getName='" + name + '\'' +
+                ", repository='" + repository + '\'' +
+                ", owner='" + owner + '\'' +
+                ", description='" + description + '\'' +
+                ", labels=" + labels +
+                ", attributeNames=" + attributeNames +
+                ", rating=" + rating +
+                ", ratingCount=" + ratingCount +
+                ", followersCount=" + followersCount +
+                ", created=" + created +
+                ", versions=" + versions +
+                ", latestVersion='" + latestVersion + '\'' +
+                ", updated=" + updated +
+                ", linkedToRepos='" + linkedToRepos + '\'' +
+                ", systemIds=" + systemIds +
+                '}';
+    }
+}
diff --git a/impl/src/main/java/com/jfrog/bintray/client/impl/model/RepositoryImpl.java b/impl/src/main/java/com/jfrog/bintray/client/impl/model/RepositoryImpl.java
new file mode 100644
index 0000000..56d176d
--- /dev/null
+++ b/impl/src/main/java/com/jfrog/bintray/client/impl/model/RepositoryImpl.java
@@ -0,0 +1,65 @@
+package com.jfrog.bintray.client.impl.model;
+
+import com.jfrog.bintray.client.api.details.RepositoryDetails;
+import com.jfrog.bintray.client.api.model.Repository;
+import org.joda.time.DateTime;
+
+import java.util.List;
+
+/**
+ * @author Noam Y. Tenne
+ */
+public class RepositoryImpl implements Repository {
+
+    private String name;
+    private String owner;
+    private String desc;
+    private List<String> labels;
+    private DateTime created;
+    private Integer packageCount;
+
+    public RepositoryImpl() {
+    }
+
+    public RepositoryImpl(RepositoryDetails repositoryDetails) {
+        this.name = repositoryDetails.getName();
+        this.owner = repositoryDetails.getOwner();
+        this.desc = repositoryDetails.getDescription();
+        this.labels = repositoryDetails.getLabels();
+        this.created = repositoryDetails.getCreated();
+        this.packageCount = repositoryDetails.getPackageCount();
+    }
+
+    public RepositoryImpl(String name, String owner, String desc, List<String> labels, DateTime created, Integer packageCount) {
+        this.name = name;
+        this.owner = owner;
+        this.desc = desc;
+        this.labels = labels;
+        this.created = created;
+        this.packageCount = packageCount;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getOwner() {
+        return owner;
+    }
+
+    public String getDesc() {
+        return desc;
+    }
+
+    public List<String> getLabels() {
+        return labels;
+    }
+
+    public DateTime getCreated() {
+        return created;
+    }
+
+    public Integer getPackageCount() {
+        return packageCount;
+    }
+}
diff --git a/impl/src/main/java/com/jfrog/bintray/client/impl/model/SubjectImpl.java b/impl/src/main/java/com/jfrog/bintray/client/impl/model/SubjectImpl.java
new file mode 100644
index 0000000..b1ed07c
--- /dev/null
+++ b/impl/src/main/java/com/jfrog/bintray/client/impl/model/SubjectImpl.java
@@ -0,0 +1,80 @@
+package com.jfrog.bintray.client.impl.model;
+
+import com.jfrog.bintray.client.api.details.SubjectDetails;
+import com.jfrog.bintray.client.api.model.Subject;
+import org.joda.time.DateTime;
+
+import java.util.Collection;
+
+/**
+ * @author Noam Y. Tenne
+ */
+public class SubjectImpl implements Subject {
+
+    private String name;
+    private String fullName;
+    private String gravatarId;
+    private Collection<String> repositories;
+    private Collection<String> organizations;
+    private Integer followersCount;
+    private DateTime registered;
+    private Long quotaUsedBytes;
+
+    public SubjectImpl() {
+    }
+
+    public SubjectImpl(SubjectDetails subjectDetails) {
+        this.name = subjectDetails.getName();
+        this.fullName = subjectDetails.getFullName();
+        this.gravatarId = subjectDetails.getGravatarId();
+        this.repositories = subjectDetails.getRepos();
+        this.organizations = subjectDetails.getOrganizations();
+        this.followersCount = subjectDetails.getFollowersCount();
+        this.registered = subjectDetails.getRegistered();
+        this.quotaUsedBytes = subjectDetails.getQuotaUsedBytes();
+    }
+
+    public SubjectImpl(String name, String fullName, String gravatarId, Collection<String> repositories,
+                       Collection<String> organizations, Integer followersCount, DateTime registered, Long quotaUsedBytes) {
+        this.name = name;
+        this.fullName = fullName;
+        this.gravatarId = gravatarId;
+        this.repositories = repositories;
+        this.organizations = organizations;
+        this.followersCount = followersCount;
+        this.registered = registered;
+        this.quotaUsedBytes = quotaUsedBytes;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getFullName() {
+        return fullName;
+    }
+
+    public String getGravatarId() {
+        return gravatarId;
+    }
+
+    public Collection<String> getRepositories() {
+        return repositories;
+    }
+
+    public Collection<String> getOrganizations() {
+        return organizations;
+    }
+
+    public Integer getFollowersCount() {
+        return followersCount;
+    }
+
+    public DateTime getRegistered() {
+        return registered;
+    }
+
+    public Long getQuotaUsedBytes() {
+        return quotaUsedBytes;
+    }
+}
diff --git a/impl/src/main/java/com/jfrog/bintray/client/impl/model/VersionImpl.java b/impl/src/main/java/com/jfrog/bintray/client/impl/model/VersionImpl.java
new file mode 100644
index 0000000..133ac7d
--- /dev/null
+++ b/impl/src/main/java/com/jfrog/bintray/client/impl/model/VersionImpl.java
@@ -0,0 +1,162 @@
+package com.jfrog.bintray.client.impl.model;
+
+import com.jfrog.bintray.client.api.details.ObjectMapperHelper;
+import com.jfrog.bintray.client.api.details.VersionDetails;
+import com.jfrog.bintray.client.api.model.Version;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.joda.time.DateTime;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * @author Noam Y. Tenne
+ */
+public class VersionImpl implements Version {
+    private static final Logger log = LoggerFactory.getLogger(VersionImpl.class);
+
+    private String name;
+    private String description;
+    private String pkg;
+    private String repository;
+    private String owner;
+    private List<String> labels;
+    private List<String> attributeNames;
+    private DateTime created;
+    private DateTime updated;
+    private DateTime released;
+    private Integer ordinal;
+    private String vcsTag;
+
+    public VersionImpl() {
+    }
+
+    public VersionImpl(VersionDetails versionDetails) {
+        this.name = versionDetails.getName();
+        this.description = versionDetails.getDescription();
+        this.pkg = versionDetails.getPkg();
+        this.repository = versionDetails.getRepo();
+        this.owner = versionDetails.getOwner();
+        this.labels = versionDetails.getLabels();
+        this.attributeNames = versionDetails.getAttributeNames();
+        this.created = versionDetails.getCreated();
+        this.updated = versionDetails.getUpdated();
+        this.released = versionDetails.getReleased();
+        this.ordinal = versionDetails.getOrdinal();
+        this.vcsTag = versionDetails.getVcsTag();
+    }
+
+    public VersionImpl(String name, String description, String pkg, String repository, String owner, List<String> labels,
+                       List<String> attributeNames, DateTime created, DateTime updated, DateTime released, int ordinal, String vcsTag) {
+        this.name = name;
+        this.description = description;
+        this.pkg = pkg;
+        this.repository = repository;
+        this.owner = owner;
+        this.labels = labels;
+        this.attributeNames = attributeNames;
+        this.created = created;
+        this.updated = updated;
+        this.released = released;
+        this.ordinal = ordinal;
+        this.vcsTag = vcsTag;
+    }
+
+    public static String getCreateUpdateJson(VersionDetails versionDetails) throws IOException {
+        ObjectMapper mapper = ObjectMapperHelper.objectMapper;
+        String jsonContent;
+        try {
+            jsonContent = mapper.writeValueAsString(versionDetails);
+        } catch (IOException e) {
+            log.error("Can't process the json file: " + e.getMessage());
+            log.debug("{}", e);
+            throw e;
+        }
+        return jsonContent;
+    }
+
+    public String name() {
+        return name;
+    }
+
+    public String description() {
+        return description;
+    }
+
+    public String pkg() {
+        return pkg;
+    }
+
+    public String repository() {
+        return repository;
+    }
+
+    public String owner() {
+        return owner;
+    }
+
+    public List<String> labels() {
+        return labels;
+    }
+
+    public List<String> attributeNames() {
+        return attributeNames;
+    }
+
+    public DateTime created() {
+        return created;
+    }
+
+    public DateTime updated() {
+        return updated;
+    }
+
+    public DateTime released() {
+        return released;
+    }
+
+    public Integer ordinal() {
+        return ordinal;
+    }
+
+    @Override
+    public String vcsTag() {
+        return vcsTag;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        VersionImpl version = (VersionImpl) o;
+
+        if (!name.equals(version.name)) return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return name.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return "Version{" +
+                "getName='" + name + '\'' +
+                ", description='" + description + '\'' +
+                ", pkg='" + pkg + '\'' +
+                ", repository='" + repository + '\'' +
+                ", owner='" + owner + '\'' +
+                ", labels=" + labels +
+                ", attributeNames=" + attributeNames +
+                ", created=" + created +
+                ", updated=" + updated +
+                ", released=" + released +
+                ", ordinal=" + ordinal +
+                '}';
+    }
+}
diff --git a/impl/src/main/java/com/jfrog/bintray/client/impl/util/URI.java b/impl/src/main/java/com/jfrog/bintray/client/impl/util/URI.java
new file mode 100644
index 0000000..3663c45
--- /dev/null
+++ b/impl/src/main/java/com/jfrog/bintray/client/impl/util/URI.java
@@ -0,0 +1,1086 @@
+/*
+ * $HeadURL: https://svn.apache.org/repos/asf/jakarta/httpcomponents/oac.hc3x/tags/HTTPCLIENT_3_1/src/java/org/apache/commons/httpclient/URI.java $
+ * $Revision: 564973 $
+ * $Date: 2007-08-11 22:51:47 +0200 (Sat, 11 Aug 2007) $
+ *
+ * ====================================================================
+ *
+ *  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 software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+/*
+ * Based on apache-httpclient 3.1 org.apache.commons.httpclient.URI.java
+ * Additional contributors:
+ *    JFrog Ltd.
+ */
+package com.jfrog.bintray.client.impl.util;
+
+import org.apache.commons.codec.DecoderException;
+import org.apache.commons.codec.net.URLCodec;
+import org.apache.http.HttpException;
+import org.apache.http.util.EncodingUtils;
+
+import java.util.BitSet;
+
+/**
+ * The interface for the URI(Uniform Resource Identifiers) version of RFC 2396.
+ * This class has the purpose of supportting of parsing a URI reference to
+ * extend any specific protocols, the character encoding of the protocol to
+ * be transported and the charset of the document.
+ * <p/>
+ * A URI is always in an "escaped" form, since escaping or unescaping a
+ * completed URI might change its semantics.
+ * <p/>
+ * Implementers should be careful not to escape or unescape the same string
+ * more than once, since unescaping an already unescaped string might lead to
+ * misinterpreting a percent data character as another escaped character,
+ * or vice versa in the case of escaping an already escaped string.
+ * <p/>
+ * In order to avoid these problems, data types used as follows:
+ * <p><blockquote><pre>
+ *   URI character sequence: char
+ *   octet sequence: byte
+ *   original character sequence: String
+ * </pre></blockquote><p>
+ * <p/>
+ * So, a URI is a sequence of characters as an array of a char type, which
+ * is not always represented as a sequence of octets as an array of byte.
+ * <p/>
+ * <p/>
+ * URI Syntactic Components
+ * <p><blockquote><pre>
+ * - In general, written as follows:
+ *   Absolute URI = <scheme&gt:<scheme-specific-part>
+ *   Generic URI = <scheme>://<authority><path>?<query>
+ * <p/>
+ * - Syntax
+ *   absoluteURI   = scheme ":" ( hier_part | opaque_part )
+ *   hier_part     = ( net_path | abs_path ) [ "?" query ]
+ *   net_path      = "//" authority [ abs_path ]
+ *   abs_path      = "/"  path_segments
+ * </pre></blockquote><p>
+ * <p/>
+ * The following examples illustrate URI that are in common use.
+ * <pre>
+ * ftp://ftp.is.co.za/rfc/rfc1808.txt
+ *    -- ftp scheme for File Transfer Protocol services
+ * gopher://spinaltap.micro.umn.edu/00/Weather/California/Los%20Angeles
+ *    -- gopher scheme for Gopher and Gopher+ Protocol services
+ * http://www.math.uio.no/faq/compression-faq/part1.html
+ *    -- http scheme for Hypertext Transfer Protocol services
+ * mailto:mduerst at ifi.unizh.ch
+ *    -- mailto scheme for electronic mail addresses
+ * news:comp.infosystems.www.servers.unix
+ *    -- news scheme for USENET news groups and articles
+ * telnet://melvyl.ucop.edu/
+ *    -- telnet scheme for interactive services via the TELNET Protocol
+ * </pre>
+ * Please, notice that there are many modifications from URL(RFC 1738) and
+ * relative URL(RFC 1808).
+ * <p/>
+ * <b>The expressions for a URI</b>
+ * <p><pre>
+ * For escaped URI forms
+ *  - URI(char[]) // constructor
+ *  - char[] getRawXxx() // method
+ *  - String getEscapedXxx() // method
+ *  - String toString() // method
+ * <p/>
+ * For unescaped URI forms
+ *  - URI(String) // constructor
+ *  - String getXXX() // method
+ * </pre><p>
+ *
+ * @author <a href="mailto:jericho at apache.org">Sung-Gu</a>
+ * @author <a href="mailto:mbowler at GargoyleSoftware.com">Mike Bowler</a>
+ * @version $Revision: 564973 $ $Date: 2002/03/14 15:14:01
+ */
+public class URI {
+
+    /**
+     * BitSet for within the userinfo component like user and password.
+     */
+    public static final BitSet within_userinfo = new BitSet(256);
+    /**
+     * BitSet for control.
+     */
+    public static final BitSet control = new BitSet(256);
+    /**
+     * BitSet for space.
+     */
+    public static final BitSet space = new BitSet(256);
+    /**
+     * BitSet for delims.
+     */
+    public static final BitSet delims = new BitSet(256);
+    /**
+     * BitSet for unwise.
+     */
+    public static final BitSet unwise = new BitSet(256);
+    /**
+     * Disallowed rel_path before escaping.
+     */
+    public static final BitSet disallowed_rel_path = new BitSet(256);
+    /**
+     * Disallowed opaque_part before escaping.
+     */
+    public static final BitSet disallowed_opaque_part = new BitSet(256);
+    /**
+     * Those characters that are allowed for the authority component.
+     */
+    public static final BitSet allowed_authority = new BitSet(256);
+    /**
+     * Those characters that are allowed for the opaque_part.
+     */
+    public static final BitSet allowed_opaque_part = new BitSet(256);
+    /**
+     * Those characters that are allowed for the reg_name.
+     */
+    public static final BitSet allowed_reg_name = new BitSet(256);
+    /**
+     * Those characters that are allowed for the userinfo component.
+     */
+    public static final BitSet allowed_userinfo = new BitSet(256);
+    /**
+     * Those characters that are allowed for within the userinfo component.
+     */
+    public static final BitSet allowed_within_userinfo = new BitSet(256);
+    /**
+     * Those characters that are allowed for the IPv6reference component.
+     * The characters '[', ']' in IPv6reference should be excluded.
+     */
+    public static final BitSet allowed_IPv6reference = new BitSet(256);
+    /**
+     * Those characters that are allowed for the host component.
+     * The characters '[', ']' in IPv6reference should be excluded.
+     */
+    public static final BitSet allowed_host = new BitSet(256);
+    /**
+     * Those characters that are allowed for the authority component.
+     */
+    public static final BitSet allowed_within_authority = new BitSet(256);
+    /**
+     * Those characters that are allowed for the abs_path.
+     */
+    public static final BitSet allowed_abs_path = new BitSet(256);
+    /**
+     * Those characters that are allowed for the rel_path.
+     */
+    public static final BitSet allowed_rel_path = new BitSet(256);
+    /**
+     * Those characters that are allowed within the path.
+     */
+    public static final BitSet allowed_within_path = new BitSet(256);
+    /**
+     * Those characters that are allowed for the query component.
+     */
+    public static final BitSet allowed_query = new BitSet(256);
+    /**
+     * Those characters that are allowed within the query component.
+     */
+    public static final BitSet allowed_within_query = new BitSet(256);
+    /**
+     * Those characters that are allowed for the fragment component.
+     */
+    public static final BitSet allowed_fragment = new BitSet(256);
+    /**
+     * The percent "%" character always has the reserved purpose of being the
+     * escape indicator, it must be escaped as "%25" in order to be used as
+     * data within a URI.
+     */
+    protected static final BitSet percent = new BitSet(256);
+    /**
+     * BitSet for digit.
+     * <p><blockquote><pre>
+     * digit    = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" |
+     *            "8" | "9"
+     * </pre></blockquote><p>
+     */
+    protected static final BitSet digit = new BitSet(256);
+    /**
+     * BitSet for alpha.
+     * <p><blockquote><pre>
+     * alpha         = lowalpha | upalpha
+     * </pre></blockquote><p>
+     */
+    protected static final BitSet alpha = new BitSet(256);
+    /**
+     * BitSet for alphanum (join of alpha & digit).
+     * <p><blockquote><pre>
+     *  alphanum      = alpha | digit
+     * </pre></blockquote><p>
+     */
+    protected static final BitSet alphanum = new BitSet(256);
+    /**
+     * BitSet for hex.
+     * <p><blockquote><pre>
+     * hex           = digit | "A" | "B" | "C" | "D" | "E" | "F" |
+     *                         "a" | "b" | "c" | "d" | "e" | "f"
+     * </pre></blockquote><p>
+     */
+    protected static final BitSet hex = new BitSet(256);
+    /**
+     * BitSet for escaped.
+     * <p><blockquote><pre>
+     * escaped       = "%" hex hex
+     * </pre></blockquote><p>
+     */
+    protected static final BitSet escaped = new BitSet(256);
+    /**
+     * BitSet for mark.
+     * <p><blockquote><pre>
+     * mark          = "-" | "_" | "." | "!" | "~" | "*" | "'" |
+     *                 "(" | ")"
+     * </pre></blockquote><p>
+     */
+    protected static final BitSet mark = new BitSet(256);
+    /**
+     * Data characters that are allowed in a URI but do not have a reserved
+     * purpose are called unreserved.
+     * <p><blockquote><pre>
+     * unreserved    = alphanum | mark
+     * </pre></blockquote><p>
+     */
+    protected static final BitSet unreserved = new BitSet(256);
+    /**
+     * BitSet for reserved.
+     * <p><blockquote><pre>
+     * reserved      = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" |
+     *                 "$" | ","
+     * </pre></blockquote><p>
+     */
+    protected static final BitSet reserved = new BitSet(256);
+    /**
+     * BitSet for uric.
+     * <p><blockquote><pre>
+     * uric          = reserved | unreserved | escaped
+     * </pre></blockquote><p>
+     */
+    protected static final BitSet uric = new BitSet(256);
+    /**
+     * BitSet for fragment (alias for uric).
+     * <p><blockquote><pre>
+     * fragment      = *uric
+     * </pre></blockquote><p>
+     */
+    protected static final BitSet fragment = uric;
+    /**
+     * BitSet for query (alias for uric).
+     * <p><blockquote><pre>
+     * query         = *uric
+     * </pre></blockquote><p>
+     */
+    protected static final BitSet query = uric;
+    /**
+     * BitSet for pchar.
+     * <p><blockquote><pre>
+     * pchar         = unreserved | escaped |
+     *                 ":" | "@" | "&" | "=" | "+" | "$" | ","
+     * </pre></blockquote><p>
+     */
+    protected static final BitSet pchar = new BitSet(256);
+    /**
+     * BitSet for param (alias for pchar).
+     * <p><blockquote><pre>
+     * param         = *pchar
+     * </pre></blockquote><p>
+     */
+    protected static final BitSet param = pchar;
+    /**
+     * BitSet for segment.
+     * <p><blockquote><pre>
+     * segment       = *pchar *( ";" param )
+     * </pre></blockquote><p>
+     */
+    protected static final BitSet segment = new BitSet(256);
+    /**
+     * BitSet for path segments.
+     * <p><blockquote><pre>
+     * path_segments = segment *( "/" segment )
+     * </pre></blockquote><p>
+     */
+    protected static final BitSet path_segments = new BitSet(256);
+    /**
+     * URI absolute path.
+     * <p><blockquote><pre>
+     * abs_path      = "/"  path_segments
+     * </pre></blockquote><p>
+     */
+    protected static final BitSet abs_path = new BitSet(256);
+    /**
+     * URI bitset for encoding typical non-slash characters.
+     * <p><blockquote><pre>
+     * uric_no_slash = unreserved | escaped | ";" | "?" | ":" | "@" |
+     *                 "&" | "=" | "+" | "$" | ","
+     * </pre></blockquote><p>
+     */
+    protected static final BitSet uric_no_slash = new BitSet(256);
+    /**
+     * URI bitset that combines uric_no_slash and uric.
+     * <p><blockquote><pre>
+     * opaque_part   = uric_no_slash *uric
+     * </pre></blockquote><p>
+     */
+    protected static final BitSet opaque_part = new BitSet(256);
+    /**
+     * URI bitset that combines absolute path and opaque part.
+     * <p><blockquote><pre>
+     * path          = [ abs_path | opaque_part ]
+     * </pre></blockquote><p>
+     */
+    protected static final BitSet path = new BitSet(256);
+    /**
+     * Port, a logical alias for digit.
+     */
+    protected static final BitSet port = digit;
+    /**
+     * Bitset that combines digit and dot fo IPv$address.
+     * <p><blockquote><pre>
+     * IPv4address   = 1*digit "." 1*digit "." 1*digit "." 1*digit
+     * </pre></blockquote><p>
+     */
+    protected static final BitSet IPv4address = new BitSet(256);
+    /**
+     * RFC 2373.
+     * <p><blockquote><pre>
+     * IPv6address = hexpart [ ":" IPv4address ]
+     * </pre></blockquote><p>
+     */
+    protected static final BitSet IPv6address = new BitSet(256);
+    /**
+     * RFC 2732, 2373.
+     * <p><blockquote><pre>
+     * IPv6reference   = "[" IPv6address "]"
+     * </pre></blockquote><p>
+     */
+    protected static final BitSet IPv6reference = new BitSet(256);
+    /**
+     * BitSet for toplabel.
+     * <p><blockquote><pre>
+     * toplabel      = alpha | alpha *( alphanum | "-" ) alphanum
+     * </pre></blockquote><p>
+     */
+    protected static final BitSet toplabel = new BitSet(256);
+    /**
+     * BitSet for hostname.
+     * <p><blockquote><pre>
+     * hostname      = *( domainlabel "." ) toplabel [ "." ]
+     * </pre></blockquote><p>
+     */
+    protected static final BitSet hostname = new BitSet(256);
+    /**
+     * BitSet for host.
+     * <p><blockquote><pre>
+     * host          = hostname | IPv4address | IPv6reference
+     * </pre></blockquote><p>
+     */
+    protected static final BitSet host = new BitSet(256);
+    /**
+     * BitSet for hostport.
+     * <p><blockquote><pre>
+     * hostport      = host [ ":" port ]
+     * </pre></blockquote><p>
+     */
+    protected static final BitSet hostport = new BitSet(256);
+    /**
+     * Bitset for userinfo.
+     * <p><blockquote><pre>
+     * userinfo      = *( unreserved | escaped |
+     *                    ";" | ":" | "&" | "=" | "+" | "$" | "," )
+     * </pre></blockquote><p>
+     */
+    protected static final BitSet userinfo = new BitSet(256);
+    /**
+     * Bitset for server.
+     * <p><blockquote><pre>
+     * server        = [ [ userinfo "@" ] hostport ]
+     * </pre></blockquote><p>
+     */
+    protected static final BitSet server = new BitSet(256);
+    /**
+     * BitSet for reg_name.
+     * <p><blockquote><pre>
+     * reg_name      = 1*( unreserved | escaped | "$" | "," |
+     *                     ";" | ":" | "@" | "&" | "=" | "+" )
+     * </pre></blockquote><p>
+     */
+    protected static final BitSet reg_name = new BitSet(256);
+    /**
+     * BitSet for authority.
+     * <p><blockquote><pre>
+     * authority     = server | reg_name
+     * </pre></blockquote><p>
+     */
+    protected static final BitSet authority = new BitSet(256);
+    /**
+     * BitSet for scheme.
+     * <p><blockquote><pre>
+     * scheme        = alpha *( alpha | digit | "+" | "-" | "." )
+     * </pre></blockquote><p>
+     */
+    protected static final BitSet scheme = new BitSet(256);
+    /**
+     * BitSet for rel_segment.
+     * <p><blockquote><pre>
+     * rel_segment   = 1*( unreserved | escaped |
+     *                     ";" | "@" | "&" | "=" | "+" | "$" | "," )
+     * </pre></blockquote><p>
+     */
+    protected static final BitSet rel_segment = new BitSet(256);
+    /**
+     * BitSet for rel_path.
+     * <p><blockquote><pre>
+     * rel_path      = rel_segment [ abs_path ]
+     * </pre></blockquote><p>
+     */
+    protected static final BitSet rel_path = new BitSet(256);
+    /**
+     * BitSet for net_path.
+     * <p><blockquote><pre>
+     * net_path      = "//" authority [ abs_path ]
+     * </pre></blockquote><p>
+     */
+    protected static final BitSet net_path = new BitSet(256);
+    /**
+     * BitSet for hier_part.
+     * <p><blockquote><pre>
+     * hier_part     = ( net_path | abs_path ) [ "?" query ]
+     * </pre></blockquote><p>
+     */
+    protected static final BitSet hier_part = new BitSet(256);
+    /**
+     * BitSet for relativeURI.
+     * <p><blockquote><pre>
+     * relativeURI   = ( net_path | abs_path | rel_path ) [ "?" query ]
+     * </pre></blockquote><p>
+     */
+    protected static final BitSet relativeURI = new BitSet(256);
+    /**
+     * BitSet for absoluteURI.
+     * <p><blockquote><pre>
+     * absoluteURI   = scheme ":" ( hier_part | opaque_part )
+     * </pre></blockquote><p>
+     */
+    protected static final BitSet absoluteURI = new BitSet(256);
+    /**
+     * BitSet for URI-reference.
+     * <p><blockquote><pre>
+     * URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ]
+     * </pre></blockquote><p>
+     */
+    protected static final BitSet URI_reference = new BitSet(256);
+
+    // Static initializer for percent
+    static {
+        percent.set('%');
+    }
+
+    // Static initializer for digit
+    static {
+        for (int i = '0'; i <= '9'; i++) {
+            digit.set(i);
+        }
+    }
+
+    // Static initializer for alpha
+    static {
+        for (int i = 'a'; i <= 'z'; i++) {
+            alpha.set(i);
+        }
+        for (int i = 'A'; i <= 'Z'; i++) {
+            alpha.set(i);
+        }
+    }
+
+    // Static initializer for alphanum
+    static {
+        alphanum.or(alpha);
+        alphanum.or(digit);
+    }
+
+    // Static initializer for hex
+    static {
+        hex.or(digit);
+        for (int i = 'a'; i <= 'f'; i++) {
+            hex.set(i);
+        }
+        for (int i = 'A'; i <= 'F'; i++) {
+            hex.set(i);
+        }
+    }
+
+    // Static initializer for escaped
+    static {
+        escaped.or(percent);
+        escaped.or(hex);
+    }
+
+    // Static initializer for mark
+    static {
+        mark.set('-');
+        mark.set('_');
+        mark.set('.');
+        mark.set('!');
+        mark.set('~');
+        mark.set('*');
+        mark.set('\'');
+        mark.set('(');
+        mark.set(')');
+    }
+
+    // Static initializer for unreserved
+    static {
+        unreserved.or(alphanum);
+        unreserved.or(mark);
+    }
+
+    // Static initializer for reserved
+    static {
+        reserved.set(';');
+        reserved.set('/');
+        reserved.set('?');
+        reserved.set(':');
+        reserved.set('@');
+        reserved.set('&');
+        reserved.set('=');
+        reserved.set('+');
+        reserved.set('$');
+        reserved.set(',');
+    }
+
+    // Static initializer for uric
+    static {
+        uric.or(reserved);
+        uric.or(unreserved);
+        uric.or(escaped);
+    }
+
+    // Static initializer for pchar
+    static {
+        pchar.or(unreserved);
+        pchar.or(escaped);
+        pchar.set(':');
+        pchar.set('@');
+        pchar.set('&');
+        pchar.set('=');
+        pchar.set('+');
+        pchar.set('$');
+        pchar.set(',');
+    }
+
+    // Static initializer for segment
+    static {
+        segment.or(pchar);
+        segment.set(';');
+        segment.or(param);
+    }
+
+    // Static initializer for path_segments
+    static {
+        path_segments.set('/');
+        path_segments.or(segment);
+    }
+
+    // Static initializer for abs_path
+    static {
+        abs_path.set('/');
+        abs_path.or(path_segments);
+    }
+
+    // Static initializer for uric_no_slash
+    static {
+        uric_no_slash.or(unreserved);
+        uric_no_slash.or(escaped);
+        uric_no_slash.set(';');
+        uric_no_slash.set('?');
+        uric_no_slash.set(';');
+        uric_no_slash.set('@');
+        uric_no_slash.set('&');
+        uric_no_slash.set('=');
+        uric_no_slash.set('+');
+        uric_no_slash.set('$');
+        uric_no_slash.set(',');
+    }
+
+    // Static initializer for opaque_part
+    static {
+        // it's generous. because first character must not include a slash
+        opaque_part.or(uric_no_slash);
+        opaque_part.or(uric);
+    }
+
+    // Static initializer for path
+    static {
+        path.or(abs_path);
+        path.or(opaque_part);
+    }
+
+    // ---------------------------- Characters disallowed within the URI syntax
+    // Excluded US-ASCII Characters are like control, space, delims and unwise
+
+    // Static initializer for IPv4address
+    static {
+        IPv4address.or(digit);
+        IPv4address.set('.');
+    }
+
+    // Static initializer for IPv6address reference
+    static {
+        IPv6address.or(hex); // hexpart
+        IPv6address.set(':');
+        IPv6address.or(IPv4address);
+    }
+
+    // Static initializer for IPv6reference
+    static {
+        IPv6reference.set('[');
+        IPv6reference.or(IPv6address);
+        IPv6reference.set(']');
+    }
+
+    // Static initializer for toplabel
+    static {
+        toplabel.or(alphanum);
+        toplabel.set('-');
+    }
+
+    // Static initializer for hostname
+    static {
+        hostname.or(toplabel);
+        // hostname.or(domainlabel);
+        hostname.set('.');
+    }
+
+    // Static initializer for host
+    static {
+        host.or(hostname);
+        // host.or(IPv4address);
+        host.or(IPv6reference); // IPv4address
+    }
+
+    // Static initializer for hostport
+    static {
+        hostport.or(host);
+        hostport.set(':');
+        hostport.or(port);
+    }
+
+    // Static initializer for userinfo
+    static {
+        userinfo.or(unreserved);
+        userinfo.or(escaped);
+        userinfo.set(';');
+        userinfo.set(':');
+        userinfo.set('&');
+        userinfo.set('=');
+        userinfo.set('+');
+        userinfo.set('$');
+        userinfo.set(',');
+    }
+
+    // Static initializer for within_userinfo
+    static {
+        within_userinfo.or(userinfo);
+        within_userinfo.clear(';'); // reserved within authority
+        within_userinfo.clear(':');
+        within_userinfo.clear('@');
+        within_userinfo.clear('?');
+        within_userinfo.clear('/');
+    }
+
+    // Static initializer for server
+    static {
+        server.or(userinfo);
+        server.set('@');
+        server.or(hostport);
+    }
+
+    // Static initializer for reg_name
+    static {
+        reg_name.or(unreserved);
+        reg_name.or(escaped);
+        reg_name.set('$');
+        reg_name.set(',');
+        reg_name.set(';');
+        reg_name.set(':');
+        reg_name.set('@');
+        reg_name.set('&');
+        reg_name.set('=');
+        reg_name.set('+');
+    }
+
+    // Static initializer for authority
+    static {
+        authority.or(server);
+        authority.or(reg_name);
+    }
+
+    // ----------------------- Characters allowed within and for each component
+
+    // Static initializer for scheme
+    static {
+        scheme.or(alpha);
+        scheme.or(digit);
+        scheme.set('+');
+        scheme.set('-');
+        scheme.set('.');
+    }
+
+    // Static initializer for rel_segment
+    static {
+        rel_segment.or(unreserved);
+        rel_segment.or(escaped);
+        rel_segment.set(';');
+        rel_segment.set('@');
+        rel_segment.set('&');
+        rel_segment.set('=');
+        rel_segment.set('+');
+        rel_segment.set('$');
+        rel_segment.set(',');
+    }
+
+    // Static initializer for rel_path
+    static {
+        rel_path.or(rel_segment);
+        rel_path.or(abs_path);
+    }
+
+    // Static initializer for net_path
+    static {
+        net_path.set('/');
+        net_path.or(authority);
+        net_path.or(abs_path);
+    }
+
+    // Static initializer for hier_part
+    static {
+        hier_part.or(net_path);
+        hier_part.or(abs_path);
+        // hier_part.set('?'); aleady included
+        hier_part.or(query);
+    }
+
+    // Static initializer for relativeURI
+    static {
+        relativeURI.or(net_path);
+        relativeURI.or(abs_path);
+        relativeURI.or(rel_path);
+        // relativeURI.set('?'); aleady included
+        relativeURI.or(query);
+    }
+
+    // Static initializer for absoluteURI
+    static {
+        absoluteURI.or(scheme);
+        absoluteURI.set(':');
+        absoluteURI.or(hier_part);
+        absoluteURI.or(opaque_part);
+    }
+
+    // Static initializer for URI_reference
+    static {
+        URI_reference.or(absoluteURI);
+        URI_reference.or(relativeURI);
+        URI_reference.set('#');
+        URI_reference.or(fragment);
+    }
+
+    // Static initializer for control
+    static {
+        for (int i = 0; i <= 0x1F; i++) {
+            control.set(i);
+        }
+        control.set(0x7F);
+    }
+
+    // Static initializer for space
+    static {
+        space.set(0x20);
+    }
+
+    // Static initializer for delims
+    static {
+        delims.set('<');
+        delims.set('>');
+        delims.set('#');
+        delims.set('%');
+        delims.set('"');
+    }
+
+    // Static initializer for unwise
+    static {
+        unwise.set('{');
+        unwise.set('}');
+        unwise.set('|');
+        unwise.set('\\');
+        unwise.set('^');
+        unwise.set('[');
+        unwise.set(']');
+        unwise.set('`');
+    }
+
+    // Static initializer for disallowed_rel_path
+    static {
+        disallowed_rel_path.or(uric);
+        disallowed_rel_path.andNot(rel_path);
+    }
+
+    // Static initializer for disallowed_opaque_part
+    static {
+        disallowed_opaque_part.or(uric);
+        disallowed_opaque_part.andNot(opaque_part);
+    }
+
+    // Static initializer for allowed_authority
+    static {
+        allowed_authority.or(authority);
+        allowed_authority.clear('%');
+    }
+
+    // Static initializer for allowed_opaque_part
+    static {
+        allowed_opaque_part.or(opaque_part);
+        allowed_opaque_part.clear('%');
+    }
+
+    // Static initializer for allowed_reg_name
+    static {
+        allowed_reg_name.or(reg_name);
+        // allowed_reg_name.andNot(percent);
+        allowed_reg_name.clear('%');
+    }
+
+    // Static initializer for allowed_userinfo
+    static {
+        allowed_userinfo.or(userinfo);
+        // allowed_userinfo.andNot(percent);
+        allowed_userinfo.clear('%');
+    }
+
+    // Static initializer for allowed_within_userinfo
+    static {
+        allowed_within_userinfo.or(within_userinfo);
+        allowed_within_userinfo.clear('%');
+    }
+
+    // Static initializer for allowed_IPv6reference
+    static {
+        allowed_IPv6reference.or(IPv6reference);
+        // allowed_IPv6reference.andNot(unwise);
+        allowed_IPv6reference.clear('[');
+        allowed_IPv6reference.clear(']');
+    }
+
+    // Static initializer for allowed_host
+    static {
+        allowed_host.or(hostname);
+        allowed_host.or(allowed_IPv6reference);
+    }
+
+    // Static initializer for allowed_within_authority
+    static {
+        allowed_within_authority.or(server);
+        allowed_within_authority.or(reg_name);
+        allowed_within_authority.clear(';');
+        allowed_within_authority.clear(':');
+        allowed_within_authority.clear('@');
+        allowed_within_authority.clear('?');
+        allowed_within_authority.clear('/');
+    }
+
+    // Static initializer for allowed_abs_path
+    static {
+        allowed_abs_path.or(abs_path);
+        // allowed_abs_path.set('/');  // aleady included
+        allowed_abs_path.andNot(percent);
+        allowed_abs_path.clear('+');
+    }
+
+    // Static initializer for allowed_rel_path
+    static {
+        allowed_rel_path.or(rel_path);
+        allowed_rel_path.clear('%');
+        allowed_rel_path.clear('+');
+    }
+
+    // Static initializer for allowed_within_path
+    static {
+        allowed_within_path.or(abs_path);
+        allowed_within_path.clear('/');
+        allowed_within_path.clear(';');
+        allowed_within_path.clear('=');
+        allowed_within_path.clear('?');
+    }
+
+    // Static initializer for allowed_query
+    static {
+        allowed_query.or(uric);
+        allowed_query.clear('%');
+    }
+
+    // Static initializer for allowed_within_query
+    static {
+        allowed_within_query.or(allowed_query);
+        allowed_within_query.andNot(reserved); // excluded 'reserved'
+    }
+
+    // Static initializer for allowed_fragment
+    static {
+        allowed_fragment.or(uric);
+        allowed_fragment.clear('%');
+    }
+
+    // ------------------------------------------ Character and escape encoding
+
+    /**
+     * Encodes URI string.
+     * <p/>
+     * This is a two mapping, one from original characters to octets, and
+     * subsequently a second from octets to URI characters:
+     * <p><blockquote><pre>
+     *   original character sequence->octet sequence->URI character sequence
+     * </pre></blockquote><p>
+     * <p/>
+     * An escaped octet is encoded as a character triplet, consisting of the
+     * percent character "%" followed by the two hexadecimal digits
+     * representing the octet code. For example, "%20" is the escaped
+     * encoding for the US-ASCII space character.
+     * <p/>
+     * Conversion from the local filesystem character set to UTF-8 will
+     * normally involve a two step process. First convert the local character
+     * set to the UCS; then convert the UCS to UTF-8.
+     * The first step in the process can be performed by maintaining a mapping
+     * table that includes the local character set code and the corresponding
+     * UCS code.
+     * The next step is to convert the UCS character code to the UTF-8 encoding.
+     * <p/>
+     * Mapping between vendor codepages can be done in a very similar manner
+     * as described above.
+     * <p/>
+     * The only time escape encodings can allowedly be made is when a URI is
+     * being created from its component parts.  The escape and validate methods
+     * are internally performed within this method.
+     *
+     * @param original the original character sequence
+     * @param allowed  those characters that are allowed within a component
+     * @param charset  the protocol charset
+     * @return URI character sequence
+     * @throws HttpException null component or unsupported character encoding
+     */
+
+    protected static char[] encode(String original, BitSet allowed,
+                                   String charset) throws HttpException {
+        if (original == null) {
+            throw new IllegalArgumentException("Original string may not be null");
+        }
+        if (allowed == null) {
+            throw new IllegalArgumentException("Allowed bitset may not be null");
+        }
+        byte[] rawdata = URLCodec.encodeUrl(allowed, EncodingUtils.getBytes(original, charset));
+        return EncodingUtils.getAsciiString(rawdata).toCharArray();
+    }
+
+    /**
+     * Decodes URI encoded string.
+     * <p/>
+     * This is a two mapping, one from URI characters to octets, and
+     * subsequently a second from octets to original characters:
+     * <p><blockquote><pre>
+     *   URI character sequence->octet sequence->original character sequence
+     * </pre></blockquote><p>
+     * <p/>
+     * A URI must be separated into its components before the escaped
+     * characters within those components can be allowedly decoded.
+     * <p/>
+     * Notice that there is a chance that URI characters that are non UTF-8
+     * may be parsed as valid UTF-8.  A recent non-scientific analysis found
+     * that EUC encoded Japanese words had a 2.7% false reading; SJIS had a
+     * 0.0005% false reading; other encoding such as ASCII or KOI-8 have a 0%
+     * false reading.
+     * <p/>
+     * The percent "%" character always has the reserved purpose of being
+     * the escape indicator, it must be escaped as "%25" in order to be used
+     * as data within a URI.
+     * <p/>
+     * The unescape method is internally performed within this method.
+     *
+     * @param component the URI character sequence
+     * @param charset   the protocol charset
+     * @return original character sequence
+     * @throws HttpException incomplete trailing escape pattern or unsupported
+     *                       character encoding
+     */
+    protected static String decode(char[] component, String charset)
+            throws HttpException {
+        if (component == null) {
+            throw new IllegalArgumentException("Component array of chars may not be null");
+        }
+        return decode(new String(component), charset);
+    }
+
+    /**
+     * Decodes URI encoded string.
+     * <p/>
+     * This is a two mapping, one from URI characters to octets, and
+     * subsequently a second from octets to original characters:
+     * <p><blockquote><pre>
+     *   URI character sequence->octet sequence->original character sequence
+     * </pre></blockquote><p>
+     * <p/>
+     * A URI must be separated into its components before the escaped
+     * characters within those components can be allowedly decoded.
+     * <p/>
+     * Notice that there is a chance that URI characters that are non UTF-8
+     * may be parsed as valid UTF-8.  A recent non-scientific analysis found
+     * that EUC encoded Japanese words had a 2.7% false reading; SJIS had a
+     * 0.0005% false reading; other encoding such as ASCII or KOI-8 have a 0%
+     * false reading.
+     * <p/>
+     * The percent "%" character always has the reserved purpose of being
+     * the escape indicator, it must be escaped as "%25" in order to be used
+     * as data within a URI.
+     * <p/>
+     * The unescape method is internally performed within this method.
+     *
+     * @param component the URI character sequence
+     * @param charset   the protocol charset
+     * @return original character sequence
+     * @throws HttpException incomplete trailing escape pattern or unsupported
+     *                       character encoding
+     * @since 3.0
+     */
+    protected static String decode(String component, String charset)
+            throws HttpException {
+        if (component == null) {
+            throw new IllegalArgumentException("Component array of chars may not be null");
+        }
+        byte[] rawdata = null;
+        try {
+            rawdata = URLCodec.decodeUrl(EncodingUtils.getAsciiBytes(component));
+        } catch (DecoderException e) {
+            throw new HttpException(e.getMessage());
+        }
+        return EncodingUtils.getString(rawdata, charset);
+    }
+
+}
+
diff --git a/impl/src/main/java/com/jfrog/bintray/client/impl/util/URIUtil.java b/impl/src/main/java/com/jfrog/bintray/client/impl/util/URIUtil.java
new file mode 100644
index 0000000..0686f23
--- /dev/null
+++ b/impl/src/main/java/com/jfrog/bintray/client/impl/util/URIUtil.java
@@ -0,0 +1,158 @@
+/*
+ * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/util/URIUtil.java,v 1.27 2004/05/05 20:34:01 olegk Exp $
+ * $Revision: 507321 $
+ * $Date: 2007-02-14 01:10:51 +0100 (Wed, 14 Feb 2007) $
+ *
+ * ====================================================================
+ *
+ *  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 software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+/*
+ * Based on apache-httpclient 3.1 org.apache.commons.httpclient.util.URIUtil.java
+ * Additional contributors:
+ *    JFrog Ltd.
+ */
+package com.jfrog.bintray.client.impl.util;
+
+import org.apache.commons.codec.DecoderException;
+import org.apache.commons.codec.net.URLCodec;
+import org.apache.http.HttpException;
+import org.apache.http.util.EncodingUtils;
+
+import java.util.BitSet;
+
+/**
+ * The URI escape and character encoding and decoding utility.
+ * It's compatible with {org.apache.commons.httpclient.HttpURL} rather
+ * than {org.apache.commons.httpclient.URI}.
+ *
+ * @author <a href="mailto:jericho at apache.org">Sung-Gu</a>
+ * @version $Revision: 507321 $ $Date: 2002/03/14 15:14:01
+ */
+public class URIUtil {
+
+    private static final String UTF8_CHARSET_NAME = "UTF-8";
+
+    /**
+     * Escape and encode a string regarded as the query component of an URI with
+     * the default protocol charset.
+     * When a query string is not misunderstood the reserved special characters
+     * ("&", "=", "+", ",", and "$") within a query component, this method
+     * is recommended to use in encoding the whole query.
+     *
+     * @param unescaped an unescaped string
+     * @return the escaped string
+     * @throws HttpException if the default protocol charset is not supported
+     * @see #encode
+     */
+    public static String encodeQuery(String unescaped) throws HttpException {
+        return encodeQuery(unescaped, UTF8_CHARSET_NAME);
+    }
+
+
+    /**
+     * Escape and encode a string regarded as the query component of an URI with
+     * a given charset.
+     * When a query string is not misunderstood the reserved special characters
+     * ("&", "=", "+", ",", and "$") within a query component, this method
+     * is recommended to use in encoding the whole query.
+     *
+     * @param unescaped an unescaped string
+     * @param charset   the charset
+     * @return the escaped string
+     * @throws HttpException if the charset is not supported
+     * @see #encode
+     */
+    public static String encodeQuery(String unescaped, String charset)
+            throws HttpException {
+
+        return encode(unescaped, URI.allowed_query, charset);
+    }
+
+
+    /**
+     * Escape and encode a given string with allowed characters not to be
+     * escaped and the default protocol charset.
+     *
+     * @param unescaped a string
+     * @param allowed   allowed characters not to be escaped
+     * @return the escaped string
+     * @throws HttpException if the default protocol charset is not supported
+     */
+    public static String encode(String unescaped, BitSet allowed)
+            throws HttpException {
+
+        return encode(unescaped, allowed, UTF8_CHARSET_NAME);
+    }
+
+
+    /**
+     * Escape and encode a given string with allowed characters not to be
+     * escaped and a given charset.
+     *
+     * @param unescaped a string
+     * @param allowed   allowed characters not to be escaped
+     * @param charset   the charset
+     * @return the escaped string
+     */
+    public static String encode(String unescaped, BitSet allowed,
+                                String charset) throws HttpException {
+        byte[] rawdata = URLCodec.encodeUrl(allowed,
+                EncodingUtils.getBytes(unescaped, charset));
+        return EncodingUtils.getAsciiString(rawdata);
+    }
+
+
+    /**
+     * Unescape and decode a given string regarded as an escaped string with the
+     * default protocol charset.
+     *
+     * @param escaped a string
+     * @return the unescaped string
+     * @throws HttpException if the string cannot be decoded (invalid)
+     */
+    public static String decode(String escaped) throws HttpException {
+        try {
+            byte[] rawdata = URLCodec.decodeUrl(EncodingUtils.getAsciiBytes(escaped));
+            return EncodingUtils.getString(rawdata, UTF8_CHARSET_NAME);
+        } catch (DecoderException e) {
+            throw new HttpException(e.getMessage());
+        }
+    }
+
+
+    /**
+     * Unescape and decode a given string regarded as an escaped string.
+     *
+     * @param escaped a string
+     * @param charset the charset
+     * @return the unescaped string
+     * @throws HttpException if the charset is not supported
+     */
+    public static String decode(String escaped, String charset)
+            throws HttpException {
+
+        return URI.decode(escaped.toCharArray(), charset);
+    }
+}
+
diff --git a/impl/src/test/groovy/com/jfrog/bintray/client/impl/BintrayClientSpec.groovy b/impl/src/test/groovy/com/jfrog/bintray/client/impl/BintrayClientSpec.groovy
new file mode 100644
index 0000000..27bb232
--- /dev/null
+++ b/impl/src/test/groovy/com/jfrog/bintray/client/impl/BintrayClientSpec.groovy
@@ -0,0 +1,704 @@
+package com.jfrog.bintray.client.impl
+
+import ch.qos.logback.classic.Level
+import ch.qos.logback.classic.Logger
+import ch.qos.logback.classic.LoggerContext
+import com.jfrog.bintray.client.api.BintrayCallException
+import com.jfrog.bintray.client.api.details.Attribute
+import com.jfrog.bintray.client.api.details.ObjectMapperHelper
+import com.jfrog.bintray.client.api.details.PackageDetails
+import com.jfrog.bintray.client.api.details.VersionDetails
+import com.jfrog.bintray.client.api.handle.Bintray
+import com.jfrog.bintray.client.api.handle.PackageHandle
+import com.jfrog.bintray.client.api.handle.RepositoryHandle
+import com.jfrog.bintray.client.api.handle.VersionHandle
+import com.jfrog.bintray.client.api.model.Pkg
+import com.jfrog.bintray.client.api.model.Subject
+import com.jfrog.bintray.client.api.model.Version
+import com.jfrog.bintray.client.impl.handle.BintrayImpl
+import com.jfrog.bintray.client.impl.model.PackageImpl
+import com.jfrog.bintray.client.impl.model.VersionImpl
+import com.timgroup.jgravatar.Gravatar
+import groovy.json.JsonSlurper
+import org.apache.commons.io.IOUtils
+import org.apache.http.HttpHeaders
+import org.apache.http.auth.UsernamePasswordCredentials
+import org.apache.http.entity.ContentType
+import org.codehaus.jackson.map.ObjectMapper
+import org.joda.time.DateTime
+import org.joda.time.format.ISODateTimeFormat
+import org.slf4j.LoggerFactory
+import spock.lang.Shared
+import spock.lang.Specification
+
+import static org.apache.http.HttpStatus.SC_NOT_FOUND
+import static org.apache.http.HttpStatus.SC_OK
+
+/**
+ * @author Noam Y. Tenne
+ */
+class BintrayClientSpec extends Specification {
+    private final static String REPO_NAME = 'generic'
+    private final static String PKG_NAME = 'bla'
+    private final static String VERSION = '1.0'
+    public static final String ATTRIBUTE_NAME = 'att1'
+    public static final String ATTRIBUTE_VALUE = 'bla'
+    @Shared
+    private Properties connectionProperties
+    @Shared
+    private Bintray bintray
+    @Shared
+    private BintrayImpl restClient
+    @Shared
+    private PackageDetails pkgBuilder
+    @Shared
+    private VersionDetails versionBuilder
+    @Shared
+    String tempPkgName = "PkgTest"
+    @Shared
+    private String pkgJson;
+    @Shared
+    String tempVerName = "3.0"
+    @Shared
+    private String verJson;
+
+    private ArrayList<Attribute<String>> attributes = [
+            new Attribute<String>('a', Attribute.Type.string, "ay1", "ay2"),
+            new Attribute<String>('b', 'b', 'e'),
+            new Attribute<String>('c', 'cee')]
+
+    private String expectedAttributes = '[[values:[ay1, ay2], name:a, type:string], [values:[cee], name:c, type:string], [values:[e, b], name:b, type:string]]'
+    private Map files = ['com/bla/bintray-client-java-api.jar'        : getClass().getResourceAsStream('/testJar1.jar'),
+                         'org/foo/bar/bintray-client-java-service.jar': getClass().getResourceAsStream('/testJar2.jar')]
+
+    private String assortedAttributes = "[{\"name\":\"verAttr2\",\"values\":[\"val1\",\"val2\"],\"type\":\"string\"},{\"name\":\"verAttr3\",\"values\":[1,2.2,4],\"type\":\"number\"},{\"name\":\"verAttr2\",\"values\":[\"2011-07-14T19:43:37+0100\"],\"type\":\"date\"}]"
+
+    void setup() {
+    }
+
+    def setupSpec() {
+        this.connectionProperties = new Properties()
+        def streamFromProperties = this.class.getResourceAsStream('/bintray-client.properties')
+        if (streamFromProperties) {
+            streamFromProperties.withStream {
+                this.connectionProperties.load(it)
+            }
+        }
+        def usernameFromEnv = System.getenv('BINTRAY_USERNAME')
+        if (usernameFromEnv) {
+            connectionProperties.username = usernameFromEnv
+        }
+        def apiKeyFromEnv = System.getenv('BINTRAY_API_KEY')
+        if (apiKeyFromEnv) {
+            connectionProperties.apiKey = apiKeyFromEnv
+        }
+        def emailFromEnv = System.getenv('BINTRAY_EMAIL')
+        if (emailFromEnv) {
+            connectionProperties.email = emailFromEnv
+        }
+        assert this.connectionProperties
+        assert this.connectionProperties.username
+        assert this.connectionProperties.apiKey
+        assert this.connectionProperties.email
+        bintray = BintrayClient.create(this.connectionProperties.username as String, this.connectionProperties.apiKey as String)
+        restClient = createClient()
+        pkgBuilder = new PackageDetails(PKG_NAME).description('bla-bla').labels(['l1', 'l2']).licenses(['Apache-2.0']).vcsUrl("https://github.com/bintray/bintray-client-java.git")
+        versionBuilder = new VersionDetails(VERSION).description('versionDesc')
+        pkgJson = "{\n" +
+                "\t\t\"name\": \"" + tempPkgName + "\",\n" +
+                "\t\t\"repo\": \"generic\",\n" +
+                "\t\t\"owner\": \"" + connectionProperties.username + "\",\n" +
+                "\t\t\"desc\": \"Bintray Client Java\",\n" +
+                "\t\t\"website_url\": \"http://www.jfrog.com\",\n" +
+                "\t\t\"issue_tracker_url\": \"https://github.com/bintray/bintray-client-java/issues\",\n" +
+                "\t\t\"vcs_url\": \"https://github.com/bintray/bintray-client-java.git\",\n" +
+                "\t\t\"licenses\": [\"MIT\"],\n" +
+                "\t\t\"labels\": [\"cool\", \"awesome\", \"gorilla\"],\n" +
+                "\t\t\"public_download_numbers\": false,\n" +
+                "\t\t\"attributes\": [{\"name\": \"att1\", \"values\" : [\"val1\"], \"type\": \"string\"},\n" +
+                "\t\t\t\t\t   {\"name\": \"att3\", \"values\" : [1, 3.3, 5], \"type\": \"number\"},\n" +
+                "\t\t\t\t\t   {\"name\": \"att5\", \"values\" : [\"2011-07-14T19:43:37+0100\"], \"type\": \"date\"}]\n" +
+                "}"
+
+        verJson = "{\n" +
+                "    \"name\": \"" + tempVerName + "\",\n" +
+                "    \"desc\": \"Version Test\",\n" +
+                "    \"package\": \"" + tempPkgName + "\",\n" +
+                "    \"repo\": \"generic\",\n" +
+                "    \"owner\": \"" + connectionProperties.username + "\",\n" +
+                "    \"labels\": [\"cool\",\"awesome\",\"gorilla\"],\n" +
+                "    \"attribute_names\": [\"verAtt1\",\"verAtt2\",\"verAtt3\"],\n" +
+                "    \"released\": \" 2015-01-07T18:00:00.000-06:00\",\n" +
+                "    \"github_use_tag_release_notes\": false,\n" +
+                "    \"vcs_tag\": \"3.8\",\n" +
+                "    \"ordinal\": 0,\n" +
+                "    \"attributes\": [{\"name\": \"VerAtt1\",\"values\": [\"VerVal1\"],\"type\": \"string\"},\n" +
+                "        {\"name\": \"VerAtt2\",\"values\": [1,3.3,5],\"type\": \"number\"},\n" +
+                "        {\"name\": \"VerAtt3\",\"values\": [\"2015-01-01T19:43:37+0100\"],\"type\": \"date\"}]\n" +
+                "}"
+
+        LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
+        //Set level for root logger
+        loggerContext.getLogger("ROOT").setLevel(Level.INFO)
+        //Disable debug for org.apache.http - you can tweak the level here
+        Logger httpLogger = loggerContext.getLogger("org.apache.http");
+        httpLogger.setLevel(Level.INFO);
+    }
+
+    private BintrayImpl createClient(String url = "https://api.bintray.com") {
+        UsernamePasswordCredentials creds = new UsernamePasswordCredentials(connectionProperties.username as String, connectionProperties.apiKey as String)
+        HttpClientConfigurator conf = new HttpClientConfigurator()
+        return new BintrayImpl(conf.hostFromUrl(url).noRetry().authentication(creds).getClient(), url, 5, 90000)
+    }
+
+    def 'Test correct URL encoding'() {
+        setup:
+        def path1 = "content/user/" + REPO_NAME + "/" + PKG_NAME + "/" + VERSION + "/com/jfrog/bintray/bintray-test/1.0/bintray-test-1.0.pom;publish=1"
+        def path2 = "docker/bla/dockertest/v1/repositories/library/ubuntu"
+        def path3 = "docker/bla/dockertest/v1/images/511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158/json with space.ext"
+        def path4 = "bla/someUser/test?a=b&c=d"
+        def path5 = "bla/someUser/testMatrix;a+=b"
+        def path6 = "t%st/spe^al/ch&ar\$/*()!#/ok?"
+
+        when:
+        def encodedPath1 = ((BintrayImpl) bintray).createUrl(path1)
+        def encodedPath2 = ((BintrayImpl) bintray).createUrl(path2)
+        def encodedPath3 = ((BintrayImpl) bintray).createUrl(path3)
+        def encodedPath4 = ((BintrayImpl) bintray).createUrl(path4)
+        def encodedPath5 = ((BintrayImpl) bintray).createUrl(path5)
+        def encodedPath6 = ((BintrayImpl) bintray).createUrl(path6)
+
+        then:
+        encodedPath1.toString().equals("https://api.bintray.com/content/user/generic/bla/1.0/com/jfrog/bintray/bintray-test/1.0/bintray-test-1.0.pom;publish=1")
+        encodedPath2.toString().equals("https://api.bintray.com/docker/bla/dockertest/v1/repositories/library/ubuntu")
+        encodedPath3.toString().equals("https://api.bintray.com/docker/bla/dockertest/v1/images/511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158/json%20with%20space.ext")
+        encodedPath4.toString().equals("https://api.bintray.com/bla/someUser/test?a=b&c=d")
+        encodedPath5.toString().equals("https://api.bintray.com/bla/someUser/testMatrix;a+=b")
+        encodedPath6.toString().equals("https://api.bintray.com/t%25st/spe%5Eal/ch&ar\$/*()!%23/ok?")
+
+    }
+
+    def 'Connection is successful and subject has correct username and avatar'() {
+        //noinspection JavaStylePropertiesInvocation,GroovySetterCallCanBePropertyAccess
+        setup:
+        Gravatar gravatar = new Gravatar().setSize(140)
+
+        when:
+        Subject clientTests = bintray.subject(connectionProperties.username).get()
+
+        then:
+        clientTests.name == connectionProperties.username
+        new URL(clientTests.gravatarId).bytes == gravatar.download(connectionProperties.email as String)
+    }
+
+    def 'Default Repos exist'(String repoName, def _) {
+        expect:
+        bintray.subject(connectionProperties.username).repository(repoName)
+
+        where:
+        repoName  | _
+        'maven'   | _
+        'rpm'     | _
+        'deb'     | _
+        'generic' | _
+    }
+
+    def 'Package created'() {
+        setup:
+        bintray.subject(connectionProperties.username).repository(REPO_NAME).createPkg(pkgBuilder).createVersion(versionBuilder)
+
+        Map<String, String> headers = new HashMap<>();
+        String auth = (connectionProperties.username + ":" + connectionProperties.apiKey)
+        headers.put(HttpHeaders.AUTHORIZATION, "Basic " + auth.bytes.encodeBase64())
+        String path = "/content/" + connectionProperties.username + "/" + REPO_NAME + "/" + PKG_NAME + "/" + VERSION + "/com/jfrog/bintray/bintray-test/1.0/bintray-test-1.0.pom;publish=1"
+        restClient.putBinary(path, headers, new ByteArrayInputStream('bla'.bytes))
+
+        when:
+        Pkg pkg = bintray.subject(connectionProperties.username).repository(REPO_NAME).pkg(PKG_NAME).get()
+        JsonSlurper slurper = new JsonSlurper()
+        def actual = slurper.parseText(IOUtils.toString(restClient.get("/packages/$connectionProperties.username/$REPO_NAME/$PKG_NAME", null).entity.content))
+
+        then:
+        pkg.name() == actual.name
+        pkg.repository() == actual.repo
+        pkg.owner() == actual.owner
+        pkg.description() == actual.desc
+        pkg.labels() == actual.labels
+        pkg.attributeNames() == actual.attribute_names
+        pkg.rating() == actual.rating?.toInteger()
+        pkg.ratingCount() == actual.rating_count?.toInteger()
+        pkg.followersCount() == actual.followers_count?.toInteger()
+        pkg.created() == ISODateTimeFormat.dateTime().parseDateTime(actual.created as String)
+        pkg.versions() == actual.versions
+        pkg.latestVersion() == actual.latest_version
+        pkg.updated() == ISODateTimeFormat.dateTime().parseDateTime(actual.updated as String)
+        pkg.linkedToRepos() == actual.linked_to_repos
+        pkg.systemIds() == actual.system_ids
+    }
+
+    def 'package exists'() {
+        when:
+        // Create the package:
+        bintray.subject(connectionProperties.username).repository(REPO_NAME).createPkg(pkgBuilder)
+
+        then:
+        // Check that the package exists:
+        bintray.subject(connectionProperties.username).repository(REPO_NAME).pkg(PKG_NAME).exists()
+
+        when:
+        // Delete the package:
+        bintray.subject(connectionProperties.username).repository(REPO_NAME).pkg(PKG_NAME).delete()
+
+        then:
+        // Check that the package does not exist:
+        !bintray.subject(connectionProperties.username).repository(REPO_NAME).pkg(PKG_NAME).exists()
+    }
+
+    def 'Version created'() {
+        setup:
+        def pkg = bintray.subject(connectionProperties.username).repository(REPO_NAME).createPkg(pkgBuilder)
+
+        when:
+        Version version = pkg.createVersion(versionBuilder).get()
+        JsonSlurper slurper = new JsonSlurper()
+        def actual = slurper.parseText(IOUtils.toString(restClient.get("/packages/$connectionProperties.username/$REPO_NAME/$PKG_NAME/versions/$VERSION", null).getEntity().getContent()))
+
+        then:
+        version.name() == actual.name
+        version.description() == actual.desc
+        version.pkg() == actual.package
+        version.repository() == actual.repo
+        version.owner() == actual.owner
+        version.labels() == actual.labels
+        version.vcsTag() == actual.vcs_tag
+        version.attributeNames() == actual.attribute_names
+        version.ordinal() == actual.ordinal.toInteger()
+        if (actual.created) {
+            version.created() == ISODateTimeFormat.dateTime().parseDateTime(actual.created as String)
+        }
+        if (actual.updated) {
+            version.updated() == ISODateTimeFormat.dateTime().parseDateTime(actual.updated as String)
+        }
+        if (actual.released) {
+            version.released() == ISODateTimeFormat.dateTime().parseDateTime(actual.released as String)
+        }
+    }
+
+    def 'version exists'() {
+        when:
+        // Create the version:
+        bintray.subject(connectionProperties.username).repository(REPO_NAME).createPkg(pkgBuilder).createVersion(versionBuilder)
+
+        then:
+        // Check that the version exists:
+        bintray.subject(connectionProperties.username).repository(REPO_NAME).pkg(PKG_NAME).version(VERSION).exists()
+
+        when:
+        // Delete the version:
+        bintray.subject(connectionProperties.username).repository(REPO_NAME).pkg(PKG_NAME).version(VERSION).delete()
+
+        then:
+        // Check that the package does not exist:
+        !bintray.subject(connectionProperties.username).repository(REPO_NAME).pkg(PKG_NAME).version(VERSION).exists()
+    }
+
+    def 'search by attributes'() {
+        setup:
+        def repo = bintray.subject(connectionProperties.username).repository(REPO_NAME)
+
+        repo.createPkg(pkgBuilder)
+        String attributesQuery = "[{\"name\": \"" + ATTRIBUTE_NAME + "\", \"values\" : [\"" + ATTRIBUTE_VALUE + "\"], \"type\": \"string\"}]"
+        def headers = new HashMap<String, String>();
+        headers.put(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.getMimeType());
+        restClient.post("/packages/" + connectionProperties.username + "/" + REPO_NAME + "/" + PKG_NAME + "/" + "attributes", headers,
+                new ByteArrayInputStream(attributesQuery.getBytes()))
+
+        when:
+        /* the following code is analogous to this rest query payload:
+        [
+        {"att1" : ["val1", "val2"]}, //att1 value is either val1 or val2 (att1 is a scalar)
+        {"att2": "[1,3]"}, //att2 value is equal to or greater than 1 and equal to or smaller than 3
+        {"att3": "[,3]"}, //att3 value is equals to or smaller than 3
+        {"att4": "[,3["}, //att3 value is smaller than 3
+        {"att5": "]2011-07-14T19:43:37+0100,]"}, //att5 value  is after 2011-07-14T19:43:37+0100 (dates are defined in ISO8601 format)
+        ]
+         */
+//        def results = repo.searchForPackage().byAttributeName('att1').in('val1', 'val2').and().
+//                byAttributeName('att2').greaterOrEqualsTo(1).lessOrEquals(3).and().
+//                byAttributeName('att3').lessOrEquals(3).and().
+//                byAttributeName('att4').lessThan(3).and().
+//                byAttributeName('att5').after(new DateTime(2011, 7, 14, 19, 43, 37, DateTimeZone.forOffsetHours(1))).and().
+//                byAttributeName('att6').equals(3).search()
+
+        List<Pkg> results = repo.searchForPackage().byAttributeName(ATTRIBUTE_NAME).equalsVal(ATTRIBUTE_VALUE).searchPackage()
+
+        then:
+        results
+        results.size() == 1
+        Pkg pkg = results[0]
+        pkg.name() == PKG_NAME
+        pkg.attributeNames()[0] == ATTRIBUTE_NAME
+    }
+
+    def 'attributes set on package'() {
+        setup:
+        def pkg = bintray.subject(connectionProperties.username).repository(REPO_NAME).createPkg(pkgBuilder)
+
+        when:
+        pkg.setAttributes(attributes)
+
+        JsonSlurper slurper = new JsonSlurper()
+        def actualPackage = slurper.parseText(IOUtils.toString(restClient.get("/packages/" + connectionProperties.username + "/" + REPO_NAME + "/" + PKG_NAME, null).getEntity().getContent()))
+        def actualAttributes = slurper.parseText(IOUtils.toString(restClient.get("/packages/" + connectionProperties.username + "/" + REPO_NAME + "/" + PKG_NAME + "/attributes", null).getEntity().getContent()))
+
+        then:
+        ['a', 'b', 'c'] == actualPackage.attribute_names.sort()
+        and:
+        expectedAttributes.equalsIgnoreCase(actualAttributes.sort().toString())
+    }
+
+    def 'attributes set on version'() {
+        setup:
+        def ver = bintray.subject(connectionProperties.username).repository(REPO_NAME).createPkg(pkgBuilder).createVersion(versionBuilder)
+
+        when:
+        ver.setAttributes(attributes)
+
+        JsonSlurper slurper = new JsonSlurper()
+        def actualVersion = slurper.parseText(IOUtils.toString(restClient.get("/packages/" + connectionProperties.username + "/" + REPO_NAME + "/" + PKG_NAME + "/versions/" + VERSION, null).getEntity().getContent()))
+        def actualAttributes = slurper.parseText(IOUtils.toString(restClient.get("/packages/" + connectionProperties.username + "/" + REPO_NAME + "/" + PKG_NAME + "/versions/" + VERSION + "/attributes", null).getEntity().getContent()))
+
+        then:
+        ['a', 'b', 'c'] == actualVersion.attribute_names.sort()
+        and:
+        expectedAttributes == actualAttributes.sort().toString()
+
+    }
+
+    def 'files uploaded and can be accessed by the author'() {
+        setup:
+        def ver = bintray.subject(connectionProperties.username).repository(REPO_NAME).createPkg(pkgBuilder).createVersion(versionBuilder)
+        def downloadServerClient = createClient("https://dl.bintray.com")
+
+        when:
+        ver.upload(this.files)
+        sleep(4000)
+        def get1 = downloadServerClient.get("/" + connectionProperties.username + "/" + REPO_NAME + "/" + files.keySet().asList().get(0), null)
+        def get2 = downloadServerClient.get("/" + connectionProperties.username + "/" + REPO_NAME + "/" + files.keySet().asList().get(1), null)
+
+        then:
+        then:
+        get1.getStatusLine().getStatusCode() == SC_OK
+        and:
+        get2.getStatusLine().getStatusCode() == SC_OK
+    }
+
+    def 'unpublished files can\'t be seen by anonymous'() {
+        setup:
+        sleep(10000) //wait for previous deletions to propagate
+        def ver = bintray.subject(connectionProperties.username).repository(REPO_NAME).createPkg(pkgBuilder).createVersion(versionBuilder)
+        HttpClientConfigurator conf = new HttpClientConfigurator();
+        def anonymousDownloadServerClient = new BintrayImpl(conf.hostFromUrl("https://dl.bintray.com").noRetry().noCookies().getClient(), "https://dl.bintray.com", 5, 90000)
+
+        when:
+        sleep(6000)
+        ver.upload(this.files)
+        sleep(10000)
+        anonymousDownloadServerClient.get("/" + connectionProperties.username + "/" + REPO_NAME + "/" + files.keySet().asList().get(0), null)
+
+        then:
+        BintrayCallException bce = thrown()
+        bce.getStatusCode().equals(401)
+    }
+
+    def 'publish artifacts'() {
+        setup:
+        VersionHandle ver = bintray.subject(connectionProperties.username).repository(REPO_NAME).createPkg(pkgBuilder).createVersion(versionBuilder).upload(this.files)
+        HttpClientConfigurator conf = new HttpClientConfigurator();
+        def anonymousDownloadServerClient = new BintrayImpl(conf.hostFromUrl("https://dl.bintray.com").noRetry().getClient(), "https://dl.bintray.com", 5, 90000)
+
+        when:
+        sleep(2000)
+        ver.publish()
+        sleep(6000)
+        def response = anonymousDownloadServerClient.get("/" + connectionProperties.username + "/" + REPO_NAME + "/" + files.keySet().asList().get(0), null)
+
+        then:
+        response.getStatusLine().getStatusCode() == SC_OK
+    }
+
+    def 'discard artifacts'() {
+        setup:
+        VersionHandle ver = bintray.subject(connectionProperties.username).repository(REPO_NAME).createPkg(pkgBuilder).createVersion(versionBuilder).upload(this.files)
+        when:
+        ver.discard()
+        sleep(4000) //wait for propagation to dl and stuff
+        "https://dl.bintray.com/$connectionProperties.username/$REPO_NAME/${files.keySet().asList().get(0)}".toURL().content
+        then:
+        IOException ioe = thrown()
+        ioe.getMessage().contains("401") || ioe instanceof FileNotFoundException
+    }
+
+    def 'on error response is returned without parsing'() {
+        setup:
+        Bintray wrongBintray = BintrayClient.create(this.connectionProperties.username as String, this.connectionProperties.apiKey as String)
+        when:
+        wrongBintray.subject('bla').get()
+        then:
+        BintrayCallException e = thrown()
+        e.statusCode == SC_NOT_FOUND
+        e.reason == 'Not Found'
+    }
+
+    def 'wrong subject gives 404'() {
+        when:
+        bintray.subject('bla').get()
+        then:
+        BintrayCallException e = thrown()
+        e.statusCode == SC_NOT_FOUND
+    }
+
+    def 'wrong repository gives 404'() {
+        when:
+        bintray.subject(connectionProperties.username).repository('bla').get()
+        then:
+        BintrayCallException e = thrown()
+        e.statusCode == SC_NOT_FOUND
+    }
+
+    def 'wrong package gives 404'() {
+        when:
+        bintray.subject(connectionProperties.username).repository(REPO_NAME).pkg('bla').get()
+        then:
+        BintrayCallException e = thrown()
+        e.statusCode == SC_NOT_FOUND
+    }
+
+    def 'wrong version gives 404'() {
+        when:
+        bintray.subject(connectionProperties.username).repository(REPO_NAME).pkg(PKG_NAME).version('3434').get()
+        then:
+        BintrayCallException e = thrown()
+        e.statusCode == SC_NOT_FOUND
+    }
+
+    def 'Attribute Serialization and Deserialization'() {
+        setup:
+        String attrs = assortedAttributes
+
+        when:
+        List<Attribute> attributes = Attribute.getAttributeListFromJson(IOUtils.toInputStream(attrs))
+        String out = Attribute.getJsonFromAttributeList(attributes)
+
+        then:
+        attributes.size == 3
+        attributes.get(0).getName().equals("verAttr2")
+        attributes.get(0).getType().equals(Attribute.Type.string)
+        attributes.get(0).getValues() == ["val1", "val2"]
+        out.equals(attrs)
+    }
+
+    def 'Package Creation Using PackageDetails'() {
+
+        setup:
+        ObjectMapper mapper = new ObjectMapper();
+        PackageDetails pkgDetailsFromJson = mapper.readValue(pkgJson, PackageDetails.class)
+
+        List<Attribute> attrs = new ArrayList<>();
+        attrs.add(new Attribute<String>("att1", Attribute.Type.string, "val1"))
+        attrs.add(new Attribute<Double>("att3", Attribute.Type.number, 1, 3.3, 5))
+        attrs.add(new Attribute<DateTime>("att5", Attribute.Type.date, DateTime.parse("2011-07-14T19:43:37+0100")))
+
+        PackageDetails pkgDetails = new PackageDetails(tempPkgName).description("Bintray Client Java").websiteUrl("http://www.jfrog.com")
+                .issueTrackerUrl("https://github.com/bintray/bintray-client-java/issues").vcsUrl("https://github.com/bintray/bintray-client-java.git")
+                .licenses(["MIT"]).labels(["cool", "awesome", "gorilla"]).publicDownloadNumbers(false).attributes(attrs)
+        pkgDetails.setRepo("generic")
+        pkgDetails.setOwner(bintray.subject(connectionProperties.username).name())
+        PackageImpl locallyCreated = new PackageImpl(pkgDetails);
+
+        when:
+        JsonSlurper slurper = new JsonSlurper()
+        PackageHandle pkgHandle = bintray.subject(connectionProperties.username).repository(REPO_NAME).createPkg(pkgDetailsFromJson)
+        PackageImpl pkg = pkgHandle.get()
+        def directJson = slurper.parseText(IOUtils.toString(restClient.get("/packages/" + connectionProperties.username + "/" + REPO_NAME + "/" + tempPkgName, null).getEntity().getContent()))
+        List<Attribute> attributes = Attribute.getAttributeListFromJson(restClient.get("/packages/" + connectionProperties.username + "/" + REPO_NAME + "/" + tempPkgName + "/attributes", null).getEntity().getContent())
+
+        then:
+        //PackageImpl
+        locallyCreated.equals(pkg)
+
+        //jsons
+        pkgDetailsFromJson.getName().equals(directJson.name)
+        pkgDetailsFromJson.getRepo().equals(directJson.repo)
+        pkgDetailsFromJson.getOwner().equals(directJson.owner)
+        pkgDetailsFromJson.getDescription().equals(directJson.desc)
+        pkgDetailsFromJson.getWebsiteUrl().equals(directJson.website_url)
+        pkgDetailsFromJson.getIssueTrackerUrl().equals(directJson.issue_tracker_url)
+        pkgDetailsFromJson.getVcsUrl().equals(directJson.vcs_url)
+        for (int i = 0; i < pkgDetails.getLabels().size(); i++) {
+            pkgDetails.getLabels().sort().get(i).equalsIgnoreCase(directJson.labels.sort()[i])
+        }
+        pkgDetailsFromJson.getPublicDownloadNumbers().equals(directJson.public_download_numbers)
+
+        //Attributes
+        for (Attribute attr : attributes) {
+            attr.equals(attrs.get(0)) || attr.equals(attrs.get(1)) || attr.equals(attrs.get(2))
+        }
+
+        cleanup:
+        try {
+            String cleanPkg = "/packages/" + connectionProperties.username + "/" + REPO_NAME + "/" + tempPkgName
+            restClient.delete(cleanPkg, null)
+        } catch (Exception e) {
+            System.err.println("cleanup: " + e)
+        }
+
+
+    }
+
+    def 'Version Creation using VersionDetails'() {
+
+        setup:
+        ObjectMapper mapper = new ObjectMapper()
+        VersionDetails verDetailsFromJson = mapper.readValue(verJson, VersionDetails.class)
+        PackageDetails pkgDetailsFromJson = mapper.readValue(pkgJson, PackageDetails.class)
+
+
+        List<Attribute> attrs = new ArrayList<>();
+        attrs.add(new Attribute<String>("verAtt1", Attribute.Type.string, "verVal1"))
+        attrs.add(new Attribute<Double>("verAtt3", Attribute.Type.number, 1, 8.2, 6))
+        attrs.add(new Attribute<DateTime>("verAtt5", Attribute.Type.date, DateTime.parse("2014-01-01T17:36:37+0100")))
+
+        VersionDetails verDetails = new VersionDetails(tempVerName).description("Version Test").releaseNotesFile("README.md")
+                .released(DateTime.parse("2014-01-01T17:36:37+0100")).useTagReleaseNotes(false).vcsTag("3.8")
+
+        verDetails.setOrdinal(5)
+        verDetails.setAttributes(attrs)
+        verDetails.setLabels(["cool", "awesome", "gorilla"])
+        verDetails.setPkg(tempPkgName)
+        verDetails.setRepo("generic")
+        verDetails.setOwner(bintray.subject(connectionProperties.username).name())
+
+        VersionImpl locallyCreated = new VersionImpl(verDetails);
+
+        when:
+        JsonSlurper slurper = new JsonSlurper()
+        VersionHandle verHandle = bintray.subject(connectionProperties.username).repository(REPO_NAME).createPkg(pkgDetailsFromJson).createVersion(verDetailsFromJson)
+        VersionImpl ver = verHandle.get()
+        def directJson = slurper.parseText(IOUtils.toString(restClient.get("/packages/" + connectionProperties.username + "/" + REPO_NAME + "/" + tempPkgName + "/versions/" + tempVerName, null).getEntity().getContent()))
+        List<Attribute> attributes = Attribute.getAttributeListFromJson(restClient.get("/packages/" + connectionProperties.username + "/" + REPO_NAME + "/" + tempPkgName + "/versions/" + tempVerName + "/attributes", null).getEntity().getContent())
+
+        then:
+        //PackageImpl
+        locallyCreated.equals(ver)
+
+        //jsons
+        verDetailsFromJson.getName().equals(directJson.name)
+        verDetailsFromJson.getDescription().equals(directJson.desc)
+        verDetailsFromJson.getRepo().equals(directJson.repo)
+        verDetailsFromJson.getPkg().equals(directJson.package)
+        verDetailsFromJson.getOwner().equals(directJson.owner)
+        for (int i = 0; i < verDetailsFromJson.getLabels().size(); i++) {
+            verDetailsFromJson.getLabels().sort().get(i).equalsIgnoreCase(directJson.labels.sort()[i])
+        }
+        for (int i = 0; i < verDetailsFromJson.getAttributeNames().size(); i++) {
+            verDetailsFromJson.getAttributeNames().sort().get(i).equalsIgnoreCase(directJson.attribute_names.sort()[i])
+        }
+
+        directJson.released != null
+        verDetailsFromJson.getUseTagReleaseNotes().equals(directJson.github_use_tag_release_notes)
+        verDetailsFromJson.getVcsTag().equals(directJson.vcs_tag)
+        verDetailsFromJson.getOrdinal().equals(Float.floatToIntBits(directJson.ordinal))
+
+        //Attributes
+        for (Attribute attr : attributes) {
+            attr.equals(attrs.get(0)) || attr.equals(attrs.get(1)) || attr.equals(attrs.get(2))
+        }
+
+        cleanup:
+        try {
+            String cleanPkg = "/packages/" + connectionProperties.username + "/" + REPO_NAME + "/" + tempPkgName
+            restClient.delete(cleanPkg, null)
+        } catch (Exception e) {
+            System.err.println("cleanup: " + e)
+        }
+
+    }
+
+    def 'send empty values - verify they are not null'() {
+
+        String minimalPkgName = "MyPackage"
+
+        String minimalPkgJson = "{\n" +
+                "\t\t\"name\": \"" + minimalPkgName + "\",\n" +
+                "\t\t\"repo\": \"" + REPO_NAME + "\",\n" +
+                "\t\t\"owner\": \"" + connectionProperties.username + "\",\n" +
+                "\t\t\"desc\": \"\",\n" +
+                "\t\t\"website_url\": \"\",\n" +
+                "\t\t\"labels\": [],\n" +
+                "\t\t\"vcs_url\": \"https://github.com/bintray/bintray-client-java.git\",\n" +
+                "\t\t\"licenses\": [\"MIT\"]\n" +
+                "}"
+
+        String minimalVerJson = "{\n" +
+                "\t\t\"name\": \"3.3.3\",\n" +
+                "\t\t\"vcs_tag\": \"\",\n" +
+                "\t\t\"labels\": null,\n" +
+                "\t\t\"description\": \"\"\n" +
+                "}"
+
+        setup:
+        ObjectMapper mapper = ObjectMapperHelper.objectMapper
+
+        ArrayList<String> licenses = new ArrayList<>();
+        licenses.add("Apache-2.0")
+        PackageDetails newPkgDetails = new PackageDetails(minimalPkgName);
+        newPkgDetails.licenses(licenses);
+        newPkgDetails.setRepo(REPO_NAME)
+        newPkgDetails.setSubject(connectionProperties.username)
+        newPkgDetails.setVcsUrl("https://github.com/bintray/bintray-client-java.git")
+        newPkgDetails.setDescription("")
+
+        VersionDetails newVerDetails = new VersionDetails("2.2.0")
+        newVerDetails.setDescription("")
+
+        PackageDetails pkgDetailsFromJson = mapper.readValue(minimalPkgJson, PackageDetails.class)
+        VersionDetails verDetailsFromJson = mapper.readValue(minimalVerJson, VersionDetails.class)
+
+        when:
+        RepositoryHandle repo = bintray.subject(connectionProperties.username).repository(REPO_NAME)
+
+        PackageHandle pkg = repo.createPkg(newPkgDetails)
+        VersionHandle ver = pkg.createVersion(newVerDetails)
+
+        pkg.update(pkgDetailsFromJson)
+        ver.update(verDetailsFromJson)
+
+        String pkgJsonContent = PackageImpl.getCreateUpdateJson(pkgDetailsFromJson);
+        String verJsonContent = VersionImpl.getCreateUpdateJson(verDetailsFromJson);
+
+        then:
+        pkgJsonContent.equals("{\"name\":\"MyPackage\",\"labels\":[],\"licenses\":[\"MIT\"],\"desc\":\"\",\"website_url\":\"\",\"vcs_url\":\"https://github.com/bintray/bintray-client-java.git\"}")
+        verJsonContent.contentEquals("{\"name\":\"3.3.3\",\"vcs_tag\":\"\"}")
+
+        cleanup:
+        try {
+            String cleanPkg = "/packages/" + connectionProperties.username + "/" + REPO_NAME + "/" + minimalPkgName
+            restClient.delete(cleanPkg, null)
+        } catch (Exception e) {
+            System.err.println("cleanup: " + e)
+        }
+    }
+
+
+    def cleanup() {
+        try {
+            String pkg = "/packages/" + connectionProperties.username + "/" + REPO_NAME + "/" + PKG_NAME
+            restClient.delete(pkg, null)
+        } catch (BintrayCallException e) {
+            if (e.getStatusCode() != SC_NOT_FOUND) { //don't care
+                throw e
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/impl/src/test/resources/simplelog.properties b/impl/src/test/resources/simplelog.properties
new file mode 100644
index 0000000..8a6f179
--- /dev/null
+++ b/impl/src/test/resources/simplelog.properties
@@ -0,0 +1,5 @@
+.level=INFO
+handlers=java.util.logging.ConsoleHandler
+java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
+httpclient.wire.header.level=FINEST
+org.apache.commons.httpclient.level=FINEST
\ No newline at end of file
diff --git a/impl/src/test/resources/testJar1.jar b/impl/src/test/resources/testJar1.jar
new file mode 100644
index 0000000..1c21129
Binary files /dev/null and b/impl/src/test/resources/testJar1.jar differ
diff --git a/impl/src/test/resources/testJar2.jar b/impl/src/test/resources/testJar2.jar
new file mode 100644
index 0000000..d78b3db
Binary files /dev/null and b/impl/src/test/resources/testJar2.jar differ
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..68ef87f
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,6 @@
+String rootName = 'bintray-client-java'
+include 'api', 'impl'
+rootProject.name = rootName
+
+project(":api").name = "$rootName-api"
+project(":impl").name = "$rootName-service"
\ No newline at end of file

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-java/bintray-client-java.git



More information about the pkg-java-commits mailing list