[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
+
+[](https://bintray.com/jfrog/bintray-tools/bintray-client-java?bdg=1)
+
+[](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>:<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