Repository GCS plugin new client library (#30168)

This does away with the deprecated `com.google.api-client:google-api-client:1.23`
and replaces it with `com.google.cloud:google-cloud-storage:1.28.0`.
It also changes security permissions for the repository-gcs plugin.
This commit is contained in:
Albert Zaharovits 2018-05-15 18:22:58 +03:00 committed by GitHub
parent d1c28c60fc
commit 801973fa9f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
77 changed files with 3438 additions and 939 deletions

View File

@ -84,11 +84,7 @@ A service account file looks like this:
"private_key_id": "...", "private_key_id": "...",
"private_key": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n", "private_key": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n",
"client_email": "service-account-for-your-repository@your-project-id.iam.gserviceaccount.com", "client_email": "service-account-for-your-repository@your-project-id.iam.gserviceaccount.com",
"client_id": "...", "client_id": "..."
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://accounts.google.com/o/oauth2/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "..."
} }
---- ----
// NOTCONSOLE // NOTCONSOLE
@ -178,6 +174,12 @@ are marked as `Secure`.
a custom name can be useful to authenticate your cluster when requests a custom name can be useful to authenticate your cluster when requests
statistics are logged in the Google Cloud Platform. Default to `repository-gcs` statistics are logged in the Google Cloud Platform. Default to `repository-gcs`
`project_id`::
The Google Cloud project id. This will be automatically infered from the credentials file but
can be specified explicitly. For example, it can be used to switch between projects when the
same credentials are usable for both the production and the development projects.
[[repository-gcs-repository]] [[repository-gcs-repository]]
==== Repository Settings ==== Repository Settings

View File

@ -22,35 +22,204 @@ esplugin {
classname 'org.elasticsearch.repositories.gcs.GoogleCloudStoragePlugin' classname 'org.elasticsearch.repositories.gcs.GoogleCloudStoragePlugin'
} }
versions << [
'google': '1.23.0',
]
dependencies { dependencies {
compile "com.google.apis:google-api-services-storage:v1-rev115-${versions.google}" compile 'com.google.cloud:google-cloud-storage:1.28.0'
compile "com.google.api-client:google-api-client:${versions.google}" compile 'com.google.cloud:google-cloud-core:1.28.0'
compile "com.google.oauth-client:google-oauth-client:${versions.google}" compile 'com.google.cloud:google-cloud-core-http:1.28.0'
compile "org.apache.httpcomponents:httpclient:${versions.httpclient}" compile 'com.google.auth:google-auth-library-oauth2-http:0.9.1'
compile "org.apache.httpcomponents:httpcore:${versions.httpcore}" compile 'com.google.auth:google-auth-library-credentials:0.9.1'
compile "commons-logging:commons-logging:${versions.commonslogging}" compile 'com.google.oauth-client:google-oauth-client:1.23.0'
compile "commons-codec:commons-codec:${versions.commonscodec}" compile 'com.google.http-client:google-http-client:1.23.0'
compile "com.google.http-client:google-http-client:${versions.google}" compile 'com.google.http-client:google-http-client-jackson:1.23.0'
compile "com.google.http-client:google-http-client-jackson2:${versions.google}" compile 'com.google.http-client:google-http-client-jackson2:1.23.0'
compile 'com.google.http-client:google-http-client-appengine:1.23.0'
compile 'com.google.api-client:google-api-client:1.23.0'
compile 'com.google.api:gax:1.25.0'
compile 'com.google.api:gax-httpjson:0.40.0'
compile 'com.google.api:api-common:1.5.0'
compile 'com.google.api.grpc:proto-google-common-protos:1.8.0'
compile 'com.google.guava:guava:20.0'
compile 'com.google.apis:google-api-services-storage:v1-rev115-1.23.0'
compile 'org.codehaus.jackson:jackson-core-asl:1.9.13'
compile 'io.grpc:grpc-context:1.9.0'
compile 'io.opencensus:opencensus-api:0.11.1'
compile 'io.opencensus:opencensus-contrib-http-util:0.11.1'
compile 'org.threeten:threetenbp:1.3.6'
} }
dependencyLicenses { dependencyLicenses {
mapping from: /google-.*/, to: 'google' mapping from: /google-cloud-.*/, to: 'google-cloud'
mapping from: /google-auth-.*/, to: 'google-auth'
mapping from: /google-http-.*/, to: 'google-http'
mapping from: /opencensus.*/, to: 'opencensus'
} }
thirdPartyAudit.excludes = [ thirdPartyAudit.excludes = [
// uses internal java api: sun.misc.Unsafe
'com.google.common.cache.Striped64',
'com.google.common.cache.Striped64$1',
'com.google.common.cache.Striped64$Cell',
'com.google.common.hash.LittleEndianByteArray$UnsafeByteArray$1',
'com.google.common.hash.LittleEndianByteArray$UnsafeByteArray$2',
'com.google.common.hash.LittleEndianByteArray$UnsafeByteArray$3',
'com.google.common.util.concurrent.AbstractFuture$UnsafeAtomicHelper',
'com.google.common.util.concurrent.AbstractFuture$UnsafeAtomicHelper$1',
'com.google.common.hash.LittleEndianByteArray$UnsafeByteArray',
'com.google.common.primitives.UnsignedBytes$LexicographicalComparatorHolder$UnsafeComparator',
'com.google.common.primitives.UnsignedBytes$LexicographicalComparatorHolder$UnsafeComparator$1',
// classes are missing // classes are missing
'com.google.common.base.Splitter', 'com.google.appengine.api.datastore.Blob',
'com.google.common.collect.Lists', 'com.google.appengine.api.datastore.DatastoreService',
'javax.servlet.ServletContextEvent', 'com.google.appengine.api.datastore.DatastoreServiceFactory',
'javax.servlet.ServletContextListener', 'com.google.appengine.api.datastore.Entity',
'org.apache.avalon.framework.logger.Logger', 'com.google.appengine.api.datastore.Key',
'org.apache.log.Hierarchy', 'com.google.appengine.api.datastore.KeyFactory',
'org.apache.log.Logger', 'com.google.appengine.api.datastore.PreparedQuery',
'com.google.appengine.api.datastore.Query',
'com.google.appengine.api.memcache.Expiration',
'com.google.appengine.api.memcache.MemcacheService',
'com.google.appengine.api.memcache.MemcacheServiceFactory',
'com.google.appengine.api.urlfetch.FetchOptions$Builder',
'com.google.appengine.api.urlfetch.FetchOptions',
'com.google.appengine.api.urlfetch.HTTPHeader',
'com.google.appengine.api.urlfetch.HTTPMethod',
'com.google.appengine.api.urlfetch.HTTPRequest',
'com.google.appengine.api.urlfetch.HTTPResponse',
'com.google.appengine.api.urlfetch.URLFetchService',
'com.google.appengine.api.urlfetch.URLFetchServiceFactory',
'com.google.gson.Gson',
'com.google.gson.GsonBuilder',
'com.google.gson.TypeAdapter',
'com.google.gson.stream.JsonReader',
'com.google.gson.stream.JsonWriter',
'com.google.iam.v1.Binding$Builder',
'com.google.iam.v1.Binding',
'com.google.iam.v1.Policy$Builder',
'com.google.iam.v1.Policy',
'com.google.protobuf.AbstractMessageLite$Builder',
'com.google.protobuf.AbstractParser',
'com.google.protobuf.Any$Builder',
'com.google.protobuf.Any',
'com.google.protobuf.AnyOrBuilder',
'com.google.protobuf.AnyProto',
'com.google.protobuf.Api$Builder',
'com.google.protobuf.Api',
'com.google.protobuf.ApiOrBuilder',
'com.google.protobuf.ApiProto',
'com.google.protobuf.ByteString',
'com.google.protobuf.CodedInputStream',
'com.google.protobuf.CodedOutputStream',
'com.google.protobuf.DescriptorProtos',
'com.google.protobuf.Descriptors$Descriptor',
'com.google.protobuf.Descriptors$EnumDescriptor',
'com.google.protobuf.Descriptors$EnumValueDescriptor',
'com.google.protobuf.Descriptors$FieldDescriptor',
'com.google.protobuf.Descriptors$FileDescriptor$InternalDescriptorAssigner',
'com.google.protobuf.Descriptors$FileDescriptor',
'com.google.protobuf.Descriptors$OneofDescriptor',
'com.google.protobuf.Duration$Builder',
'com.google.protobuf.Duration',
'com.google.protobuf.DurationOrBuilder',
'com.google.protobuf.DurationProto',
'com.google.protobuf.EmptyProto',
'com.google.protobuf.Enum$Builder',
'com.google.protobuf.Enum',
'com.google.protobuf.EnumOrBuilder',
'com.google.protobuf.ExtensionRegistry',
'com.google.protobuf.ExtensionRegistryLite',
'com.google.protobuf.FloatValue$Builder',
'com.google.protobuf.FloatValue',
'com.google.protobuf.FloatValueOrBuilder',
'com.google.protobuf.GeneratedMessage$GeneratedExtension',
'com.google.protobuf.GeneratedMessage',
'com.google.protobuf.GeneratedMessageV3$Builder',
'com.google.protobuf.GeneratedMessageV3$BuilderParent',
'com.google.protobuf.GeneratedMessageV3$FieldAccessorTable',
'com.google.protobuf.GeneratedMessageV3',
'com.google.protobuf.Internal$EnumLite',
'com.google.protobuf.Internal$EnumLiteMap',
'com.google.protobuf.Internal',
'com.google.protobuf.InvalidProtocolBufferException',
'com.google.protobuf.LazyStringArrayList',
'com.google.protobuf.LazyStringList',
'com.google.protobuf.MapEntry$Builder',
'com.google.protobuf.MapEntry',
'com.google.protobuf.MapField',
'com.google.protobuf.Message',
'com.google.protobuf.MessageOrBuilder',
'com.google.protobuf.Parser',
'com.google.protobuf.ProtocolMessageEnum',
'com.google.protobuf.ProtocolStringList',
'com.google.protobuf.RepeatedFieldBuilderV3',
'com.google.protobuf.SingleFieldBuilderV3',
'com.google.protobuf.Struct$Builder',
'com.google.protobuf.Struct',
'com.google.protobuf.StructOrBuilder',
'com.google.protobuf.StructProto',
'com.google.protobuf.Timestamp$Builder',
'com.google.protobuf.Timestamp',
'com.google.protobuf.TimestampProto',
'com.google.protobuf.Type$Builder',
'com.google.protobuf.Type',
'com.google.protobuf.TypeOrBuilder',
'com.google.protobuf.TypeProto',
'com.google.protobuf.UInt32Value$Builder',
'com.google.protobuf.UInt32Value',
'com.google.protobuf.UInt32ValueOrBuilder',
'com.google.protobuf.UnknownFieldSet$Builder',
'com.google.protobuf.UnknownFieldSet',
'com.google.protobuf.WireFormat$FieldType',
'com.google.protobuf.WrappersProto',
'com.google.protobuf.util.Timestamps',
'org.apache.http.ConnectionReuseStrategy',
'org.apache.http.Header',
'org.apache.http.HttpEntity',
'org.apache.http.HttpEntityEnclosingRequest',
'org.apache.http.HttpHost',
'org.apache.http.HttpRequest',
'org.apache.http.HttpResponse',
'org.apache.http.HttpVersion',
'org.apache.http.RequestLine',
'org.apache.http.StatusLine',
'org.apache.http.client.AuthenticationHandler',
'org.apache.http.client.HttpClient',
'org.apache.http.client.HttpRequestRetryHandler',
'org.apache.http.client.RedirectHandler',
'org.apache.http.client.RequestDirector',
'org.apache.http.client.UserTokenHandler',
'org.apache.http.client.methods.HttpDelete',
'org.apache.http.client.methods.HttpEntityEnclosingRequestBase',
'org.apache.http.client.methods.HttpGet',
'org.apache.http.client.methods.HttpHead',
'org.apache.http.client.methods.HttpOptions',
'org.apache.http.client.methods.HttpPost',
'org.apache.http.client.methods.HttpPut',
'org.apache.http.client.methods.HttpRequestBase',
'org.apache.http.client.methods.HttpTrace',
'org.apache.http.conn.ClientConnectionManager',
'org.apache.http.conn.ConnectionKeepAliveStrategy',
'org.apache.http.conn.params.ConnManagerParams',
'org.apache.http.conn.params.ConnPerRouteBean',
'org.apache.http.conn.params.ConnRouteParams',
'org.apache.http.conn.routing.HttpRoutePlanner',
'org.apache.http.conn.scheme.PlainSocketFactory',
'org.apache.http.conn.scheme.Scheme',
'org.apache.http.conn.scheme.SchemeRegistry',
'org.apache.http.conn.ssl.SSLSocketFactory',
'org.apache.http.conn.ssl.X509HostnameVerifier',
'org.apache.http.entity.AbstractHttpEntity',
'org.apache.http.impl.client.DefaultHttpClient',
'org.apache.http.impl.client.DefaultHttpRequestRetryHandler',
'org.apache.http.impl.conn.ProxySelectorRoutePlanner',
'org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager',
'org.apache.http.message.BasicHttpResponse',
'org.apache.http.params.BasicHttpParams',
'org.apache.http.params.HttpConnectionParams',
'org.apache.http.params.HttpParams',
'org.apache.http.params.HttpProtocolParams',
'org.apache.http.protocol.HttpContext',
'org.apache.http.protocol.HttpProcessor',
'org.apache.http.protocol.HttpRequestExecutor'
] ]
check { check {

View File

@ -0,0 +1 @@
7e537338d40a57ad469239acb6d828fa544fb52b

View File

@ -0,0 +1,27 @@
Copyright 2016, Google Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -1 +0,0 @@
4b95f4897fa13f2cd904aee711aeafc0c5295cd8

View File

@ -1 +0,0 @@
f6f66e966c70a83ffbdb6f17a0919eaf7c8aca7f

View File

@ -0,0 +1 @@
36ab73c0b5d4a67447eb89a3174cc76ced150bd1

View File

@ -0,0 +1,27 @@
Copyright 2016, Google Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1 @@
cb4bafbfd45b9d24efbb6138a31e37918fac015f

View File

@ -0,0 +1,27 @@
Copyright 2016, Google Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,201 @@
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:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) 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
(d) 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.

View File

@ -0,0 +1,201 @@
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:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) 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
(d) 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.

View File

@ -0,0 +1,28 @@
Copyright 2014, Google Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1 @@
25e0f45f3b3d1b4fccc8944845e51a7a4f359652

View File

@ -0,0 +1 @@
c0fe3a39b0f28d59de1986b3c50f018cd7cb9ec2

View File

@ -0,0 +1,201 @@
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:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) 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
(d) 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.

View File

@ -0,0 +1 @@
c0e88c78ce17c92d76bf46345faf3fa68833b216

View File

@ -0,0 +1 @@
7b4559a9513abd98da50958c56a10f8ae00cb0f7

View File

@ -0,0 +1 @@
226019ae816b42c59f1b06999aeeb73722b87200

View File

@ -0,0 +1 @@
0eda0d0f758c1cc525866e52e1226c4eb579d130

View File

@ -0,0 +1 @@
a72ea3a197937ef63a893e73df312dac0d813663

View File

@ -0,0 +1,28 @@
Copyright 2014, Google Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1 @@
28b0836f48c9705abf73829bbc536dba29a1329a

View File

@ -0,0 +1 @@
89507701249388e1ed5ddcf8c41f4ce1be7831ef

View File

@ -0,0 +1,202 @@
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:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) 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
(d) 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.

View File

@ -1 +0,0 @@
733db77aa8d9b2d68015189df76ab06304406e50

View File

@ -1 +0,0 @@
e7501a1b34325abb00d17dde96150604a0658b54

View File

@ -0,0 +1 @@
3c304d70f42f832e0a86d45bd437f692129299a4

View File

@ -0,0 +1,202 @@
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:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) 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
(d) 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.

View File

@ -0,0 +1,202 @@
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:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) 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
(d) 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.

View File

@ -0,0 +1,201 @@
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:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) 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
(d) 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.

View File

@ -0,0 +1,202 @@
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:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) 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
(d) 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.

View File

@ -0,0 +1 @@
54689fbf750a7f26e34fa1f1f96b883c53f51486

View File

@ -0,0 +1 @@
82e572b41e81ecf58d0d1e9a3953a05aa8f9c84b

View File

@ -0,0 +1 @@
b3282312ba82536fc9a7778cabfde149a875e877

View File

@ -0,0 +1,202 @@
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:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) 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
(d) 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.

View File

@ -0,0 +1 @@
89dcc04a7e028c3c963413a71f950703cf51f057

View File

@ -0,0 +1,31 @@
/*
* Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of JSR-310 nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

View File

@ -69,7 +69,6 @@ task googleCloudStorageFixture(type: AntFixture) {
/** A service account file that points to the Google Cloud Storage service emulated by the fixture **/ /** A service account file that points to the Google Cloud Storage service emulated by the fixture **/
task createServiceAccountFile() { task createServiceAccountFile() {
dependsOn googleCloudStorageFixture
doLast { doLast {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA") KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA")
keyPairGenerator.initialize(1024) keyPairGenerator.initialize(1024)
@ -83,11 +82,7 @@ task createServiceAccountFile() {
' "private_key_id": "' + UUID.randomUUID().toString() + '",\n' + ' "private_key_id": "' + UUID.randomUUID().toString() + '",\n' +
' "private_key": "-----BEGIN PRIVATE KEY-----\\n' + encodedKey + '\\n-----END PRIVATE KEY-----\\n",\n' + ' "private_key": "-----BEGIN PRIVATE KEY-----\\n' + encodedKey + '\\n-----END PRIVATE KEY-----\\n",\n' +
' "client_email": "integration_test@appspot.gserviceaccount.com",\n' + ' "client_email": "integration_test@appspot.gserviceaccount.com",\n' +
' "client_id": "123456789101112130594",\n' + ' "client_id": "123456789101112130594"\n' +
" \"auth_uri\": \"http://${googleCloudStorageFixture.addressAndPort}/o/oauth2/auth\",\n" +
" \"token_uri\": \"http://${googleCloudStorageFixture.addressAndPort}/o/oauth2/token\",\n" +
' "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",\n' +
' "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/integration_test%40appspot.gserviceaccount.com"\n' +
'}', 'UTF-8') '}', 'UTF-8')
} }
} }
@ -109,6 +104,7 @@ integTestCluster {
dependsOn createServiceAccountFile, googleCloudStorageFixture dependsOn createServiceAccountFile, googleCloudStorageFixture
/* Use a closure on the string to delay evaluation until tests are executed */ /* Use a closure on the string to delay evaluation until tests are executed */
setting 'gcs.client.integration_test.endpoint', "http://${ -> googleCloudStorageFixture.addressAndPort }" setting 'gcs.client.integration_test.endpoint', "http://${ -> googleCloudStorageFixture.addressAndPort }"
setting 'gcs.client.integration_test.token_uri', "http://${ -> googleCloudStorageFixture.addressAndPort }/o/oauth2/token"
} else { } else {
println "Using an external service to test the repository-gcs plugin" println "Using an external service to test the repository-gcs plugin"
} }

View File

@ -31,13 +31,18 @@ import java.io.BufferedReader;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.GZIPInputStream;
import static java.util.Collections.emptyMap; import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
@ -52,7 +57,7 @@ import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
*/ */
public class GoogleCloudStorageTestServer { public class GoogleCloudStorageTestServer {
private static byte[] EMPTY_BYTE = new byte[0]; private static final byte[] EMPTY_BYTE = new byte[0];
/** List of the buckets stored on this test server **/ /** List of the buckets stored on this test server **/
private final Map<String, Bucket> buckets = ConcurrentCollections.newConcurrentMap(); private final Map<String, Bucket> buckets = ConcurrentCollections.newConcurrentMap();
@ -63,13 +68,6 @@ public class GoogleCloudStorageTestServer {
/** Server endpoint **/ /** Server endpoint **/
private final String endpoint; private final String endpoint;
/**
* Creates a {@link GoogleCloudStorageTestServer} with the default endpoint
*/
GoogleCloudStorageTestServer() {
this("https://www.googleapis.com");
}
/** /**
* Creates a {@link GoogleCloudStorageTestServer} with a custom endpoint * Creates a {@link GoogleCloudStorageTestServer} with a custom endpoint
*/ */
@ -87,29 +85,6 @@ public class GoogleCloudStorageTestServer {
return endpoint; return endpoint;
} }
/**
* Returns a Google Cloud Storage response for the given request
*
* @param method the HTTP method of the request
* @param url the HTTP URL of the request
* @param headers the HTTP headers of the request
* @param body the HTTP request body
* @return a {@link Response}
*
* @throws IOException if something goes wrong
*/
public Response handle(final String method,
final String url,
final Map<String, List<String>> headers,
byte[] body) throws IOException {
final int questionMark = url.indexOf('?');
if (questionMark == -1) {
return handle(method, url, null, headers, body);
}
return handle(method, url.substring(0, questionMark), url.substring(questionMark + 1), headers, body);
}
/** /**
* Returns a Google Cloud Storage response for the given request * Returns a Google Cloud Storage response for the given request
* *
@ -165,7 +140,7 @@ public class GoogleCloudStorageTestServer {
// //
// https://cloud.google.com/storage/docs/json_api/v1/buckets/get // https://cloud.google.com/storage/docs/json_api/v1/buckets/get
handlers.insert("GET " + endpoint + "/storage/v1/b/{bucket}", (params, headers, body) -> { handlers.insert("GET " + endpoint + "/storage/v1/b/{bucket}", (params, headers, body) -> {
String name = params.get("bucket"); final String name = params.get("bucket");
if (Strings.hasText(name) == false) { if (Strings.hasText(name) == false) {
return newError(RestStatus.INTERNAL_SERVER_ERROR, "bucket name is missing"); return newError(RestStatus.INTERNAL_SERVER_ERROR, "bucket name is missing");
} }
@ -181,7 +156,7 @@ public class GoogleCloudStorageTestServer {
// //
// https://cloud.google.com/storage/docs/json_api/v1/objects/get // https://cloud.google.com/storage/docs/json_api/v1/objects/get
handlers.insert("GET " + endpoint + "/storage/v1/b/{bucket}/o/{object}", (params, headers, body) -> { handlers.insert("GET " + endpoint + "/storage/v1/b/{bucket}/o/{object}", (params, headers, body) -> {
String objectName = params.get("object"); final String objectName = params.get("object");
if (Strings.hasText(objectName) == false) { if (Strings.hasText(objectName) == false) {
return newError(RestStatus.INTERNAL_SERVER_ERROR, "object name is missing"); return newError(RestStatus.INTERNAL_SERVER_ERROR, "object name is missing");
} }
@ -191,7 +166,7 @@ public class GoogleCloudStorageTestServer {
return newError(RestStatus.NOT_FOUND, "bucket not found"); return newError(RestStatus.NOT_FOUND, "bucket not found");
} }
for (Map.Entry<String, byte[]> object : bucket.objects.entrySet()) { for (final Map.Entry<String, byte[]> object : bucket.objects.entrySet()) {
if (object.getKey().equals(objectName)) { if (object.getKey().equals(objectName)) {
return newResponse(RestStatus.OK, emptyMap(), buildObjectResource(bucket.name, objectName, object.getValue())); return newResponse(RestStatus.OK, emptyMap(), buildObjectResource(bucket.name, objectName, object.getValue()));
} }
@ -203,7 +178,7 @@ public class GoogleCloudStorageTestServer {
// //
// https://cloud.google.com/storage/docs/json_api/v1/objects/delete // https://cloud.google.com/storage/docs/json_api/v1/objects/delete
handlers.insert("DELETE " + endpoint + "/storage/v1/b/{bucket}/o/{object}", (params, headers, body) -> { handlers.insert("DELETE " + endpoint + "/storage/v1/b/{bucket}/o/{object}", (params, headers, body) -> {
String objectName = params.get("object"); final String objectName = params.get("object");
if (Strings.hasText(objectName) == false) { if (Strings.hasText(objectName) == false) {
return newError(RestStatus.INTERNAL_SERVER_ERROR, "object name is missing"); return newError(RestStatus.INTERNAL_SERVER_ERROR, "object name is missing");
} }
@ -224,33 +199,157 @@ public class GoogleCloudStorageTestServer {
// //
// https://cloud.google.com/storage/docs/json_api/v1/objects/insert // https://cloud.google.com/storage/docs/json_api/v1/objects/insert
handlers.insert("POST " + endpoint + "/upload/storage/v1/b/{bucket}/o", (params, headers, body) -> { handlers.insert("POST " + endpoint + "/upload/storage/v1/b/{bucket}/o", (params, headers, body) -> {
if ("resumable".equals(params.get("uploadType")) == false) { final String uploadType = params.get("uploadType");
return newError(RestStatus.INTERNAL_SERVER_ERROR, "upload type must be resumable"); if ("resumable".equals(uploadType)) {
}
final String objectName = params.get("name"); final String objectName = params.get("name");
if (Strings.hasText(objectName) == false) { if (Strings.hasText(objectName) == false) {
return newError(RestStatus.INTERNAL_SERVER_ERROR, "object name is missing"); return newError(RestStatus.INTERNAL_SERVER_ERROR, "object name is missing");
} }
final Bucket bucket = buckets.get(params.get("bucket")); final Bucket bucket = buckets.get(params.get("bucket"));
if (bucket == null) { if (bucket == null) {
return newError(RestStatus.NOT_FOUND, "bucket not found"); return newError(RestStatus.NOT_FOUND, "bucket not found");
} }
if (bucket.objects.putIfAbsent(objectName, EMPTY_BYTE) == null) {
if (bucket.objects.put(objectName, EMPTY_BYTE) == null) { final String location = endpoint + "/upload/storage/v1/b/" + bucket.name + "/o?uploadType=resumable&upload_id="
String location = endpoint + "/upload/storage/v1/b/" + bucket.name + "/o?uploadType=resumable&upload_id=" + objectName; + objectName;
return new Response(RestStatus.CREATED, singletonMap("Location", location), XContentType.JSON.mediaType(), EMPTY_BYTE); return new Response(RestStatus.CREATED, singletonMap("Location", location), XContentType.JSON.mediaType(), EMPTY_BYTE);
} else { } else {
return newError(RestStatus.CONFLICT, "object already exist"); return newError(RestStatus.CONFLICT, "object already exist");
} }
} else if ("multipart".equals(uploadType)) {
/*
* A multipart/related request body looks like this (note the binary dump inside a text blob! nice!):
* --__END_OF_PART__
* Content-Length: 135
* Content-Type: application/json; charset=UTF-8
* content-transfer-encoding: binary
*
* {"bucket":"bucket_test","crc32c":"7XacHQ==","md5Hash":"fVztGkklMlUamsSmJK7W+w==",
* "name":"tests-KEwE3bU4TuyetBgQIghmUw/master.dat-temp"}
* --__END_OF_PART__
* content-transfer-encoding: binary
*
* KEwE3bU4TuyetBgQIghmUw
* --__END_OF_PART__--
*/
String boundary = "__END_OF_PART__";
// Determine the multipart boundary
final List<String> contentTypes = headers.getOrDefault("Content-Type", headers.get("Content-type"));
if (contentTypes != null) {
final String contentType = contentTypes.get(0);
if ((contentType != null) && contentType.contains("multipart/related; boundary=")) {
boundary = contentType.replace("multipart/related; boundary=", "");
}
}
InputStream inputStreamBody = new ByteArrayInputStream(body);
final List<String> contentEncodings = headers.getOrDefault("Content-Encoding", headers.get("Content-encoding"));
if (contentEncodings != null) {
if (contentEncodings.stream().anyMatch(x -> "gzip".equalsIgnoreCase(x))) {
inputStreamBody = new GZIPInputStream(inputStreamBody);
}
}
// Read line by line ?both? parts of the multipart. Decoding headers as
// IS_8859_1 is safe.
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStreamBody, StandardCharsets.ISO_8859_1))) {
String line;
// read first part delimiter
line = reader.readLine();
if ((line == null) || (line.equals("--" + boundary) == false)) {
return newError(RestStatus.INTERNAL_SERVER_ERROR,
"Error parsing multipart request. Does not start with the part delimiter.");
}
final Map<String, List<String>> firstPartHeaders = new HashMap<>();
// Reads the first part's headers, if any
while ((line = reader.readLine()) != null) {
if (line.equals("\r\n") || (line.length() == 0)) {
// end of headers
break;
} else {
final String[] header = line.split(":", 2);
firstPartHeaders.put(header[0], singletonList(header[1]));
}
}
final List<String> firstPartContentTypes = firstPartHeaders.getOrDefault("Content-Type",
firstPartHeaders.get("Content-type"));
if ((firstPartContentTypes == null)
|| (firstPartContentTypes.stream().noneMatch(x -> x.contains("application/json")))) {
return newError(RestStatus.INTERNAL_SERVER_ERROR,
"Error parsing multipart request. Metadata part expected to have the \"application/json\" content type.");
}
// read metadata part, a single line
line = reader.readLine();
final byte[] metadata = line.getBytes(StandardCharsets.ISO_8859_1);
if ((firstPartContentTypes != null) && (firstPartContentTypes.stream().anyMatch((x -> x.contains("charset=utf-8"))))) {
// decode as utf-8
line = new String(metadata, StandardCharsets.UTF_8);
}
final Matcher objectNameMatcher = Pattern.compile("\"name\":\"([^\"]*)\"").matcher(line);
objectNameMatcher.find();
final String objectName = objectNameMatcher.group(1);
final Matcher bucketNameMatcher = Pattern.compile("\"bucket\":\"([^\"]*)\"").matcher(line);
bucketNameMatcher.find();
final String bucketName = bucketNameMatcher.group(1);
// read second part delimiter
line = reader.readLine();
if ((line == null) || (line.equals("--" + boundary) == false)) {
return newError(RestStatus.INTERNAL_SERVER_ERROR,
"Error parsing multipart request. Second part does not start with delimiter. "
+ "Is the metadata multi-line?");
}
final Map<String, List<String>> secondPartHeaders = new HashMap<>();
// Reads the second part's headers, if any
while ((line = reader.readLine()) != null) {
if (line.equals("\r\n") || (line.length() == 0)) {
// end of headers
break;
} else {
final String[] header = line.split(":", 2);
secondPartHeaders.put(header[0], singletonList(header[1]));
}
}
final List<String> secondPartTransferEncoding = secondPartHeaders.getOrDefault("Content-Transfer-Encoding",
secondPartHeaders.get("content-transfer-encoding"));
if ((secondPartTransferEncoding == null)
|| (secondPartTransferEncoding.stream().noneMatch(x -> x.contains("binary")))) {
return newError(RestStatus.INTERNAL_SERVER_ERROR,
"Error parsing multipart request. Data part expected to have the \"binary\" content transfer encoding.");
}
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
int c;
while ((c = reader.read()) != -1) {
// one char to one byte, because of the ISO_8859_1 encoding
baos.write(c);
}
final byte[] temp = baos.toByteArray();
final byte[] trailingEnding = ("\r\n--" + boundary + "--\r\n").getBytes(StandardCharsets.ISO_8859_1);
// check trailing
for (int i = trailingEnding.length - 1; i >= 0; i--) {
if (trailingEnding[i] != temp[(temp.length - trailingEnding.length) + i]) {
return newError(RestStatus.INTERNAL_SERVER_ERROR, "Error parsing multipart request.");
}
}
final Bucket bucket = buckets.get(bucketName);
if (bucket == null) {
return newError(RestStatus.NOT_FOUND, "bucket not found");
}
final byte[] objectData = Arrays.copyOf(temp, temp.length - trailingEnding.length);
if ((objectName != null) && (bucketName != null) && (objectData != null)) {
bucket.objects.put(objectName, objectData);
return new Response(RestStatus.OK, emptyMap(), XContentType.JSON.mediaType(), metadata);
} else {
return newError(RestStatus.INTERNAL_SERVER_ERROR, "error parsing multipart request");
}
}
} else {
return newError(RestStatus.INTERNAL_SERVER_ERROR, "upload type must be resumable or multipart");
}
}); });
// Insert Object (upload) // Insert Object (upload)
// //
// https://cloud.google.com/storage/docs/json_api/v1/how-tos/resumable-upload // https://cloud.google.com/storage/docs/json_api/v1/how-tos/resumable-upload
handlers.insert("PUT " + endpoint + "/upload/storage/v1/b/{bucket}/o", (params, headers, body) -> { handlers.insert("PUT " + endpoint + "/upload/storage/v1/b/{bucket}/o", (params, headers, body) -> {
String objectId = params.get("upload_id"); final String objectId = params.get("upload_id");
if (Strings.hasText(objectId) == false) { if (Strings.hasText(objectId) == false) {
return newError(RestStatus.INTERNAL_SERVER_ERROR, "upload id is missing"); return newError(RestStatus.INTERNAL_SERVER_ERROR, "upload id is missing");
} }
@ -268,37 +367,45 @@ public class GoogleCloudStorageTestServer {
return newResponse(RestStatus.OK, emptyMap(), buildObjectResource(bucket.name, objectId, body)); return newResponse(RestStatus.OK, emptyMap(), buildObjectResource(bucket.name, objectId, body));
}); });
// Copy Object // Rewrite or Copy Object
// //
// https://cloud.google.com/storage/docs/json_api/v1/objects/rewrite
// https://cloud.google.com/storage/docs/json_api/v1/objects/copy // https://cloud.google.com/storage/docs/json_api/v1/objects/copy
handlers.insert("POST " + endpoint + "/storage/v1/b/{srcBucket}/o/{src}/copyTo/b/{destBucket}/o/{dest}", (params, headers, body)-> { handlers.insert("POST " + endpoint + "/storage/v1/b/{srcBucket}/o/{src}/{action}/b/{destBucket}/o/{dest}",
String source = params.get("src"); (params, headers, body) -> {
final String action = params.get("action");
if ((action.equals("rewriteTo") == false) && (action.equals("copyTo") == false)) {
return newError(RestStatus.INTERNAL_SERVER_ERROR, "Action not implemented. None of \"rewriteTo\" or \"copyTo\".");
}
final String source = params.get("src");
if (Strings.hasText(source) == false) { if (Strings.hasText(source) == false) {
return newError(RestStatus.INTERNAL_SERVER_ERROR, "source object name is missing"); return newError(RestStatus.INTERNAL_SERVER_ERROR, "source object name is missing");
} }
final Bucket srcBucket = buckets.get(params.get("srcBucket")); final Bucket srcBucket = buckets.get(params.get("srcBucket"));
if (srcBucket == null) { if (srcBucket == null) {
return newError(RestStatus.NOT_FOUND, "source bucket not found"); return newError(RestStatus.NOT_FOUND, "source bucket not found");
} }
final String dest = params.get("dest");
String dest = params.get("dest");
if (Strings.hasText(dest) == false) { if (Strings.hasText(dest) == false) {
return newError(RestStatus.INTERNAL_SERVER_ERROR, "destination object name is missing"); return newError(RestStatus.INTERNAL_SERVER_ERROR, "destination object name is missing");
} }
final Bucket destBucket = buckets.get(params.get("destBucket")); final Bucket destBucket = buckets.get(params.get("destBucket"));
if (destBucket == null) { if (destBucket == null) {
return newError(RestStatus.NOT_FOUND, "destination bucket not found"); return newError(RestStatus.NOT_FOUND, "destination bucket not found");
} }
final byte[] sourceBytes = srcBucket.objects.get(source); final byte[] sourceBytes = srcBucket.objects.get(source);
if (sourceBytes == null) { if (sourceBytes == null) {
return newError(RestStatus.NOT_FOUND, "source object not found"); return newError(RestStatus.NOT_FOUND, "source object not found");
} }
destBucket.objects.put(dest, sourceBytes); destBucket.objects.put(dest, sourceBytes);
if (action.equals("rewriteTo")) {
final XContentBuilder respBuilder = jsonBuilder();
buildRewriteResponse(respBuilder, destBucket.name, dest, sourceBytes.length);
return newResponse(RestStatus.OK, emptyMap(), respBuilder);
} else {
assert action.equals("copyTo");
return newResponse(RestStatus.OK, emptyMap(), buildObjectResource(destBucket.name, dest, sourceBytes)); return newResponse(RestStatus.OK, emptyMap(), buildObjectResource(destBucket.name, dest, sourceBytes));
}
}); });
// List Objects // List Objects
@ -317,8 +424,8 @@ public class GoogleCloudStorageTestServer {
builder.startArray("items"); builder.startArray("items");
final String prefixParam = params.get("prefix"); final String prefixParam = params.get("prefix");
for (Map.Entry<String, byte[]> object : bucket.objects.entrySet()) { for (final Map.Entry<String, byte[]> object : bucket.objects.entrySet()) {
if (prefixParam != null && object.getKey().startsWith(prefixParam) == false) { if ((prefixParam != null) && (object.getKey().startsWith(prefixParam) == false)) {
continue; continue;
} }
buildObjectResource(builder, bucket.name, object.getKey(), object.getValue()); buildObjectResource(builder, bucket.name, object.getKey(), object.getValue());
@ -333,7 +440,7 @@ public class GoogleCloudStorageTestServer {
// //
// https://cloud.google.com/storage/docs/request-body // https://cloud.google.com/storage/docs/request-body
handlers.insert("GET " + endpoint + "/download/storage/v1/b/{bucket}/o/{object}", (params, headers, body) -> { handlers.insert("GET " + endpoint + "/download/storage/v1/b/{bucket}/o/{object}", (params, headers, body) -> {
String object = params.get("object"); final String object = params.get("object");
if (Strings.hasText(object) == false) { if (Strings.hasText(object) == false) {
return newError(RestStatus.INTERNAL_SERVER_ERROR, "object id is missing"); return newError(RestStatus.INTERNAL_SERVER_ERROR, "object id is missing");
} }
@ -353,7 +460,7 @@ public class GoogleCloudStorageTestServer {
// Batch // Batch
// //
// https://cloud.google.com/storage/docs/json_api/v1/how-tos/batch // https://cloud.google.com/storage/docs/json_api/v1/how-tos/batch
handlers.insert("POST " + endpoint + "/batch", (params, headers, body) -> { handlers.insert("POST " + endpoint + "/batch/storage/v1", (params, headers, body) -> {
final List<Response> batchedResponses = new ArrayList<>(); final List<Response> batchedResponses = new ArrayList<>();
// A batch request body looks like this: // A batch request body looks like this:
@ -385,7 +492,7 @@ public class GoogleCloudStorageTestServer {
final List<String> contentTypes = headers.getOrDefault("Content-Type", headers.get("Content-type")); final List<String> contentTypes = headers.getOrDefault("Content-Type", headers.get("Content-type"));
if (contentTypes != null) { if (contentTypes != null) {
final String contentType = contentTypes.get(0); final String contentType = contentTypes.get(0);
if (contentType != null && contentType.contains("multipart/mixed; boundary=")) { if ((contentType != null) && contentType.contains("multipart/mixed; boundary=")) {
boundary = contentType.replace("multipart/mixed; boundary=", ""); boundary = contentType.replace("multipart/mixed; boundary=", "");
} }
} }
@ -398,25 +505,25 @@ public class GoogleCloudStorageTestServer {
while ((line = reader.readLine()) != null) { while ((line = reader.readLine()) != null) {
// Start of a batched request // Start of a batched request
if (line.equals("--" + boundary)) { if (line.equals("--" + boundary)) {
Map<String, List<String>> batchedHeaders = new HashMap<>(); final Map<String, List<String>> batchedHeaders = new HashMap<>();
// Reads the headers, if any // Reads the headers, if any
while ((line = reader.readLine()) != null) { while ((line = reader.readLine()) != null) {
if (line.equals("\r\n") || line.length() == 0) { if (line.equals("\r\n") || (line.length() == 0)) {
// end of headers // end of headers
break; break;
} else { } else {
String[] header = line.split(":", 2); final String[] header = line.split(":", 2);
batchedHeaders.put(header[0], singletonList(header[1])); batchedHeaders.put(header[0], singletonList(header[1]));
} }
} }
// Reads the method and URL // Reads the method and URL
line = reader.readLine(); line = reader.readLine();
String batchedUrl = line.substring(0, line.lastIndexOf(' ')); final String batchedUrl = line.substring(0, line.lastIndexOf(' '));
final Map<String, String> batchedParams = new HashMap<>(); final Map<String, String> batchedParams = new HashMap<>();
int questionMark = batchedUrl.indexOf('?'); final int questionMark = batchedUrl.indexOf('?');
if (questionMark != -1) { if (questionMark != -1) {
RestUtils.decodeQueryString(batchedUrl.substring(questionMark + 1), 0, batchedParams); RestUtils.decodeQueryString(batchedUrl.substring(questionMark + 1), 0, batchedParams);
} }
@ -424,16 +531,16 @@ public class GoogleCloudStorageTestServer {
// Reads the body // Reads the body
line = reader.readLine(); line = reader.readLine();
byte[] batchedBody = new byte[0]; byte[] batchedBody = new byte[0];
if (line != null || line.startsWith("--" + boundary) == false) { if ((line != null) || (line.startsWith("--" + boundary) == false)) {
batchedBody = line.getBytes(StandardCharsets.UTF_8); batchedBody = line.getBytes(StandardCharsets.UTF_8);
} }
// Executes the batched request // Executes the batched request
RequestHandler handler = handlers.retrieve(batchedUrl, batchedParams); final RequestHandler handler = handlers.retrieve(batchedUrl, batchedParams);
if (handler != null) { if (handler != null) {
try { try {
batchedResponses.add(handler.execute(batchedParams, batchedHeaders, batchedBody)); batchedResponses.add(handler.execute(batchedParams, batchedHeaders, batchedBody));
} catch (IOException e) { } catch (final IOException e) {
batchedResponses.add(newError(RestStatus.INTERNAL_SERVER_ERROR, e.getMessage())); batchedResponses.add(newError(RestStatus.INTERNAL_SERVER_ERROR, e.getMessage()));
} }
} }
@ -442,11 +549,11 @@ public class GoogleCloudStorageTestServer {
} }
// Now we can build the response // Now we can build the response
String sep = "--"; final String sep = "--";
String line = "\r\n"; final String line = "\r\n";
StringBuilder builder = new StringBuilder(); final StringBuilder builder = new StringBuilder();
for (Response response : batchedResponses) { for (final Response response : batchedResponses) {
builder.append(sep).append(boundary).append(line); builder.append(sep).append(boundary).append(line);
builder.append("Content-Type: application/http").append(line); builder.append("Content-Type: application/http").append(line);
builder.append(line); builder.append(line);
@ -465,7 +572,7 @@ public class GoogleCloudStorageTestServer {
builder.append(line); builder.append(line);
builder.append(sep).append(boundary).append(sep); builder.append(sep).append(boundary).append(sep);
byte[] content = builder.toString().getBytes(StandardCharsets.UTF_8); final byte[] content = builder.toString().getBytes(StandardCharsets.UTF_8);
return new Response(RestStatus.OK, emptyMap(), "multipart/mixed; boundary=" + boundary, content); return new Response(RestStatus.OK, emptyMap(), "multipart/mixed; boundary=" + boundary, content);
}); });
@ -525,7 +632,7 @@ public class GoogleCloudStorageTestServer {
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
BytesReference.bytes(xContentBuilder).writeTo(out); BytesReference.bytes(xContentBuilder).writeTo(out);
return new Response(status, headers, XContentType.JSON.mediaType(), out.toByteArray()); return new Response(status, headers, XContentType.JSON.mediaType(), out.toByteArray());
} catch (IOException e) { } catch (final IOException e) {
return newError(RestStatus.INTERNAL_SERVER_ERROR, e.getMessage()); return newError(RestStatus.INTERNAL_SERVER_ERROR, e.getMessage());
} }
} }
@ -552,8 +659,8 @@ public class GoogleCloudStorageTestServer {
BytesReference.bytes(builder).writeTo(out); BytesReference.bytes(builder).writeTo(out);
} }
return new Response(status, emptyMap(), XContentType.JSON.mediaType(), out.toByteArray()); return new Response(status, emptyMap(), XContentType.JSON.mediaType(), out.toByteArray());
} catch (IOException e) { } catch (final IOException e) {
byte[] bytes = (message != null ? message : "something went wrong").getBytes(StandardCharsets.UTF_8); final byte[] bytes = (message != null ? message : "something went wrong").getBytes(StandardCharsets.UTF_8);
return new Response(RestStatus.INTERNAL_SERVER_ERROR, emptyMap(), " text/plain", bytes); return new Response(RestStatus.INTERNAL_SERVER_ERROR, emptyMap(), " text/plain", bytes);
} }
} }
@ -565,6 +672,7 @@ public class GoogleCloudStorageTestServer {
private static XContentBuilder buildBucketResource(final String name) throws IOException { private static XContentBuilder buildBucketResource(final String name) throws IOException {
return jsonBuilder().startObject() return jsonBuilder().startObject()
.field("kind", "storage#bucket") .field("kind", "storage#bucket")
.field("name", name)
.field("id", name) .field("id", name)
.endObject(); .endObject();
} }
@ -573,8 +681,7 @@ public class GoogleCloudStorageTestServer {
* Storage Object JSON representation as defined in * Storage Object JSON representation as defined in
* https://cloud.google.com/storage/docs/json_api/v1/objects#resource * https://cloud.google.com/storage/docs/json_api/v1/objects#resource
*/ */
private static XContentBuilder buildObjectResource(final String bucket, final String name, final byte[] bytes) private static XContentBuilder buildObjectResource(final String bucket, final String name, final byte[] bytes) throws IOException {
throws IOException {
return buildObjectResource(jsonBuilder(), bucket, name, bytes); return buildObjectResource(jsonBuilder(), bucket, name, bytes);
} }
@ -590,7 +697,32 @@ public class GoogleCloudStorageTestServer {
.field("kind", "storage#object") .field("kind", "storage#object")
.field("id", String.join("/", bucket, name)) .field("id", String.join("/", bucket, name))
.field("name", name) .field("name", name)
.field("bucket", bucket)
.field("size", String.valueOf(bytes.length)) .field("size", String.valueOf(bytes.length))
.endObject(); .endObject();
} }
/**
* Builds the rewrite response as defined by
* https://cloud.google.com/storage/docs/json_api/v1/objects/rewrite
*/
private static XContentBuilder buildRewriteResponse(final XContentBuilder builder,
final String destBucket,
final String dest,
final int byteSize) throws IOException {
builder.startObject()
.field("kind", "storage#rewriteResponse")
.field("totalBytesRewritten", String.valueOf(byteSize))
.field("objectSize", String.valueOf(byteSize))
.field("done", true)
.startObject("resource")
.field("kind", "storage#object")
.field("id", String.join("/", destBucket, dest))
.field("name", dest)
.field("bucket", destBucket)
.field("size", String.valueOf(byteSize))
.endObject()
.endObject();
return builder;
}
} }

View File

@ -19,59 +19,55 @@
package org.elasticsearch.repositories.gcs; package org.elasticsearch.repositories.gcs;
import com.google.api.client.googleapis.batch.BatchRequest; import com.google.cloud.ReadChannel;
import com.google.api.client.googleapis.batch.json.JsonBatchCallback; import com.google.cloud.WriteChannel;
import com.google.api.client.googleapis.json.GoogleJsonError; import com.google.cloud.storage.Blob;
import com.google.api.client.googleapis.json.GoogleJsonResponseException; import com.google.cloud.storage.BlobId;
import com.google.api.client.http.HttpHeaders; import com.google.cloud.storage.BlobInfo;
import com.google.api.client.http.InputStreamContent; import com.google.cloud.storage.Bucket;
import com.google.api.services.storage.Storage; import com.google.cloud.storage.Storage;
import com.google.api.services.storage.model.Bucket; import com.google.cloud.storage.Storage.BlobListOption;
import com.google.api.services.storage.model.Objects; import com.google.cloud.storage.Storage.CopyRequest;
import com.google.api.services.storage.model.StorageObject; import org.elasticsearch.common.SuppressForbidden;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.blobstore.BlobContainer; import org.elasticsearch.common.blobstore.BlobContainer;
import org.elasticsearch.common.blobstore.BlobMetaData; import org.elasticsearch.common.blobstore.BlobMetaData;
import org.elasticsearch.common.blobstore.BlobPath; import org.elasticsearch.common.blobstore.BlobPath;
import org.elasticsearch.common.blobstore.BlobStore; import org.elasticsearch.common.blobstore.BlobStore;
import org.elasticsearch.common.blobstore.BlobStoreException; import org.elasticsearch.common.blobstore.BlobStoreException;
import org.elasticsearch.common.blobstore.support.PlainBlobMetaData; import org.elasticsearch.common.blobstore.support.PlainBlobMetaData;
import org.elasticsearch.common.collect.MapBuilder;
import org.elasticsearch.common.component.AbstractComponent; import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.CountDown; import org.elasticsearch.core.internal.io.Streams;
import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.file.NoSuchFileException; import java.nio.file.NoSuchFileException;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Spliterator;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import static java.net.HttpURLConnection.HTTP_NOT_FOUND;
class GoogleCloudStorageBlobStore extends AbstractComponent implements BlobStore { class GoogleCloudStorageBlobStore extends AbstractComponent implements BlobStore {
/** // The recommended maximum size of a blob that should be uploaded in a single
* Google Cloud Storage batch requests are limited to 1000 operations // request. Larger files should be uploaded over multiple requests (this is
**/ // called "resumable upload")
private static final int MAX_BATCHING_REQUESTS = 999; // https://cloud.google.com/storage/docs/json_api/v1/how-tos/resumable-upload
private static final int LARGE_BLOB_THRESHOLD_BYTE_SIZE = 5 * 1024 * 1024;
private final Storage client; private final Storage storage;
private final String bucket; private final String bucket;
GoogleCloudStorageBlobStore(Settings settings, String bucket, Storage storageClient) { GoogleCloudStorageBlobStore(Settings settings, String bucket, Storage storage) {
super(settings); super(settings);
this.bucket = bucket; this.bucket = bucket;
this.client = storageClient; this.storage = storage;
if (doesBucketExist(bucket) == false) { if (doesBucketExist(bucket) == false) {
throw new BlobStoreException("Bucket [" + bucket + "] does not exist"); throw new BlobStoreException("Bucket [" + bucket + "] does not exist");
} }
@ -99,59 +95,45 @@ class GoogleCloudStorageBlobStore extends AbstractComponent implements BlobStore
*/ */
boolean doesBucketExist(String bucketName) { boolean doesBucketExist(String bucketName) {
try { try {
return SocketAccess.doPrivilegedIOException(() -> { final Bucket bucket = SocketAccess.doPrivilegedIOException(() -> storage.get(bucketName));
try { return bucket != null;
Bucket bucket = client.buckets().get(bucketName).execute(); } catch (final Exception e) {
if (bucket != null) {
return Strings.hasText(bucket.getId());
}
} catch (GoogleJsonResponseException e) {
GoogleJsonError error = e.getDetails();
if ((e.getStatusCode() == HTTP_NOT_FOUND) || ((error != null) && (error.getCode() == HTTP_NOT_FOUND))) {
return false;
}
throw e;
}
return false;
});
} catch (IOException e) {
throw new BlobStoreException("Unable to check if bucket [" + bucketName + "] exists", e); throw new BlobStoreException("Unable to check if bucket [" + bucketName + "] exists", e);
} }
} }
/** /**
* List all blobs in the bucket * List blobs in the bucket under the specified path. The path root is removed.
* *
* @param path base path of the blobs to list * @param path
* base path of the blobs to list
* @return a map of blob names and their metadata * @return a map of blob names and their metadata
*/ */
Map<String, BlobMetaData> listBlobs(String path) throws IOException { Map<String, BlobMetaData> listBlobs(String path) throws IOException {
return SocketAccess.doPrivilegedIOException(() -> listBlobsByPath(bucket, path, path)); return listBlobsByPrefix(path, "");
} }
/** /**
* List all blobs in the bucket which have a prefix * List all blobs in the bucket which have a prefix
* *
* @param path base path of the blobs to list * @param path
* @param prefix prefix of the blobs to list * base path of the blobs to list. This path is removed from the
* @return a map of blob names and their metadata * names of the blobs returned.
* @param prefix
* prefix of the blobs to list.
* @return a map of blob names and their metadata.
*/ */
Map<String, BlobMetaData> listBlobsByPrefix(String path, String prefix) throws IOException { Map<String, BlobMetaData> listBlobsByPrefix(String path, String prefix) throws IOException {
return SocketAccess.doPrivilegedIOException(() -> listBlobsByPath(bucket, buildKey(path, prefix), path)); final String pathPrefix = buildKey(path, prefix);
} final MapBuilder<String, BlobMetaData> mapBuilder = MapBuilder.newMapBuilder();
SocketAccess.doPrivilegedVoidIOException(() -> {
/** storage.get(bucket).list(BlobListOption.prefix(pathPrefix)).iterateAll().forEach(blob -> {
* Lists all blobs in a given bucket assert blob.getName().startsWith(path);
* final String suffixName = blob.getName().substring(path.length());
* @param bucketName name of the bucket mapBuilder.put(suffixName, new PlainBlobMetaData(suffixName, blob.getSize()));
* @param path base path of the blobs to list });
* @param pathToRemove if true, this path part is removed from blob name });
* @return a map of blob names and their metadata return mapBuilder.immutableMap();
*/
private Map<String, BlobMetaData> listBlobsByPath(String bucketName, String path, String pathToRemove) throws IOException {
return blobsStream(client, bucketName, path, MAX_BATCHING_REQUESTS)
.map(new BlobMetaDataConverter(pathToRemove))
.collect(Collectors.toMap(PlainBlobMetaData::name, Function.identity()));
} }
/** /**
@ -161,19 +143,9 @@ class GoogleCloudStorageBlobStore extends AbstractComponent implements BlobStore
* @return true if the blob exists, false otherwise * @return true if the blob exists, false otherwise
*/ */
boolean blobExists(String blobName) throws IOException { boolean blobExists(String blobName) throws IOException {
try { final BlobId blobId = BlobId.of(bucket, blobName);
StorageObject blob = SocketAccess.doPrivilegedIOException(() -> client.objects().get(bucket, blobName).execute()); final Blob blob = SocketAccess.doPrivilegedIOException(() -> storage.get(blobId));
if (blob != null) { return blob != null;
return Strings.hasText(blob.getId());
}
} catch (GoogleJsonResponseException e) {
GoogleJsonError error = e.getDetails();
if ((e.getStatusCode() == HTTP_NOT_FOUND) || ((error != null) && (error.getCode() == HTTP_NOT_FOUND))) {
return false;
}
throw e;
}
return false;
} }
/** /**
@ -183,18 +155,29 @@ class GoogleCloudStorageBlobStore extends AbstractComponent implements BlobStore
* @return an InputStream * @return an InputStream
*/ */
InputStream readBlob(String blobName) throws IOException { InputStream readBlob(String blobName) throws IOException {
try { final BlobId blobId = BlobId.of(bucket, blobName);
return SocketAccess.doPrivilegedIOException(() -> { final Blob blob = SocketAccess.doPrivilegedIOException(() -> storage.get(blobId));
Storage.Objects.Get object = client.objects().get(bucket, blobName); if (blob == null) {
return object.executeMediaAsInputStream(); throw new NoSuchFileException("Blob [" + blobName + "] does not exit");
}
final ReadChannel readChannel = SocketAccess.doPrivilegedIOException(blob::reader);
return Channels.newInputStream(new ReadableByteChannel() {
@SuppressForbidden(reason = "Channel is based of a socket not a file")
@Override
public int read(ByteBuffer dst) throws IOException {
return SocketAccess.doPrivilegedIOException(() -> readChannel.read(dst));
}
@Override
public boolean isOpen() {
return readChannel.isOpen();
}
@Override
public void close() throws IOException {
SocketAccess.doPrivilegedVoidIOException(readChannel::close);
}
}); });
} catch (GoogleJsonResponseException e) {
GoogleJsonError error = e.getDetails();
if ((e.getStatusCode() == HTTP_NOT_FOUND) || ((error != null) && (error.getCode() == HTTP_NOT_FOUND))) {
throw new NoSuchFileException(e.getMessage());
}
throw e;
}
} }
/** /**
@ -204,14 +187,58 @@ class GoogleCloudStorageBlobStore extends AbstractComponent implements BlobStore
* @param blobSize expected size of the blob to be written * @param blobSize expected size of the blob to be written
*/ */
void writeBlob(String blobName, InputStream inputStream, long blobSize) throws IOException { void writeBlob(String blobName, InputStream inputStream, long blobSize) throws IOException {
SocketAccess.doPrivilegedVoidIOException(() -> { final BlobInfo blobInfo = BlobInfo.newBuilder(bucket, blobName).build();
InputStreamContent stream = new InputStreamContent(null, inputStream); if (blobSize > LARGE_BLOB_THRESHOLD_BYTE_SIZE) {
stream.setLength(blobSize); writeBlobResumable(blobInfo, inputStream);
} else {
writeBlobMultipart(blobInfo, inputStream, blobSize);
}
}
Storage.Objects.Insert insert = client.objects().insert(bucket, null, stream); /**
insert.setName(blobName); * Uploads a blob using the "resumable upload" method (multiple requests, which
insert.execute(); * can be independently retried in case of failure, see
}); * https://cloud.google.com/storage/docs/json_api/v1/how-tos/resumable-upload
*
* @param blobInfo the info for the blob to be uploaded
* @param inputStream the stream containing the blob data
*/
private void writeBlobResumable(BlobInfo blobInfo, InputStream inputStream) throws IOException {
final WriteChannel writeChannel = SocketAccess.doPrivilegedIOException(() -> storage.writer(blobInfo));
Streams.copy(inputStream, Channels.newOutputStream(new WritableByteChannel() {
@Override
public boolean isOpen() {
return writeChannel.isOpen();
}
@Override
public void close() throws IOException {
SocketAccess.doPrivilegedVoidIOException(writeChannel::close);
}
@SuppressForbidden(reason = "Channel is based of a socket not a file")
@Override
public int write(ByteBuffer src) throws IOException {
return SocketAccess.doPrivilegedIOException(() -> writeChannel.write(src));
}
}));
}
/**
* Uploads a blob using the "multipart upload" method (a single
* 'multipart/related' request containing both data and metadata. The request is
* gziped), see:
* https://cloud.google.com/storage/docs/json_api/v1/how-tos/multipart-upload
*
* @param blobInfo the info for the blob to be uploaded
* @param inputStream the stream containing the blob data
* @param blobSize the size
*/
private void writeBlobMultipart(BlobInfo blobInfo, InputStream inputStream, long blobSize) throws IOException {
assert blobSize <= LARGE_BLOB_THRESHOLD_BYTE_SIZE : "large blob uploads should use the resumable upload method";
final ByteArrayOutputStream baos = new ByteArrayOutputStream(Math.toIntExact(blobSize));
Streams.copy(inputStream, baos);
SocketAccess.doPrivilegedVoidIOException(() -> storage.create(blobInfo, baos.toByteArray()));
} }
/** /**
@ -220,10 +247,11 @@ class GoogleCloudStorageBlobStore extends AbstractComponent implements BlobStore
* @param blobName name of the blob * @param blobName name of the blob
*/ */
void deleteBlob(String blobName) throws IOException { void deleteBlob(String blobName) throws IOException {
if (!blobExists(blobName)) { final BlobId blobId = BlobId.of(bucket, blobName);
final boolean deleted = SocketAccess.doPrivilegedIOException(() -> storage.delete(blobId));
if (deleted == false) {
throw new NoSuchFileException("Blob [" + blobName + "] does not exist"); throw new NoSuchFileException("Blob [" + blobName + "] does not exist");
} }
SocketAccess.doPrivilegedIOException(() -> client.objects().delete(bucket, blobName).execute());
} }
/** /**
@ -232,7 +260,7 @@ class GoogleCloudStorageBlobStore extends AbstractComponent implements BlobStore
* @param prefix prefix of the buckets to delete * @param prefix prefix of the buckets to delete
*/ */
void deleteBlobsByPrefix(String prefix) throws IOException { void deleteBlobsByPrefix(String prefix) throws IOException {
deleteBlobs(listBlobsByPath(bucket, prefix, null).keySet()); deleteBlobs(listBlobsByPrefix("", prefix).keySet());
} }
/** /**
@ -241,163 +269,55 @@ class GoogleCloudStorageBlobStore extends AbstractComponent implements BlobStore
* @param blobNames names of the bucket to delete * @param blobNames names of the bucket to delete
*/ */
void deleteBlobs(Collection<String> blobNames) throws IOException { void deleteBlobs(Collection<String> blobNames) throws IOException {
if (blobNames == null || blobNames.isEmpty()) { if (blobNames.isEmpty()) {
return; return;
} }
// for a single op submit a simple delete instead of a batch of size 1
if (blobNames.size() == 1) { if (blobNames.size() == 1) {
deleteBlob(blobNames.iterator().next()); deleteBlob(blobNames.iterator().next());
return; return;
} }
final List<Storage.Objects.Delete> deletions = new ArrayList<>(Math.min(MAX_BATCHING_REQUESTS, blobNames.size())); final List<BlobId> blobIdsToDelete = blobNames.stream().map(blobName -> BlobId.of(bucket, blobName)).collect(Collectors.toList());
final Iterator<String> blobs = blobNames.iterator(); final List<Boolean> deletedStatuses = SocketAccess.doPrivilegedIOException(() -> storage.delete(blobIdsToDelete));
assert blobIdsToDelete.size() == deletedStatuses.size();
SocketAccess.doPrivilegedVoidIOException(() -> { boolean failed = false;
while (blobs.hasNext()) { for (int i = 0; i < blobIdsToDelete.size(); i++) {
// Create a delete request for each blob to delete if (deletedStatuses.get(i) == false) {
deletions.add(client.objects().delete(bucket, blobs.next())); logger.error("Failed to delete blob [{}] in bucket [{}]", blobIdsToDelete.get(i).getName(), bucket);
failed = true;
if (blobs.hasNext() == false || deletions.size() == MAX_BATCHING_REQUESTS) {
try {
// Deletions are executed using a batch request
BatchRequest batch = client.batch();
// Used to track successful deletions
CountDown countDown = new CountDown(deletions.size());
for (Storage.Objects.Delete delete : deletions) {
// Queue the delete request in batch
delete.queue(batch, new JsonBatchCallback<Void>() {
@Override
public void onFailure(GoogleJsonError e, HttpHeaders responseHeaders) throws IOException {
logger.error("failed to delete blob [{}] in bucket [{}]: {}", delete.getObject(), delete.getBucket(), e
.getMessage());
}
@Override
public void onSuccess(Void aVoid, HttpHeaders responseHeaders) throws IOException {
countDown.countDown();
}
});
}
batch.execute();
if (countDown.isCountedDown() == false) {
throw new IOException("Failed to delete all [" + deletions.size() + "] blobs");
}
} finally {
deletions.clear();
} }
} }
if (failed) {
throw new IOException("Failed to delete all [" + blobIdsToDelete.size() + "] blobs");
} }
});
} }
/** /**
* Moves a blob within the same bucket * Moves a blob within the same bucket
* *
* @param sourceBlob name of the blob to move * @param sourceBlob name of the blob to move
* @param targetBlob new name of the blob in the target bucket * @param targetBlob new name of the blob in the same bucket
*/ */
void moveBlob(String sourceBlob, String targetBlob) throws IOException { void moveBlob(String sourceBlobName, String targetBlobName) throws IOException {
SocketAccess.doPrivilegedIOException(() -> { final BlobId sourceBlobId = BlobId.of(bucket, sourceBlobName);
final BlobId targetBlobId = BlobId.of(bucket, targetBlobName);
final CopyRequest request = CopyRequest.newBuilder()
.setSource(sourceBlobId)
.setTarget(targetBlobId)
.build();
SocketAccess.doPrivilegedVoidIOException(() -> {
// There's no atomic "move" in GCS so we need to copy and delete // There's no atomic "move" in GCS so we need to copy and delete
client.objects().copy(bucket, sourceBlob, bucket, targetBlob, null).execute(); storage.copy(request).getResult();
client.objects().delete(bucket, sourceBlob).execute(); final boolean deleted = storage.delete(sourceBlobId);
return null; if (deleted == false) {
throw new IOException("Failed to move source [" + sourceBlobName + "] to target [" + targetBlobName + "]");
}
}); });
} }
private String buildKey(String keyPath, String s) { private static String buildKey(String keyPath, String s) {
assert s != null; assert s != null;
return keyPath + s; return keyPath + s;
} }
/**
* Converts a {@link StorageObject} to a {@link PlainBlobMetaData}
*/
class BlobMetaDataConverter implements Function<StorageObject, PlainBlobMetaData> {
private final String pathToRemove;
BlobMetaDataConverter(String pathToRemove) {
this.pathToRemove = pathToRemove;
}
@Override
public PlainBlobMetaData apply(StorageObject storageObject) {
String blobName = storageObject.getName();
if (Strings.hasLength(pathToRemove)) {
blobName = blobName.substring(pathToRemove.length());
}
return new PlainBlobMetaData(blobName, storageObject.getSize().longValue());
}
}
/**
* Spliterator can be used to list storage objects stored in a bucket.
*/
static class StorageObjectsSpliterator implements Spliterator<StorageObject> {
private final Storage.Objects.List list;
StorageObjectsSpliterator(Storage client, String bucketName, String prefix, long pageSize) throws IOException {
list = SocketAccess.doPrivilegedIOException(() -> client.objects().list(bucketName));
list.setMaxResults(pageSize);
if (prefix != null) {
list.setPrefix(prefix);
}
}
@Override
public boolean tryAdvance(Consumer<? super StorageObject> action) {
try {
// Retrieves the next page of items
Objects objects = SocketAccess.doPrivilegedIOException(list::execute);
if ((objects == null) || (objects.getItems() == null) || (objects.getItems().isEmpty())) {
return false;
}
// Consumes all the items
objects.getItems().forEach(action::accept);
// Sets the page token of the next page,
// null indicates that all items have been consumed
String next = objects.getNextPageToken();
if (next != null) {
list.setPageToken(next);
return true;
}
return false;
} catch (Exception e) {
throw new BlobStoreException("Exception while listing objects", e);
}
}
@Override
public Spliterator<StorageObject> trySplit() {
return null;
}
@Override
public long estimateSize() {
return Long.MAX_VALUE;
}
@Override
public int characteristics() {
return 0;
}
}
/**
* Returns a {@link Stream} of {@link StorageObject}s that are stored in a given bucket.
*/
static Stream<StorageObject> blobsStream(Storage client, String bucketName, String prefix, long pageSize) throws IOException {
return StreamSupport.stream(new StorageObjectsSpliterator(client, bucketName, prefix, pageSize), false);
}
} }

View File

@ -18,8 +18,10 @@
*/ */
package org.elasticsearch.repositories.gcs; package org.elasticsearch.repositories.gcs;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.services.storage.StorageScopes; import com.google.api.services.storage.StorageScopes;
import com.google.auth.oauth2.ServiceAccountCredentials;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.settings.SecureSetting; import org.elasticsearch.common.settings.SecureSetting;
import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
@ -28,10 +30,12 @@ import org.elasticsearch.common.unit.TimeValue;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.UncheckedIOException; import java.io.UncheckedIOException;
import java.net.URI;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.function.Function;
import static org.elasticsearch.common.settings.Setting.timeSetting; import static org.elasticsearch.common.settings.Setting.timeSetting;
@ -48,7 +52,15 @@ public class GoogleCloudStorageClientSettings {
/** An override for the Storage endpoint to connect to. */ /** An override for the Storage endpoint to connect to. */
static final Setting.AffixSetting<String> ENDPOINT_SETTING = Setting.affixKeySetting(PREFIX, "endpoint", static final Setting.AffixSetting<String> ENDPOINT_SETTING = Setting.affixKeySetting(PREFIX, "endpoint",
key -> new Setting<>(key, "", s -> s, Setting.Property.NodeScope)); key -> Setting.simpleString(key, Setting.Property.NodeScope));
/** An override for the Google Project ID. */
static final Setting.AffixSetting<String> PROJECT_ID_SETTING = Setting.affixKeySetting(PREFIX, "project_id",
key -> Setting.simpleString(key, Setting.Property.NodeScope));
/** An override for the Token Server URI in the oauth flow. */
static final Setting.AffixSetting<URI> TOKEN_URI_SETTING = Setting.affixKeySetting(PREFIX, "token_uri",
key -> new Setting<>(key, "", URI::create, Setting.Property.NodeScope));
/** /**
* The timeout to establish a connection. A value of {@code -1} corresponds to an infinite timeout. A value of {@code 0} * The timeout to establish a connection. A value of {@code -1} corresponds to an infinite timeout. A value of {@code 0}
@ -64,45 +76,59 @@ public class GoogleCloudStorageClientSettings {
static final Setting.AffixSetting<TimeValue> READ_TIMEOUT_SETTING = Setting.affixKeySetting(PREFIX, "read_timeout", static final Setting.AffixSetting<TimeValue> READ_TIMEOUT_SETTING = Setting.affixKeySetting(PREFIX, "read_timeout",
key -> timeSetting(key, TimeValue.ZERO, TimeValue.MINUS_ONE, Setting.Property.NodeScope)); key -> timeSetting(key, TimeValue.ZERO, TimeValue.MINUS_ONE, Setting.Property.NodeScope));
/** Name used by the client when it uses the Google Cloud JSON API. **/ /** Name used by the client when it uses the Google Cloud JSON API. */
static final Setting.AffixSetting<String> APPLICATION_NAME_SETTING = Setting.affixKeySetting(PREFIX, "application_name", static final Setting.AffixSetting<String> APPLICATION_NAME_SETTING = Setting.affixKeySetting(PREFIX, "application_name",
key -> new Setting<>(key, "repository-gcs", s -> s, Setting.Property.NodeScope)); key -> new Setting<>(key, "repository-gcs", Function.identity(), Setting.Property.NodeScope, Setting.Property.Deprecated));
/** The credentials used by the client to connect to the Storage endpoint **/ /** The credentials used by the client to connect to the Storage endpoint. */
private final GoogleCredential credential; private final ServiceAccountCredentials credential;
/** The Storage root URL the client should talk to, or empty string to use the default. **/ /** The Storage endpoint URL the client should talk to. Null value sets the default. */
private final String endpoint; private final String endpoint;
/** The timeout to establish a connection **/ /** The Google project ID overriding the default way to infer it. Null value sets the default. */
private final String projectId;
/** The timeout to establish a connection */
private final TimeValue connectTimeout; private final TimeValue connectTimeout;
/** The timeout to read data from an established connection **/ /** The timeout to read data from an established connection */
private final TimeValue readTimeout; private final TimeValue readTimeout;
/** The Storage client application name **/ /** The Storage client application name */
private final String applicationName; private final String applicationName;
GoogleCloudStorageClientSettings(final GoogleCredential credential, /** The token server URI. This leases access tokens in the oauth flow. */
private final URI tokenUri;
GoogleCloudStorageClientSettings(final ServiceAccountCredentials credential,
final String endpoint, final String endpoint,
final String projectId,
final TimeValue connectTimeout, final TimeValue connectTimeout,
final TimeValue readTimeout, final TimeValue readTimeout,
final String applicationName) { final String applicationName,
final URI tokenUri) {
this.credential = credential; this.credential = credential;
this.endpoint = endpoint; this.endpoint = endpoint;
this.projectId = projectId;
this.connectTimeout = connectTimeout; this.connectTimeout = connectTimeout;
this.readTimeout = readTimeout; this.readTimeout = readTimeout;
this.applicationName = applicationName; this.applicationName = applicationName;
this.tokenUri = tokenUri;
} }
public GoogleCredential getCredential() { public ServiceAccountCredentials getCredential() {
return credential; return credential;
} }
public String getEndpoint() { public String getHost() {
return endpoint; return endpoint;
} }
public String getProjectId() {
return Strings.hasLength(projectId) ? projectId : (credential != null ? credential.getProjectId() : null);
}
public TimeValue getConnectTimeout() { public TimeValue getConnectTimeout() {
return connectTimeout; return connectTimeout;
} }
@ -115,9 +141,13 @@ public class GoogleCloudStorageClientSettings {
return applicationName; return applicationName;
} }
public URI getTokenUri() {
return tokenUri;
}
public static Map<String, GoogleCloudStorageClientSettings> load(final Settings settings) { public static Map<String, GoogleCloudStorageClientSettings> load(final Settings settings) {
final Map<String, GoogleCloudStorageClientSettings> clients = new HashMap<>(); final Map<String, GoogleCloudStorageClientSettings> clients = new HashMap<>();
for (String clientName: settings.getGroups(PREFIX).keySet()) { for (final String clientName: settings.getGroups(PREFIX).keySet()) {
clients.put(clientName, getClientSettings(settings, clientName)); clients.put(clientName, getClientSettings(settings, clientName));
} }
if (clients.containsKey("default") == false) { if (clients.containsKey("default") == false) {
@ -132,22 +162,27 @@ public class GoogleCloudStorageClientSettings {
return new GoogleCloudStorageClientSettings( return new GoogleCloudStorageClientSettings(
loadCredential(settings, clientName), loadCredential(settings, clientName),
getConfigValue(settings, clientName, ENDPOINT_SETTING), getConfigValue(settings, clientName, ENDPOINT_SETTING),
getConfigValue(settings, clientName, PROJECT_ID_SETTING),
getConfigValue(settings, clientName, CONNECT_TIMEOUT_SETTING), getConfigValue(settings, clientName, CONNECT_TIMEOUT_SETTING),
getConfigValue(settings, clientName, READ_TIMEOUT_SETTING), getConfigValue(settings, clientName, READ_TIMEOUT_SETTING),
getConfigValue(settings, clientName, APPLICATION_NAME_SETTING) getConfigValue(settings, clientName, APPLICATION_NAME_SETTING),
getConfigValue(settings, clientName, TOKEN_URI_SETTING)
); );
} }
/** /**
* Loads the service account file corresponding to a given client name. If no file is defined for the client, * Loads the service account file corresponding to a given client name. If no
* a {@code null} credential is returned. * file is defined for the client, a {@code null} credential is returned.
* *
* @param settings the {@link Settings} * @param settings
* @param clientName the client name * the {@link Settings}
* @param clientName
* the client name
* *
* @return the {@link GoogleCredential} to use for the given client, {@code null} if no service account is defined. * @return the {@link ServiceAccountCredentials} to use for the given client,
* {@code null} if no service account is defined.
*/ */
static GoogleCredential loadCredential(final Settings settings, final String clientName) { static ServiceAccountCredentials loadCredential(final Settings settings, final String clientName) {
try { try {
if (CREDENTIALS_FILE_SETTING.getConcreteSettingForNamespace(clientName).exists(settings) == false) { if (CREDENTIALS_FILE_SETTING.getConcreteSettingForNamespace(clientName).exists(settings) == false) {
// explicitly returning null here so that the default credential // explicitly returning null here so that the default credential
@ -155,19 +190,22 @@ public class GoogleCloudStorageClientSettings {
return null; return null;
} }
try (InputStream credStream = CREDENTIALS_FILE_SETTING.getConcreteSettingForNamespace(clientName).get(settings)) { try (InputStream credStream = CREDENTIALS_FILE_SETTING.getConcreteSettingForNamespace(clientName).get(settings)) {
GoogleCredential credential = GoogleCredential.fromStream(credStream); final Collection<String> scopes = Collections.singleton(StorageScopes.DEVSTORAGE_FULL_CONTROL);
if (credential.createScopedRequired()) { return SocketAccess.doPrivilegedIOException(() -> {
credential = credential.createScoped(Collections.singleton(StorageScopes.DEVSTORAGE_FULL_CONTROL)); final ServiceAccountCredentials credentials = ServiceAccountCredentials.fromStream(credStream);
if (credentials.createScopedRequired()) {
return (ServiceAccountCredentials) credentials.createScoped(scopes);
} }
return credential; return credentials;
});
} }
} catch (IOException e) { } catch (final IOException e) {
throw new UncheckedIOException(e); throw new UncheckedIOException(e);
} }
} }
private static <T> T getConfigValue(final Settings settings, final String clientName, final Setting.AffixSetting<T> clientSetting) { private static <T> T getConfigValue(final Settings settings, final String clientName, final Setting.AffixSetting<T> clientSetting) {
Setting<T> concreteSetting = clientSetting.getConcreteSettingForNamespace(clientName); final Setting<T> concreteSetting = clientSetting.getConcreteSettingForNamespace(clientName);
return concreteSetting.get(settings); return concreteSetting.get(settings);
} }
} }

View File

@ -19,21 +19,6 @@
package org.elasticsearch.repositories.gcs; package org.elasticsearch.repositories.gcs;
import com.google.api.client.auth.oauth2.TokenRequest;
import com.google.api.client.auth.oauth2.TokenResponse;
import com.google.api.client.googleapis.json.GoogleJsonError;
import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpHeaders;
import com.google.api.client.json.GenericJson;
import com.google.api.client.json.webtoken.JsonWebSignature;
import com.google.api.client.json.webtoken.JsonWebToken;
import com.google.api.client.util.ClassInfo;
import com.google.api.client.util.Data;
import com.google.api.services.storage.Storage;
import com.google.api.services.storage.model.Bucket;
import com.google.api.services.storage.model.Objects;
import com.google.api.services.storage.model.StorageObject;
import org.elasticsearch.SpecialPermission;
import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.common.xcontent.NamedXContentRegistry;
@ -42,8 +27,6 @@ import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.plugins.RepositoryPlugin; import org.elasticsearch.plugins.RepositoryPlugin;
import org.elasticsearch.repositories.Repository; import org.elasticsearch.repositories.Repository;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -51,63 +34,6 @@ import java.util.Map;
public class GoogleCloudStoragePlugin extends Plugin implements RepositoryPlugin { public class GoogleCloudStoragePlugin extends Plugin implements RepositoryPlugin {
static {
/*
* Google HTTP client changes access levels because its silly and we
* can't allow that on any old stack stack so we pull it here, up front,
* so we can cleanly check the permissions for it. Without this changing
* the permission can fail if any part of core is on the stack because
* our plugin permissions don't allow core to "reach through" plugins to
* change the permission. Because that'd be silly.
*/
SpecialPermission.check();
AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
// ClassInfo put in cache all the fields of a given class
// that are annoted with @Key; at the same time it changes
// the field access level using setAccessible(). Calling
// them here put the ClassInfo in cache (they are never evicted)
// before the SecurityManager is installed.
ClassInfo.of(HttpHeaders.class, true);
ClassInfo.of(JsonWebSignature.Header.class, false);
ClassInfo.of(JsonWebToken.Payload.class, false);
ClassInfo.of(TokenRequest.class, false);
ClassInfo.of(TokenResponse.class, false);
ClassInfo.of(GenericJson.class, false);
ClassInfo.of(GenericUrl.class, false);
Data.nullOf(GoogleJsonError.ErrorInfo.class);
ClassInfo.of(GoogleJsonError.class, false);
Data.nullOf(Bucket.Cors.class);
ClassInfo.of(Bucket.class, false);
ClassInfo.of(Bucket.Cors.class, false);
ClassInfo.of(Bucket.Lifecycle.class, false);
ClassInfo.of(Bucket.Logging.class, false);
ClassInfo.of(Bucket.Owner.class, false);
ClassInfo.of(Bucket.Versioning.class, false);
ClassInfo.of(Bucket.Website.class, false);
ClassInfo.of(StorageObject.class, false);
ClassInfo.of(StorageObject.Owner.class, false);
ClassInfo.of(Objects.class, false);
ClassInfo.of(Storage.Buckets.Get.class, false);
ClassInfo.of(Storage.Buckets.Insert.class, false);
ClassInfo.of(Storage.Objects.Get.class, false);
ClassInfo.of(Storage.Objects.Insert.class, false);
ClassInfo.of(Storage.Objects.Delete.class, false);
ClassInfo.of(Storage.Objects.Copy.class, false);
ClassInfo.of(Storage.Objects.List.class, false);
return null;
});
}
private final Map<String, GoogleCloudStorageClientSettings> clientsSettings; private final Map<String, GoogleCloudStorageClientSettings> clientsSettings;
public GoogleCloudStoragePlugin(final Settings settings) { public GoogleCloudStoragePlugin(final Settings settings) {
@ -134,8 +60,10 @@ public class GoogleCloudStoragePlugin extends Plugin implements RepositoryPlugin
return Arrays.asList( return Arrays.asList(
GoogleCloudStorageClientSettings.CREDENTIALS_FILE_SETTING, GoogleCloudStorageClientSettings.CREDENTIALS_FILE_SETTING,
GoogleCloudStorageClientSettings.ENDPOINT_SETTING, GoogleCloudStorageClientSettings.ENDPOINT_SETTING,
GoogleCloudStorageClientSettings.PROJECT_ID_SETTING,
GoogleCloudStorageClientSettings.CONNECT_TIMEOUT_SETTING, GoogleCloudStorageClientSettings.CONNECT_TIMEOUT_SETTING,
GoogleCloudStorageClientSettings.READ_TIMEOUT_SETTING, GoogleCloudStorageClientSettings.READ_TIMEOUT_SETTING,
GoogleCloudStorageClientSettings.APPLICATION_NAME_SETTING); GoogleCloudStorageClientSettings.APPLICATION_NAME_SETTING,
GoogleCloudStorageClientSettings.TOKEN_URI_SETTING);
} }
} }

View File

@ -19,7 +19,6 @@
package org.elasticsearch.repositories.gcs; package org.elasticsearch.repositories.gcs;
import com.google.api.services.storage.Storage;
import org.elasticsearch.cluster.metadata.RepositoryMetaData; import org.elasticsearch.cluster.metadata.RepositoryMetaData;
import org.elasticsearch.common.Strings; import org.elasticsearch.common.Strings;
import org.elasticsearch.common.blobstore.BlobPath; import org.elasticsearch.common.blobstore.BlobPath;
@ -27,7 +26,6 @@ import org.elasticsearch.common.blobstore.BlobStore;
import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.unit.ByteSizeUnit; import org.elasticsearch.common.unit.ByteSizeUnit;
import org.elasticsearch.common.unit.ByteSizeValue; import org.elasticsearch.common.unit.ByteSizeValue;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.env.Environment; import org.elasticsearch.env.Environment;
import org.elasticsearch.repositories.RepositoryException; import org.elasticsearch.repositories.RepositoryException;
@ -39,7 +37,8 @@ import static org.elasticsearch.common.settings.Setting.Property;
import static org.elasticsearch.common.settings.Setting.boolSetting; import static org.elasticsearch.common.settings.Setting.boolSetting;
import static org.elasticsearch.common.settings.Setting.byteSizeSetting; import static org.elasticsearch.common.settings.Setting.byteSizeSetting;
import static org.elasticsearch.common.settings.Setting.simpleString; import static org.elasticsearch.common.settings.Setting.simpleString;
import static org.elasticsearch.common.unit.TimeValue.timeValueMillis;
import com.google.cloud.storage.Storage;
class GoogleCloudStorageRepository extends BlobStoreRepository { class GoogleCloudStorageRepository extends BlobStoreRepository {

View File

@ -19,23 +19,26 @@
package org.elasticsearch.repositories.gcs; package org.elasticsearch.repositories.gcs;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential; import com.google.api.client.googleapis.GoogleUtils;
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.http.HttpBackOffIOExceptionHandler;
import com.google.api.client.http.HttpBackOffUnsuccessfulResponseHandler;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpRequestInitializer;
import com.google.api.client.http.HttpTransport; import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.HttpUnsuccessfulResponseHandler; import com.google.api.client.http.javanet.DefaultConnectionFactory;
import com.google.api.client.json.jackson2.JacksonFactory; import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.util.ExponentialBackOff; import com.google.auth.oauth2.ServiceAccountCredentials;
import com.google.api.services.storage.Storage; import com.google.cloud.http.HttpTransportOptions;
import com.google.cloud.storage.Storage;
import com.google.cloud.storage.StorageOptions;
import org.elasticsearch.common.Strings; import org.elasticsearch.common.Strings;
import org.elasticsearch.common.collect.MapBuilder;
import org.elasticsearch.common.component.AbstractComponent; import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.env.Environment; import org.elasticsearch.env.Environment;
import java.io.IOException; import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Map; import java.util.Map;
public class GoogleCloudStorageService extends AbstractComponent { public class GoogleCloudStorageService extends AbstractComponent {
@ -51,42 +54,107 @@ public class GoogleCloudStorageService extends AbstractComponent {
/** /**
* Creates a client that can be used to manage Google Cloud Storage objects. * Creates a client that can be used to manage Google Cloud Storage objects.
* *
* @param clientName name of client settings to use from secure settings * @param clientName name of client settings to use, including secure settings
* @return a Client instance that can be used to manage Storage objects * @return a Client instance that can be used to manage Storage objects
*/ */
public Storage createClient(final String clientName) throws Exception { public Storage createClient(final String clientName) throws Exception {
final GoogleCloudStorageClientSettings clientSettings = clientsSettings.get(clientName); final GoogleCloudStorageClientSettings clientSettings = clientsSettings.get(clientName);
if (clientSettings == null) { if (clientSettings == null) {
throw new IllegalArgumentException("Unknown client name [" + clientName + "]. Existing client configs: " + throw new IllegalArgumentException("Unknown client name [" + clientName + "]. Existing client configs: "
Strings.collectionToDelimitedString(clientsSettings.keySet(), ",")); + Strings.collectionToDelimitedString(clientsSettings.keySet(), ","));
} }
final HttpTransport httpTransport = createHttpTransport(clientSettings.getHost());
HttpTransport transport = GoogleNetHttpTransport.newTrustedTransport(); final HttpTransportOptions httpTransportOptions = HttpTransportOptions.newBuilder()
HttpRequestInitializer requestInitializer = createRequestInitializer(clientSettings); .setConnectTimeout(toTimeout(clientSettings.getConnectTimeout()))
.setReadTimeout(toTimeout(clientSettings.getReadTimeout()))
Storage.Builder storage = new Storage.Builder(transport, JacksonFactory.getDefaultInstance(), requestInitializer); .setHttpTransportFactory(() -> httpTransport)
.build();
final StorageOptions.Builder storageOptionsBuilder = StorageOptions.newBuilder()
.setTransportOptions(httpTransportOptions)
.setHeaderProvider(() -> {
final MapBuilder<String, String> mapBuilder = MapBuilder.newMapBuilder();
if (Strings.hasLength(clientSettings.getApplicationName())) { if (Strings.hasLength(clientSettings.getApplicationName())) {
storage.setApplicationName(clientSettings.getApplicationName()); mapBuilder.put("user-agent", clientSettings.getApplicationName());
} }
if (Strings.hasLength(clientSettings.getEndpoint())) { return mapBuilder.immutableMap();
storage.setRootUrl(clientSettings.getEndpoint()); });
if (Strings.hasLength(clientSettings.getHost())) {
storageOptionsBuilder.setHost(clientSettings.getHost());
} }
return storage.build(); if (Strings.hasLength(clientSettings.getProjectId())) {
storageOptionsBuilder.setProjectId(clientSettings.getProjectId());
}
if (clientSettings.getCredential() == null) {
logger.warn("\"Application Default Credentials\" are not supported out of the box."
+ " Additional file system permissions have to be granted to the plugin.");
} else {
ServiceAccountCredentials serviceAccountCredentials = clientSettings.getCredential();
// override token server URI
final URI tokenServerUri = clientSettings.getTokenUri();
if (Strings.hasLength(tokenServerUri.toString())) {
// Rebuild the service account credentials in order to use a custom Token url.
// This is mostly used for testing purpose.
serviceAccountCredentials = serviceAccountCredentials.toBuilder().setTokenServerUri(tokenServerUri).build();
}
storageOptionsBuilder.setCredentials(serviceAccountCredentials);
}
return storageOptionsBuilder.build().getService();
} }
static HttpRequestInitializer createRequestInitializer(final GoogleCloudStorageClientSettings settings) throws IOException { /**
GoogleCredential credential = settings.getCredential(); * Pins the TLS trust certificates and, more importantly, overrides connection
if (credential == null) { * URLs in the case of a custom endpoint setting because some connections don't
credential = GoogleCredential.getApplicationDefault(); * fully honor this setting (bugs in the SDK). The default connection factory
* opens a new connection for each request. This is required for the storage
* instance to be thread-safe.
**/
private static HttpTransport createHttpTransport(final String endpoint) throws Exception {
final NetHttpTransport.Builder builder = new NetHttpTransport.Builder();
// requires java.lang.RuntimePermission "setFactory"
builder.trustCertificates(GoogleUtils.getCertificateTrustStore());
if (Strings.hasLength(endpoint)) {
final URL endpointUrl = URI.create(endpoint).toURL();
builder.setConnectionFactory(new DefaultConnectionFactory() {
@Override
public HttpURLConnection openConnection(final URL originalUrl) throws IOException {
// test if the URL is built correctly, ie following the `host` setting
if (originalUrl.getHost().equals(endpointUrl.getHost()) && originalUrl.getPort() == endpointUrl.getPort()
&& originalUrl.getProtocol().equals(endpointUrl.getProtocol())) {
return super.openConnection(originalUrl);
} }
return new DefaultHttpRequestInitializer(credential, toTimeout(settings.getConnectTimeout()), toTimeout(settings.getReadTimeout())); // override connection URLs because some don't follow the config. See
// https://github.com/GoogleCloudPlatform/google-cloud-java/issues/3254 and
// https://github.com/GoogleCloudPlatform/google-cloud-java/issues/3255
URI originalUri;
try {
originalUri = originalUrl.toURI();
} catch (final URISyntaxException e) {
throw new RuntimeException(e);
}
String overridePath = "/";
if (originalUri.getRawPath() != null) {
overridePath = originalUri.getRawPath();
}
if (originalUri.getRawQuery() != null) {
overridePath += "?" + originalUri.getRawQuery();
}
return super.openConnection(
new URL(endpointUrl.getProtocol(), endpointUrl.getHost(), endpointUrl.getPort(), overridePath));
}
});
}
return builder.build();
} }
/** Converts timeout values from the settings to a timeout value for the Google Cloud SDK **/ /**
* Converts timeout values from the settings to a timeout value for the Google
* Cloud SDK
**/
static Integer toTimeout(final TimeValue timeout) { static Integer toTimeout(final TimeValue timeout) {
// Null or zero in settings means the default timeout // Null or zero in settings means the default timeout
if (timeout == null || TimeValue.ZERO.equals(timeout)) { if (timeout == null || TimeValue.ZERO.equals(timeout)) {
return null; // negative value means using the default value
return -1;
} }
// -1 means infinite timeout // -1 means infinite timeout
if (TimeValue.MINUS_ONE.equals(timeout)) { if (TimeValue.MINUS_ONE.equals(timeout)) {
@ -96,51 +164,4 @@ public class GoogleCloudStorageService extends AbstractComponent {
return Math.toIntExact(timeout.getMillis()); return Math.toIntExact(timeout.getMillis());
} }
/**
* HTTP request initializer that set timeouts and backoff handler while deferring authentication to GoogleCredential.
* See https://cloud.google.com/storage/transfer/create-client#retry
*/
static class DefaultHttpRequestInitializer implements HttpRequestInitializer {
private final Integer connectTimeout;
private final Integer readTimeout;
private final GoogleCredential credential;
DefaultHttpRequestInitializer(GoogleCredential credential, Integer connectTimeoutMillis, Integer readTimeoutMillis) {
this.credential = credential;
this.connectTimeout = connectTimeoutMillis;
this.readTimeout = readTimeoutMillis;
}
@Override
public void initialize(HttpRequest request) {
if (connectTimeout != null) {
request.setConnectTimeout(connectTimeout);
}
if (readTimeout != null) {
request.setReadTimeout(readTimeout);
}
request.setIOExceptionHandler(new HttpBackOffIOExceptionHandler(newBackOff()));
request.setInterceptor(credential);
final HttpUnsuccessfulResponseHandler handler = new HttpBackOffUnsuccessfulResponseHandler(newBackOff());
request.setUnsuccessfulResponseHandler((req, resp, supportsRetry) -> {
// Let the credential handle the response. If it failed, we rely on our backoff handler
return credential.handleResponse(req, resp, supportsRetry) || handler.handleResponse(req, resp, supportsRetry);
}
);
}
private ExponentialBackOff newBackOff() {
return new ExponentialBackOff.Builder()
.setInitialIntervalMillis(100)
.setMaxIntervalMillis(6000)
.setMaxElapsedTimeMillis(900000)
.setMultiplier(1.5)
.setRandomizationFactor(0.5)
.build();
}
}
} }

View File

@ -18,11 +18,12 @@
*/ */
grant { grant {
// required by: com.google.api.client.json.JsonParser#parseValue
permission java.lang.RuntimePermission "accessDeclaredMembers"; permission java.lang.RuntimePermission "accessDeclaredMembers";
permission java.lang.RuntimePermission "setFactory"; // required by: com.google.api.client.json.GenericJson#<init>
permission java.lang.reflect.ReflectPermission "suppressAccessChecks"; permission java.lang.reflect.ReflectPermission "suppressAccessChecks";
permission java.net.URLPermission "http://www.googleapis.com/*", "*"; // required to add google certs to the gcs client trustore
permission java.net.URLPermission "https://www.googleapis.com/*", "*"; permission java.lang.RuntimePermission "setFactory";
// gcs client opens socket connections for to access repository // gcs client opens socket connections for to access repository
permission java.net.SocketPermission "*", "connect"; permission java.net.SocketPermission "*", "connect";

View File

@ -0,0 +1,54 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.google.cloud.storage;
import com.google.cloud.storage.spi.v1.StorageRpc;
import static org.mockito.Mockito.mock;
/**
* Utility class that exposed Google SDK package protected methods to
* create specific StorageRpc objects in unit tests.
*/
public class StorageRpcOptionUtils {
private StorageRpcOptionUtils(){}
public static String getPrefix(final Storage.BlobListOption... options) {
if (options != null) {
for (final Option option : options) {
final StorageRpc.Option rpcOption = option.getRpcOption();
if (StorageRpc.Option.PREFIX.equals(rpcOption)) {
return (String) option.getValue();
}
}
}
return null;
}
public static CopyWriter createCopyWriter(final Blob result) {
return new CopyWriter(mock(StorageOptions.class), mock(StorageRpc.RewriteResponse.class)) {
@Override
public Blob getResult() {
return result;
}
};
}
}

View File

@ -0,0 +1,37 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.google.cloud.storage;
/**
* Utility class that exposed Google SDK package protected methods to
* create buckets and blobs objects in unit tests.
*/
public class StorageTestUtils {
private StorageTestUtils(){}
public static Bucket createBucket(final Storage storage, final String bucketName) {
return new Bucket(storage, (BucketInfo.BuilderImpl) BucketInfo.newBuilder(bucketName));
}
public static Blob createBlob(final Storage storage, final String bucketName, final String blobName, final long blobSize) {
return new Blob(storage, (BlobInfo.BuilderImpl) BlobInfo.newBuilder(bucketName, blobName).setSize(blobSize));
}
}

View File

@ -19,7 +19,7 @@
package org.elasticsearch.repositories.gcs; package org.elasticsearch.repositories.gcs;
import com.google.api.services.storage.Storage; import com.google.cloud.storage.Storage;
import org.elasticsearch.cluster.metadata.RepositoryMetaData; import org.elasticsearch.cluster.metadata.RepositoryMetaData;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.ByteSizeUnit; import org.elasticsearch.common.unit.ByteSizeUnit;

View File

@ -18,20 +18,25 @@
*/ */
package org.elasticsearch.repositories.gcs; package org.elasticsearch.repositories.gcs;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.services.storage.StorageScopes; import com.google.api.services.storage.StorageScopes;
import com.google.auth.oauth2.ServiceAccountCredentials;
import org.elasticsearch.common.collect.Tuple; import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.settings.MockSecureSettings; import org.elasticsearch.common.settings.MockSecureSettings;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.ESTestCase;
import java.net.URI;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.KeyPair; import java.security.KeyPair;
import java.security.KeyPairGenerator; import java.security.KeyPairGenerator;
import java.util.ArrayList;
import java.util.Base64; import java.util.Base64;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
@ -39,6 +44,7 @@ import static org.elasticsearch.repositories.gcs.GoogleCloudStorageClientSetting
import static org.elasticsearch.repositories.gcs.GoogleCloudStorageClientSettings.CONNECT_TIMEOUT_SETTING; import static org.elasticsearch.repositories.gcs.GoogleCloudStorageClientSettings.CONNECT_TIMEOUT_SETTING;
import static org.elasticsearch.repositories.gcs.GoogleCloudStorageClientSettings.CREDENTIALS_FILE_SETTING; import static org.elasticsearch.repositories.gcs.GoogleCloudStorageClientSettings.CREDENTIALS_FILE_SETTING;
import static org.elasticsearch.repositories.gcs.GoogleCloudStorageClientSettings.ENDPOINT_SETTING; import static org.elasticsearch.repositories.gcs.GoogleCloudStorageClientSettings.ENDPOINT_SETTING;
import static org.elasticsearch.repositories.gcs.GoogleCloudStorageClientSettings.PROJECT_ID_SETTING;
import static org.elasticsearch.repositories.gcs.GoogleCloudStorageClientSettings.READ_TIMEOUT_SETTING; import static org.elasticsearch.repositories.gcs.GoogleCloudStorageClientSettings.READ_TIMEOUT_SETTING;
import static org.elasticsearch.repositories.gcs.GoogleCloudStorageClientSettings.getClientSettings; import static org.elasticsearch.repositories.gcs.GoogleCloudStorageClientSettings.getClientSettings;
import static org.elasticsearch.repositories.gcs.GoogleCloudStorageClientSettings.loadCredential; import static org.elasticsearch.repositories.gcs.GoogleCloudStorageClientSettings.loadCredential;
@ -46,59 +52,78 @@ import static org.elasticsearch.repositories.gcs.GoogleCloudStorageClientSetting
public class GoogleCloudStorageClientSettingsTests extends ESTestCase { public class GoogleCloudStorageClientSettingsTests extends ESTestCase {
public void testLoadWithEmptySettings() { public void testLoadWithEmptySettings() {
Map<String, GoogleCloudStorageClientSettings> clientsSettings = GoogleCloudStorageClientSettings.load(Settings.EMPTY); final Map<String, GoogleCloudStorageClientSettings> clientsSettings = GoogleCloudStorageClientSettings.load(Settings.EMPTY);
assertEquals(1, clientsSettings.size()); assertEquals(1, clientsSettings.size());
assertNotNull(clientsSettings.get("default")); assertNotNull(clientsSettings.get("default"));
} }
public void testLoad() throws Exception { public void testLoad() throws Exception {
final int nbClients = randomIntBetween(1, 5); final int nbClients = randomIntBetween(1, 5);
final Tuple<Map<String, GoogleCloudStorageClientSettings>, Settings> randomClients = randomClients(nbClients); final List<Setting<?>> deprecationWarnings = new ArrayList<>();
final Tuple<Map<String, GoogleCloudStorageClientSettings>, Settings> randomClients = randomClients(nbClients, deprecationWarnings);
final Map<String, GoogleCloudStorageClientSettings> expectedClientsSettings = randomClients.v1(); final Map<String, GoogleCloudStorageClientSettings> expectedClientsSettings = randomClients.v1();
Map<String, GoogleCloudStorageClientSettings> actualClientsSettings = GoogleCloudStorageClientSettings.load(randomClients.v2()); final Map<String, GoogleCloudStorageClientSettings> actualClientsSettings = GoogleCloudStorageClientSettings
.load(randomClients.v2());
assertEquals(expectedClientsSettings.size(), actualClientsSettings.size()); assertEquals(expectedClientsSettings.size(), actualClientsSettings.size());
for (String clientName : expectedClientsSettings.keySet()) { for (final String clientName : expectedClientsSettings.keySet()) {
GoogleCloudStorageClientSettings actualClientSettings = actualClientsSettings.get(clientName); final GoogleCloudStorageClientSettings actualClientSettings = actualClientsSettings.get(clientName);
assertNotNull(actualClientSettings); assertNotNull(actualClientSettings);
GoogleCloudStorageClientSettings expectedClientSettings = expectedClientsSettings.get(clientName); final GoogleCloudStorageClientSettings expectedClientSettings = expectedClientsSettings.get(clientName);
assertNotNull(expectedClientSettings); assertNotNull(expectedClientSettings);
assertGoogleCredential(expectedClientSettings.getCredential(), actualClientSettings.getCredential()); assertGoogleCredential(expectedClientSettings.getCredential(), actualClientSettings.getCredential());
assertEquals(expectedClientSettings.getEndpoint(), actualClientSettings.getEndpoint()); assertEquals(expectedClientSettings.getHost(), actualClientSettings.getHost());
assertEquals(expectedClientSettings.getProjectId(), actualClientSettings.getProjectId());
assertEquals(expectedClientSettings.getConnectTimeout(), actualClientSettings.getConnectTimeout()); assertEquals(expectedClientSettings.getConnectTimeout(), actualClientSettings.getConnectTimeout());
assertEquals(expectedClientSettings.getReadTimeout(), actualClientSettings.getReadTimeout()); assertEquals(expectedClientSettings.getReadTimeout(), actualClientSettings.getReadTimeout());
assertEquals(expectedClientSettings.getApplicationName(), actualClientSettings.getApplicationName()); assertEquals(expectedClientSettings.getApplicationName(), actualClientSettings.getApplicationName());
} }
if (deprecationWarnings.isEmpty() == false) {
assertSettingDeprecationsAndWarnings(deprecationWarnings.toArray(new Setting<?>[0]));
}
} }
public void testLoadCredential() throws Exception { public void testLoadCredential() throws Exception {
Tuple<Map<String, GoogleCloudStorageClientSettings>, Settings> randomClient = randomClients(1); final List<Setting<?>> deprecationWarnings = new ArrayList<>();
GoogleCloudStorageClientSettings expectedClientSettings = randomClient.v1().values().iterator().next(); final Tuple<Map<String, GoogleCloudStorageClientSettings>, Settings> randomClient = randomClients(1, deprecationWarnings);
String clientName = randomClient.v1().keySet().iterator().next(); final GoogleCloudStorageClientSettings expectedClientSettings = randomClient.v1().values().iterator().next();
final String clientName = randomClient.v1().keySet().iterator().next();
assertGoogleCredential(expectedClientSettings.getCredential(), loadCredential(randomClient.v2(), clientName)); assertGoogleCredential(expectedClientSettings.getCredential(), loadCredential(randomClient.v2(), clientName));
} }
public void testProjectIdDefaultsToCredentials() throws Exception {
final String clientName = randomAlphaOfLength(5);
final Tuple<ServiceAccountCredentials, byte[]> credentials = randomCredential(clientName);
final ServiceAccountCredentials credential = credentials.v1();
final GoogleCloudStorageClientSettings googleCloudStorageClientSettings = new GoogleCloudStorageClientSettings(credential,
ENDPOINT_SETTING.getDefault(Settings.EMPTY), PROJECT_ID_SETTING.getDefault(Settings.EMPTY),
CONNECT_TIMEOUT_SETTING.getDefault(Settings.EMPTY), READ_TIMEOUT_SETTING.getDefault(Settings.EMPTY),
APPLICATION_NAME_SETTING.getDefault(Settings.EMPTY), new URI(""));
assertEquals(credential.getProjectId(), googleCloudStorageClientSettings.getProjectId());
}
/** Generates a given number of GoogleCloudStorageClientSettings along with the Settings to build them from **/ /** Generates a given number of GoogleCloudStorageClientSettings along with the Settings to build them from **/
private Tuple<Map<String, GoogleCloudStorageClientSettings>, Settings> randomClients(final int nbClients) throws Exception { private Tuple<Map<String, GoogleCloudStorageClientSettings>, Settings> randomClients(final int nbClients,
final List<Setting<?>> deprecationWarnings)
throws Exception {
final Map<String, GoogleCloudStorageClientSettings> expectedClients = new HashMap<>(); final Map<String, GoogleCloudStorageClientSettings> expectedClients = new HashMap<>();
expectedClients.put("default", getClientSettings(Settings.EMPTY, "default"));
final Settings.Builder settings = Settings.builder(); final Settings.Builder settings = Settings.builder();
final MockSecureSettings secureSettings = new MockSecureSettings(); final MockSecureSettings secureSettings = new MockSecureSettings();
for (int i = 0; i < nbClients; i++) { for (int i = 0; i < nbClients; i++) {
String clientName = randomAlphaOfLength(5).toLowerCase(Locale.ROOT); final String clientName = randomAlphaOfLength(5).toLowerCase(Locale.ROOT);
final GoogleCloudStorageClientSettings clientSettings = randomClient(clientName, settings, secureSettings, deprecationWarnings);
GoogleCloudStorageClientSettings clientSettings = randomClient(clientName, settings, secureSettings);
expectedClients.put(clientName, clientSettings); expectedClients.put(clientName, clientSettings);
} }
if (randomBoolean()) { if (randomBoolean()) {
GoogleCloudStorageClientSettings clientSettings = randomClient("default", settings, secureSettings); final GoogleCloudStorageClientSettings clientSettings = randomClient("default", settings, secureSettings, deprecationWarnings);
expectedClients.put("default", clientSettings); expectedClients.put("default", clientSettings);
} else {
expectedClients.put("default", getClientSettings(Settings.EMPTY, "default"));
} }
return Tuple.tuple(expectedClients, settings.setSecureSettings(secureSettings).build()); return Tuple.tuple(expectedClients, settings.setSecureSettings(secureSettings).build());
@ -107,20 +132,30 @@ public class GoogleCloudStorageClientSettingsTests extends ESTestCase {
/** Generates a random GoogleCloudStorageClientSettings along with the Settings to build it **/ /** Generates a random GoogleCloudStorageClientSettings along with the Settings to build it **/
private static GoogleCloudStorageClientSettings randomClient(final String clientName, private static GoogleCloudStorageClientSettings randomClient(final String clientName,
final Settings.Builder settings, final Settings.Builder settings,
final MockSecureSettings secureSettings) throws Exception { final MockSecureSettings secureSettings,
final List<Setting<?>> deprecationWarnings) throws Exception {
Tuple<GoogleCredential, byte[]> credentials = randomCredential(clientName); final Tuple<ServiceAccountCredentials, byte[]> credentials = randomCredential(clientName);
GoogleCredential credential = credentials.v1(); final ServiceAccountCredentials credential = credentials.v1();
secureSettings.setFile(CREDENTIALS_FILE_SETTING.getConcreteSettingForNamespace(clientName).getKey(), credentials.v2()); secureSettings.setFile(CREDENTIALS_FILE_SETTING.getConcreteSettingForNamespace(clientName).getKey(), credentials.v2());
String endpoint; String endpoint;
if (randomBoolean()) { if (randomBoolean()) {
endpoint = randomAlphaOfLength(5); endpoint = randomFrom("http://www.elastic.co", "http://metadata.google.com:88/oauth", "https://www.googleapis.com",
"https://www.elastic.co:443", "http://localhost:8443", "https://www.googleapis.com/oauth/token");
settings.put(ENDPOINT_SETTING.getConcreteSettingForNamespace(clientName).getKey(), endpoint); settings.put(ENDPOINT_SETTING.getConcreteSettingForNamespace(clientName).getKey(), endpoint);
} else { } else {
endpoint = ENDPOINT_SETTING.getDefault(Settings.EMPTY); endpoint = ENDPOINT_SETTING.getDefault(Settings.EMPTY);
} }
String projectId;
if (randomBoolean()) {
projectId = randomAlphaOfLength(5);
settings.put(PROJECT_ID_SETTING.getConcreteSettingForNamespace(clientName).getKey(), projectId);
} else {
projectId = PROJECT_ID_SETTING.getDefault(Settings.EMPTY);
}
TimeValue connectTimeout; TimeValue connectTimeout;
if (randomBoolean()) { if (randomBoolean()) {
connectTimeout = randomTimeout(); connectTimeout = randomTimeout();
@ -141,40 +176,35 @@ public class GoogleCloudStorageClientSettingsTests extends ESTestCase {
if (randomBoolean()) { if (randomBoolean()) {
applicationName = randomAlphaOfLength(5); applicationName = randomAlphaOfLength(5);
settings.put(APPLICATION_NAME_SETTING.getConcreteSettingForNamespace(clientName).getKey(), applicationName); settings.put(APPLICATION_NAME_SETTING.getConcreteSettingForNamespace(clientName).getKey(), applicationName);
deprecationWarnings.add(APPLICATION_NAME_SETTING.getConcreteSettingForNamespace(clientName));
} else { } else {
applicationName = APPLICATION_NAME_SETTING.getDefault(Settings.EMPTY); applicationName = APPLICATION_NAME_SETTING.getDefault(Settings.EMPTY);
} }
return new GoogleCloudStorageClientSettings(credential, endpoint, connectTimeout, readTimeout, applicationName); return new GoogleCloudStorageClientSettings(credential, endpoint, projectId, connectTimeout, readTimeout, applicationName,
new URI(""));
} }
/** Generates a random GoogleCredential along with its corresponding Service Account file provided as a byte array **/ /** Generates a random GoogleCredential along with its corresponding Service Account file provided as a byte array **/
private static Tuple<GoogleCredential, byte[]> randomCredential(final String clientName) throws Exception { private static Tuple<ServiceAccountCredentials, byte[]> randomCredential(final String clientName) throws Exception {
KeyPair keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair(); final KeyPair keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair();
final ServiceAccountCredentials.Builder credentialBuilder = ServiceAccountCredentials.newBuilder();
GoogleCredential.Builder credentialBuilder = new GoogleCredential.Builder(); credentialBuilder.setClientId("id_" + clientName);
credentialBuilder.setServiceAccountId(clientName); credentialBuilder.setClientEmail(clientName);
credentialBuilder.setServiceAccountProjectId("project_id_" + clientName); credentialBuilder.setProjectId("project_id_" + clientName);
credentialBuilder.setServiceAccountScopes(Collections.singleton(StorageScopes.DEVSTORAGE_FULL_CONTROL)); credentialBuilder.setPrivateKey(keyPair.getPrivate());
credentialBuilder.setServiceAccountPrivateKey(keyPair.getPrivate()); credentialBuilder.setPrivateKeyId("private_key_id_" + clientName);
credentialBuilder.setServiceAccountPrivateKeyId("private_key_id_" + clientName); credentialBuilder.setScopes(Collections.singleton(StorageScopes.DEVSTORAGE_FULL_CONTROL));
final String encodedPrivateKey = Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded());
String encodedPrivateKey = Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded()); final String serviceAccount = "{\"type\":\"service_account\"," +
String serviceAccount = "{\"type\":\"service_account\"," +
"\"project_id\":\"project_id_" + clientName + "\"," + "\"project_id\":\"project_id_" + clientName + "\"," +
"\"private_key_id\":\"private_key_id_" + clientName + "\"," + "\"private_key_id\":\"private_key_id_" + clientName + "\"," +
"\"private_key\":\"-----BEGIN PRIVATE KEY-----\\n" + "\"private_key\":\"-----BEGIN PRIVATE KEY-----\\n" +
encodedPrivateKey + encodedPrivateKey +
"\\n-----END PRIVATE KEY-----\\n\"," + "\\n-----END PRIVATE KEY-----\\n\"," +
"\"client_email\":\"" + clientName + "\"," + "\"client_email\":\"" + clientName + "\"," +
"\"client_id\":\"id_" + clientName + "\"," + "\"client_id\":\"id_" + clientName + "\"" +
"\"auth_uri\":\"https://accounts.google.com/o/oauth2/auth\"," + "}";
"\"token_uri\":\"https://accounts.google.com/o/oauth2/token\"," +
"\"auth_provider_x509_cert_url\":\"https://www.googleapis.com/oauth2/v1/certs\"," +
"\"client_x509_cert_url\":\"https://www.googleapis.com/robot/v1/metadata/x509/" +
clientName +
"%40appspot.gserviceaccount.com\"}";
return Tuple.tuple(credentialBuilder.build(), serviceAccount.getBytes(StandardCharsets.UTF_8)); return Tuple.tuple(credentialBuilder.build(), serviceAccount.getBytes(StandardCharsets.UTF_8));
} }
@ -182,14 +212,16 @@ public class GoogleCloudStorageClientSettingsTests extends ESTestCase {
return randomFrom(TimeValue.MINUS_ONE, TimeValue.ZERO, TimeValue.parseTimeValue(randomPositiveTimeValue(), "test")); return randomFrom(TimeValue.MINUS_ONE, TimeValue.ZERO, TimeValue.parseTimeValue(randomPositiveTimeValue(), "test"));
} }
private static void assertGoogleCredential(final GoogleCredential expected, final GoogleCredential actual) { private static void assertGoogleCredential(ServiceAccountCredentials expected, ServiceAccountCredentials actual) {
if (expected != null) { if (expected != null) {
assertEquals(expected.getServiceAccountUser(), actual.getServiceAccountUser()); assertEquals(expected.getServiceAccountUser(), actual.getServiceAccountUser());
assertEquals(expected.getServiceAccountId(), actual.getServiceAccountId()); assertEquals(expected.getClientId(), actual.getClientId());
assertEquals(expected.getServiceAccountProjectId(), actual.getServiceAccountProjectId()); assertEquals(expected.getClientEmail(), actual.getClientEmail());
assertEquals(expected.getServiceAccountScopesAsString(), actual.getServiceAccountScopesAsString()); assertEquals(expected.getAccount(), actual.getAccount());
assertEquals(expected.getServiceAccountPrivateKey(), actual.getServiceAccountPrivateKey()); assertEquals(expected.getProjectId(), actual.getProjectId());
assertEquals(expected.getServiceAccountPrivateKeyId(), actual.getServiceAccountPrivateKeyId()); assertEquals(expected.getScopes(), actual.getScopes());
assertEquals(expected.getPrivateKey(), actual.getPrivateKey());
assertEquals(expected.getPrivateKeyId(), actual.getPrivateKeyId());
} else { } else {
assertNull(actual); assertNull(actual);
} }

View File

@ -19,79 +19,65 @@
package org.elasticsearch.repositories.gcs; package org.elasticsearch.repositories.gcs;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential; import com.google.auth.Credentials;
import com.google.api.client.http.GenericUrl; import com.google.cloud.http.HttpTransportOptions;
import com.google.api.client.http.HttpIOExceptionHandler; import com.google.cloud.storage.Storage;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpRequestFactory; import org.elasticsearch.common.settings.Setting;
import com.google.api.client.http.HttpRequestInitializer;
import com.google.api.client.http.HttpResponse;
import com.google.api.client.http.HttpUnsuccessfulResponseHandler;
import com.google.api.client.testing.http.MockHttpTransport;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.env.Environment; import org.elasticsearch.env.Environment;
import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.ESTestCase;
import org.hamcrest.Matchers;
import java.util.Collections;
import java.util.Locale;
import java.io.IOException;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
public class GoogleCloudStorageServiceTests extends ESTestCase { public class GoogleCloudStorageServiceTests extends ESTestCase {
/** public void testClientInitializer() throws Exception {
* Test that the {@link GoogleCloudStorageService.DefaultHttpRequestInitializer} attaches new instances final String clientName = randomAlphaOfLength(4).toLowerCase(Locale.ROOT);
* of {@link HttpIOExceptionHandler} and {@link HttpUnsuccessfulResponseHandler} for every HTTP requests.
*/
public void testDefaultHttpRequestInitializer() throws IOException {
final Environment environment = mock(Environment.class); final Environment environment = mock(Environment.class);
when(environment.settings()).thenReturn(Settings.EMPTY); final TimeValue connectTimeValue = TimeValue.timeValueNanos(randomIntBetween(0, 2000000));
final TimeValue readTimeValue = TimeValue.timeValueNanos(randomIntBetween(0, 2000000));
final GoogleCredential credential = mock(GoogleCredential.class); final String applicationName = randomAlphaOfLength(4);
when(credential.handleResponse(any(HttpRequest.class), any(HttpResponse.class), anyBoolean())).thenReturn(false); final String hostName = randomFrom("http://", "https://") + randomAlphaOfLength(4) + ":" + randomIntBetween(1, 65535);
final String projectIdName = randomAlphaOfLength(4);
final TimeValue readTimeout = TimeValue.timeValueSeconds(randomIntBetween(1, 120)); final Settings settings = Settings.builder()
final TimeValue connectTimeout = TimeValue.timeValueSeconds(randomIntBetween(1, 120)); .put(GoogleCloudStorageClientSettings.CONNECT_TIMEOUT_SETTING.getConcreteSettingForNamespace(clientName).getKey(),
final String endpoint = randomBoolean() ? randomAlphaOfLength(10) : null; connectTimeValue.getStringRep())
final String applicationName = randomBoolean() ? randomAlphaOfLength(10) : null; .put(GoogleCloudStorageClientSettings.READ_TIMEOUT_SETTING.getConcreteSettingForNamespace(clientName).getKey(),
readTimeValue.getStringRep())
final GoogleCloudStorageClientSettings clientSettings = .put(GoogleCloudStorageClientSettings.APPLICATION_NAME_SETTING.getConcreteSettingForNamespace(clientName).getKey(),
new GoogleCloudStorageClientSettings(credential, endpoint, connectTimeout, readTimeout, applicationName); applicationName)
.put(GoogleCloudStorageClientSettings.ENDPOINT_SETTING.getConcreteSettingForNamespace(clientName).getKey(), hostName)
final HttpRequestInitializer initializer = GoogleCloudStorageService.createRequestInitializer(clientSettings); .put(GoogleCloudStorageClientSettings.PROJECT_ID_SETTING.getConcreteSettingForNamespace(clientName).getKey(), projectIdName)
final HttpRequestFactory requestFactory = new MockHttpTransport().createRequestFactory(initializer); .build();
when(environment.settings()).thenReturn(settings);
final HttpRequest request1 = requestFactory.buildGetRequest(new GenericUrl()); final GoogleCloudStorageClientSettings clientSettings = GoogleCloudStorageClientSettings.getClientSettings(settings, clientName);
assertEquals((int) connectTimeout.millis(), request1.getConnectTimeout()); final GoogleCloudStorageService service = new GoogleCloudStorageService(environment,
assertEquals((int) readTimeout.millis(), request1.getReadTimeout()); Collections.singletonMap(clientName, clientSettings));
assertSame(credential, request1.getInterceptor()); final IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> service.createClient("another_client"));
assertNotNull(request1.getIOExceptionHandler()); assertThat(e.getMessage(), Matchers.startsWith("Unknown client name"));
assertNotNull(request1.getUnsuccessfulResponseHandler()); assertSettingDeprecationsAndWarnings(
new Setting<?>[] { GoogleCloudStorageClientSettings.APPLICATION_NAME_SETTING.getConcreteSettingForNamespace(clientName) });
final HttpRequest request2 = requestFactory.buildGetRequest(new GenericUrl()); final Storage storage = service.createClient(clientName);
assertEquals((int) connectTimeout.millis(), request2.getConnectTimeout()); assertThat(storage.getOptions().getApplicationName(), Matchers.containsString(applicationName));
assertEquals((int) readTimeout.millis(), request2.getReadTimeout()); assertThat(storage.getOptions().getHost(), Matchers.is(hostName));
assertSame(request1.getInterceptor(), request2.getInterceptor()); assertThat(storage.getOptions().getProjectId(), Matchers.is(projectIdName));
assertNotNull(request2.getIOExceptionHandler()); assertThat(storage.getOptions().getTransportOptions(), Matchers.instanceOf(HttpTransportOptions.class));
assertNotSame(request1.getIOExceptionHandler(), request2.getIOExceptionHandler()); assertThat(((HttpTransportOptions) storage.getOptions().getTransportOptions()).getConnectTimeout(),
assertNotNull(request2.getUnsuccessfulResponseHandler()); Matchers.is((int) connectTimeValue.millis()));
assertNotSame(request1.getUnsuccessfulResponseHandler(), request2.getUnsuccessfulResponseHandler()); assertThat(((HttpTransportOptions) storage.getOptions().getTransportOptions()).getReadTimeout(),
Matchers.is((int) readTimeValue.millis()));
request1.getUnsuccessfulResponseHandler().handleResponse(null, null, false); assertThat(storage.getOptions().getCredentials(), Matchers.nullValue(Credentials.class));
verify(credential, times(1)).handleResponse(any(HttpRequest.class), any(HttpResponse.class), anyBoolean());
request2.getUnsuccessfulResponseHandler().handleResponse(null, null, false);
verify(credential, times(2)).handleResponse(any(HttpRequest.class), any(HttpResponse.class), anyBoolean());
} }
public void testToTimeout() { public void testToTimeout() {
assertNull(GoogleCloudStorageService.toTimeout(null)); assertEquals(-1, GoogleCloudStorageService.toTimeout(null).intValue());
assertNull(GoogleCloudStorageService.toTimeout(TimeValue.ZERO)); assertEquals(-1, GoogleCloudStorageService.toTimeout(TimeValue.ZERO).intValue());
assertEquals(0, GoogleCloudStorageService.toTimeout(TimeValue.MINUS_ONE).intValue()); assertEquals(0, GoogleCloudStorageService.toTimeout(TimeValue.MINUS_ONE).intValue());
} }
} }

View File

@ -19,289 +19,478 @@
package org.elasticsearch.repositories.gcs; package org.elasticsearch.repositories.gcs;
import com.google.api.client.googleapis.json.GoogleJsonError; import com.google.api.gax.paging.Page;
import com.google.api.client.googleapis.json.GoogleJsonResponseException; import com.google.cloud.Policy;
import com.google.api.client.http.AbstractInputStreamContent; import com.google.cloud.ReadChannel;
import com.google.api.client.http.HttpHeaders; import com.google.cloud.RestorableState;
import com.google.api.client.http.HttpMethods; import com.google.cloud.WriteChannel;
import com.google.api.client.http.HttpRequest; import com.google.cloud.storage.Acl;
import com.google.api.client.http.HttpRequestInitializer; import com.google.cloud.storage.Blob;
import com.google.api.client.http.HttpResponseException; import com.google.cloud.storage.BlobId;
import com.google.api.client.http.LowLevelHttpRequest; import com.google.cloud.storage.BlobInfo;
import com.google.api.client.http.LowLevelHttpResponse; import com.google.cloud.storage.Bucket;
import com.google.api.client.http.MultipartContent; import com.google.cloud.storage.BucketInfo;
import com.google.api.client.json.JsonFactory; import com.google.cloud.storage.CopyWriter;
import com.google.api.client.testing.http.MockHttpTransport; import com.google.cloud.storage.ServiceAccount;
import com.google.api.client.testing.http.MockLowLevelHttpRequest; import com.google.cloud.storage.Storage;
import com.google.api.client.testing.http.MockLowLevelHttpResponse; import com.google.cloud.storage.StorageBatch;
import com.google.api.services.storage.Storage; import com.google.cloud.storage.StorageException;
import com.google.api.services.storage.model.Bucket; import com.google.cloud.storage.StorageOptions;
import com.google.api.services.storage.model.StorageObject; import com.google.cloud.storage.StorageRpcOptionUtils;
import org.elasticsearch.common.io.Streams; import com.google.cloud.storage.StorageTestUtils;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.core.internal.io.IOUtils;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.math.BigInteger; import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import static org.mockito.Mockito.mock; import java.util.stream.Collectors;
/** /**
* {@link MockStorage} mocks a {@link Storage} client by storing all the blobs * {@link MockStorage} mocks a {@link Storage} client by storing all the blobs
* in a given concurrent map. * in a given concurrent map.
*/ */
class MockStorage extends Storage { class MockStorage implements Storage {
/* A custom HTTP header name used to propagate the name of the blobs to delete in batch requests */
private static final String DELETION_HEADER = "x-blob-to-delete";
private final String bucketName; private final String bucketName;
private final ConcurrentMap<String, byte[]> blobs; private final ConcurrentMap<String, byte[]> blobs;
MockStorage(final String bucket, final ConcurrentMap<String, byte[]> blobs) { MockStorage(final String bucket, final ConcurrentMap<String, byte[]> blobs) {
super(new MockedHttpTransport(blobs), mock(JsonFactory.class), mock(HttpRequestInitializer.class)); this.bucketName = Objects.requireNonNull(bucket);
this.bucketName = bucket; this.blobs = Objects.requireNonNull(blobs);
this.blobs = blobs;
} }
@Override @Override
public Buckets buckets() { public Bucket get(String bucket, BucketGetOption... options) {
return new MockBuckets(); if (bucketName.equals(bucket)) {
} return StorageTestUtils.createBucket(this, bucketName);
@Override
public Objects objects() {
return new MockObjects();
}
class MockBuckets extends Buckets {
@Override
public Get get(String getBucket) {
return new Get(getBucket) {
@Override
public Bucket execute() {
if (bucketName.equals(getBucket())) {
Bucket bucket = new Bucket();
bucket.setId(bucketName);
return bucket;
} else { } else {
return null; return null;
} }
} }
};
}
}
class MockObjects extends Objects {
@Override @Override
public Get get(String getBucket, String getObject) { public Blob get(BlobId blob) {
return new Get(getBucket, getObject) { if (bucketName.equals(blob.getBucket())) {
@Override final byte[] bytes = blobs.get(blob.getName());
public StorageObject execute() throws IOException { if (bytes != null) {
if (bucketName.equals(getBucket()) == false) { return StorageTestUtils.createBlob(this, bucketName, blob.getName(), bytes.length);
throw newBucketNotFoundException(getBucket());
}
if (blobs.containsKey(getObject()) == false) {
throw newObjectNotFoundException(getObject());
}
StorageObject storageObject = new StorageObject();
storageObject.setId(getObject());
return storageObject;
}
@Override
public InputStream executeMediaAsInputStream() throws IOException {
if (bucketName.equals(getBucket()) == false) {
throw newBucketNotFoundException(getBucket());
}
if (blobs.containsKey(getObject()) == false) {
throw newObjectNotFoundException(getObject());
}
return new ByteArrayInputStream(blobs.get(getObject()));
}
};
}
@Override
public Insert insert(String insertBucket, StorageObject insertObject, AbstractInputStreamContent insertStream) {
return new Insert(insertBucket, insertObject) {
@Override
public StorageObject execute() throws IOException {
if (bucketName.equals(getBucket()) == false) {
throw newBucketNotFoundException(getBucket());
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
Streams.copy(insertStream.getInputStream(), out);
blobs.put(getName(), out.toByteArray());
return null;
}
};
}
@Override
public List list(String listBucket) {
return new List(listBucket) {
@Override
public com.google.api.services.storage.model.Objects execute() throws IOException {
if (bucketName.equals(getBucket()) == false) {
throw newBucketNotFoundException(getBucket());
}
final com.google.api.services.storage.model.Objects objects = new com.google.api.services.storage.model.Objects();
final java.util.List<StorageObject> storageObjects = new ArrayList<>();
for (Entry<String, byte[]> blob : blobs.entrySet()) {
if (getPrefix() == null || blob.getKey().startsWith(getPrefix())) {
StorageObject storageObject = new StorageObject();
storageObject.setId(blob.getKey());
storageObject.setName(blob.getKey());
storageObject.setSize(BigInteger.valueOf((long) blob.getValue().length));
storageObjects.add(storageObject);
} }
} }
objects.setItems(storageObjects);
return objects;
}
};
}
@Override
public Delete delete(String deleteBucket, String deleteObject) {
return new Delete(deleteBucket, deleteObject) {
@Override
public Void execute() throws IOException {
if (bucketName.equals(getBucket()) == false) {
throw newBucketNotFoundException(getBucket());
}
if (blobs.containsKey(getObject()) == false) {
throw newObjectNotFoundException(getObject());
}
blobs.remove(getObject());
return null; return null;
} }
@Override @Override
public HttpRequest buildHttpRequest() throws IOException { public boolean delete(BlobId blob) {
HttpRequest httpRequest = super.buildHttpRequest(); if (bucketName.equals(blob.getBucket()) && blobs.containsKey(blob.getName())) {
httpRequest.getHeaders().put(DELETION_HEADER, getObject()); return blobs.remove(blob.getName()) != null;
return httpRequest;
} }
}; return false;
} }
@Override @Override
public Copy copy(String srcBucket, String srcObject, String destBucket, String destObject, StorageObject content) { public List<Boolean> delete(Iterable<BlobId> blobIds) {
return new Copy(srcBucket, srcObject, destBucket, destObject, content) { final List<Boolean> ans = new ArrayList<>();
@Override for (final BlobId blobId : blobIds) {
public StorageObject execute() throws IOException { ans.add(delete(blobId));
if (bucketName.equals(getSourceBucket()) == false) {
throw newBucketNotFoundException(getSourceBucket());
} }
if (bucketName.equals(getDestinationBucket()) == false) { return ans;
throw newBucketNotFoundException(getDestinationBucket());
} }
final byte[] bytes = blobs.get(getSourceObject()); @Override
public Blob create(BlobInfo blobInfo, byte[] content, BlobTargetOption... options) {
if (bucketName.equals(blobInfo.getBucket()) == false) {
throw new StorageException(404, "Bucket not found");
}
blobs.put(blobInfo.getName(), content);
return get(BlobId.of(blobInfo.getBucket(), blobInfo.getName()));
}
@Override
public CopyWriter copy(CopyRequest copyRequest) {
if (bucketName.equals(copyRequest.getSource().getBucket()) == false) {
throw new StorageException(404, "Source bucket not found");
}
if (bucketName.equals(copyRequest.getTarget().getBucket()) == false) {
throw new StorageException(404, "Target bucket not found");
}
final byte[] bytes = blobs.get(copyRequest.getSource().getName());
if (bytes == null) { if (bytes == null) {
throw newObjectNotFoundException(getSourceObject()); throw new StorageException(404, "Source blob does not exist");
} }
blobs.put(getDestinationObject(), bytes); blobs.put(copyRequest.getTarget().getName(), bytes);
return StorageRpcOptionUtils
StorageObject storageObject = new StorageObject(); .createCopyWriter(get(BlobId.of(copyRequest.getTarget().getBucket(), copyRequest.getTarget().getName())));
storageObject.setId(getDestinationObject());
return storageObject;
}
};
}
}
private static GoogleJsonResponseException newBucketNotFoundException(final String bucket) {
HttpResponseException.Builder builder = new HttpResponseException.Builder(404, "Bucket not found: " + bucket, new HttpHeaders());
return new GoogleJsonResponseException(builder, new GoogleJsonError());
}
private static GoogleJsonResponseException newObjectNotFoundException(final String object) {
HttpResponseException.Builder builder = new HttpResponseException.Builder(404, "Object not found: " + object, new HttpHeaders());
return new GoogleJsonResponseException(builder, new GoogleJsonError());
}
/**
* {@link MockedHttpTransport} extends the existing testing transport to analyze the content
* of {@link com.google.api.client.googleapis.batch.BatchRequest} and delete the appropriates
* blobs. We use this because {@link Storage#batch()} is final and there is no other way to
* extend batch requests for testing purposes.
*/
static class MockedHttpTransport extends MockHttpTransport {
private final ConcurrentMap<String, byte[]> blobs;
MockedHttpTransport(final ConcurrentMap<String, byte[]> blobs) {
this.blobs = blobs;
} }
@Override @Override
public LowLevelHttpRequest buildRequest(final String method, final String url) throws IOException { public Page<Blob> list(String bucket, BlobListOption... options) {
// We analyze the content of the Batch request to detect our custom HTTP header, if (bucketName.equals(bucket) == false) {
// and extract from it the name of the blob to delete. Then we reply a simple throw new StorageException(404, "Bucket not found");
// batch response so that the client parser is happy. }
// final Storage storage = this;
// See https://cloud.google.com/storage/docs/json_api/v1/how-tos/batch for the final String prefix = StorageRpcOptionUtils.getPrefix(options);
// format of the batch request body.
if (HttpMethods.POST.equals(method) && url.endsWith("/batch")) { return new Page<Blob>() {
return new MockLowLevelHttpRequest() {
@Override @Override
public LowLevelHttpResponse execute() throws IOException { public boolean hasNextPage() {
final String contentType = new MultipartContent().getType(); return false;
final StringBuilder builder = new StringBuilder();
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
getStreamingContent().writeTo(out);
Streams.readAllLines(new ByteArrayInputStream(out.toByteArray()), line -> {
if (line != null && line.startsWith(DELETION_HEADER)) {
builder.append("--__END_OF_PART__\r\n");
builder.append("Content-Type: application/http").append("\r\n");
builder.append("\r\n");
builder.append("HTTP/1.1 ");
final String blobName = line.substring(line.indexOf(':') + 1).trim();
if (blobs.containsKey(blobName)) {
builder.append(RestStatus.OK.getStatus());
blobs.remove(blobName);
} else {
builder.append(RestStatus.NOT_FOUND.getStatus());
}
builder.append("\r\n");
builder.append("Content-Type: application/json; charset=UTF-8").append("\r\n");
builder.append("Content-Length: 0").append("\r\n");
builder.append("\r\n");
}
});
builder.append("\r\n");
builder.append("--__END_OF_PART__--");
} }
MockLowLevelHttpResponse response = new MockLowLevelHttpResponse(); @Override
response.setStatusCode(200); public String getNextPageToken() {
response.setContent(builder.toString()); return null;
response.setContentType(contentType); }
return response;
@Override
public Page<Blob> getNextPage() {
throw new UnsupportedOperationException();
}
@Override
public Iterable<Blob> iterateAll() {
return blobs.entrySet().stream()
.filter(blob -> ((prefix == null) || blob.getKey().startsWith(prefix)))
.map(blob -> StorageTestUtils.createBlob(storage, bucketName, blob.getKey(), blob.getValue().length))
.collect(Collectors.toList());
}
@Override
public Iterable<Blob> getValues() {
throw new UnsupportedOperationException();
} }
}; };
} else { }
return super.buildRequest(method, url);
} @Override
} public ReadChannel reader(BlobId blob, BlobSourceOption... options) {
if (bucketName.equals(blob.getBucket())) {
final byte[] bytes = blobs.get(blob.getName());
final ReadableByteChannel readableByteChannel = Channels.newChannel(new ByteArrayInputStream(bytes));
return new ReadChannel() {
@Override
public void close() {
IOUtils.closeWhileHandlingException(readableByteChannel);
}
@Override
public void seek(long position) throws IOException {
throw new UnsupportedOperationException();
}
@Override
public void setChunkSize(int chunkSize) {
throw new UnsupportedOperationException();
}
@Override
public RestorableState<ReadChannel> capture() {
throw new UnsupportedOperationException();
}
@Override
public int read(ByteBuffer dst) throws IOException {
return readableByteChannel.read(dst);
}
@Override
public boolean isOpen() {
return readableByteChannel.isOpen();
}
};
}
return null;
}
@Override
public WriteChannel writer(BlobInfo blobInfo, BlobWriteOption... options) {
if (bucketName.equals(blobInfo.getBucket())) {
final ByteArrayOutputStream output = new ByteArrayOutputStream();
return new WriteChannel() {
final WritableByteChannel writableByteChannel = Channels.newChannel(output);
@Override
public void setChunkSize(int chunkSize) {
throw new UnsupportedOperationException();
}
@Override
public RestorableState<WriteChannel> capture() {
throw new UnsupportedOperationException();
}
@Override
public int write(ByteBuffer src) throws IOException {
return writableByteChannel.write(src);
}
@Override
public boolean isOpen() {
return writableByteChannel.isOpen();
}
@Override
public void close() throws IOException {
IOUtils.closeWhileHandlingException(writableByteChannel);
blobs.put(blobInfo.getName(), output.toByteArray());
}
};
}
return null;
}
// Everything below this line is not implemented.
@Override
public Bucket create(BucketInfo bucketInfo, BucketTargetOption... options) {
return null;
}
@Override
public Blob create(BlobInfo blobInfo, BlobTargetOption... options) {
return null;
}
@Override
public Blob create(BlobInfo blobInfo, InputStream content, BlobWriteOption... options) {
return null;
}
@Override
public Blob get(String bucket, String blob, BlobGetOption... options) {
return null;
}
@Override
public Blob get(BlobId blob, BlobGetOption... options) {
return null;
}
@Override
public Page<Bucket> list(BucketListOption... options) {
return null;
}
@Override
public Bucket update(BucketInfo bucketInfo, BucketTargetOption... options) {
return null;
}
@Override
public Blob update(BlobInfo blobInfo, BlobTargetOption... options) {
return null;
}
@Override
public Blob update(BlobInfo blobInfo) {
return null;
}
@Override
public boolean delete(String bucket, BucketSourceOption... options) {
return false;
}
@Override
public boolean delete(String bucket, String blob, BlobSourceOption... options) {
return false;
}
@Override
public boolean delete(BlobId blob, BlobSourceOption... options) {
return false;
}
@Override
public Blob compose(ComposeRequest composeRequest) {
return null;
}
@Override
public byte[] readAllBytes(String bucket, String blob, BlobSourceOption... options) {
return new byte[0];
}
@Override
public byte[] readAllBytes(BlobId blob, BlobSourceOption... options) {
return new byte[0];
}
@Override
public StorageBatch batch() {
return null;
}
@Override
public ReadChannel reader(String bucket, String blob, BlobSourceOption... options) {
return null;
}
@Override
public URL signUrl(BlobInfo blobInfo, long duration, TimeUnit unit, SignUrlOption... options) {
return null;
}
@Override
public List<Blob> get(BlobId... blobIds) {
return null;
}
@Override
public List<Blob> get(Iterable<BlobId> blobIds) {
return null;
}
@Override
public List<Blob> update(BlobInfo... blobInfos) {
return null;
}
@Override
public List<Blob> update(Iterable<BlobInfo> blobInfos) {
return null;
}
@Override
public List<Boolean> delete(BlobId... blobIds) {
return null;
}
@Override
public Acl getAcl(String bucket, Acl.Entity entity, BucketSourceOption... options) {
return null;
}
@Override
public Acl getAcl(String bucket, Acl.Entity entity) {
return null;
}
@Override
public boolean deleteAcl(String bucket, Acl.Entity entity, BucketSourceOption... options) {
return false;
}
@Override
public boolean deleteAcl(String bucket, Acl.Entity entity) {
return false;
}
@Override
public Acl createAcl(String bucket, Acl acl, BucketSourceOption... options) {
return null;
}
@Override
public Acl createAcl(String bucket, Acl acl) {
return null;
}
@Override
public Acl updateAcl(String bucket, Acl acl, BucketSourceOption... options) {
return null;
}
@Override
public Acl updateAcl(String bucket, Acl acl) {
return null;
}
@Override
public List<Acl> listAcls(String bucket, BucketSourceOption... options) {
return null;
}
@Override
public List<Acl> listAcls(String bucket) {
return null;
}
@Override
public Acl getDefaultAcl(String bucket, Acl.Entity entity) {
return null;
}
@Override
public boolean deleteDefaultAcl(String bucket, Acl.Entity entity) {
return false;
}
@Override
public Acl createDefaultAcl(String bucket, Acl acl) {
return null;
}
@Override
public Acl updateDefaultAcl(String bucket, Acl acl) {
return null;
}
@Override
public List<Acl> listDefaultAcls(String bucket) {
return null;
}
@Override
public Acl getAcl(BlobId blob, Acl.Entity entity) {
return null;
}
@Override
public boolean deleteAcl(BlobId blob, Acl.Entity entity) {
return false;
}
@Override
public Acl createAcl(BlobId blob, Acl acl) {
return null;
}
@Override
public Acl updateAcl(BlobId blob, Acl acl) {
return null;
}
@Override
public List<Acl> listAcls(BlobId blob) {
return null;
}
@Override
public Policy getIamPolicy(String bucket, BucketSourceOption... options) {
return null;
}
@Override
public Policy setIamPolicy(String bucket, Policy policy, BucketSourceOption... options) {
return null;
}
@Override
public List<Boolean> testIamPermissions(String bucket, List<String> permissions, BucketSourceOption... options) {
return null;
}
@Override
public ServiceAccount getServiceAccount(String projectId) {
return null;
}
@Override
public StorageOptions getOptions() {
return null;
} }
} }