Browse Source

HLRC: Get ML calendars (#33760)

David Kyle 7 years ago
parent
commit
9543992d8e
18 changed files with 563 additions and 66 deletions
  1. 15 2
      client/rest-high-level/src/main/java/org/elasticsearch/client/MLRequestConverters.java
  2. 40 0
      client/rest-high-level/src/main/java/org/elasticsearch/client/MachineLearningClient.java
  3. 104 0
      client/rest-high-level/src/main/java/org/elasticsearch/client/ml/GetCalendarsRequest.java
  4. 86 0
      client/rest-high-level/src/main/java/org/elasticsearch/client/ml/GetCalendarsResponse.java
  5. 4 12
      client/rest-high-level/src/main/java/org/elasticsearch/client/ml/job/results/AnomalyRecord.java
  6. 3 12
      client/rest-high-level/src/main/java/org/elasticsearch/client/ml/job/results/Bucket.java
  7. 4 12
      client/rest-high-level/src/main/java/org/elasticsearch/client/ml/job/results/BucketInfluencer.java
  8. 4 12
      client/rest-high-level/src/main/java/org/elasticsearch/client/ml/job/results/Influencer.java
  9. 4 12
      client/rest-high-level/src/main/java/org/elasticsearch/client/ml/job/results/OverallBucket.java
  10. 0 1
      client/rest-high-level/src/main/java/org/elasticsearch/client/ml/job/results/Result.java
  11. 24 1
      client/rest-high-level/src/test/java/org/elasticsearch/client/MLRequestConvertersTests.java
  12. 26 1
      client/rest-high-level/src/test/java/org/elasticsearch/client/MachineLearningIT.java
  13. 65 0
      client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/MlClientDocumentationIT.java
  14. 46 0
      client/rest-high-level/src/test/java/org/elasticsearch/client/ml/GetCalendarsRequestTests.java
  15. 52 0
      client/rest-high-level/src/test/java/org/elasticsearch/client/ml/GetCalendarsResponseTests.java
  16. 83 0
      docs/java-rest/high-level/ml/get-calendars.asciidoc
  17. 1 1
      docs/java-rest/high-level/ml/put-calendar.asciidoc
  18. 2 0
      docs/java-rest/high-level/supported-apis.asciidoc

+ 15 - 2
client/rest-high-level/src/main/java/org/elasticsearch/client/MLRequestConverters.java

@@ -34,6 +34,7 @@ import org.elasticsearch.client.ml.DeleteJobRequest;
 import org.elasticsearch.client.ml.FlushJobRequest;
 import org.elasticsearch.client.ml.ForecastJobRequest;
 import org.elasticsearch.client.ml.GetBucketsRequest;
+import org.elasticsearch.client.ml.GetCalendarsRequest;
 import org.elasticsearch.client.ml.GetCategoriesRequest;
 import org.elasticsearch.client.ml.GetDatafeedRequest;
 import org.elasticsearch.client.ml.GetInfluencersRequest;
@@ -229,7 +230,7 @@ final class MLRequestConverters {
         return request;
     }
 
-    static Request deleteForecast(DeleteForecastRequest deleteForecastRequest) throws IOException {
+    static Request deleteForecast(DeleteForecastRequest deleteForecastRequest) {
         String endpoint = new EndpointBuilder()
             .addPathPartAsIs("_xpack")
             .addPathPartAsIs("ml")
@@ -305,7 +306,7 @@ final class MLRequestConverters {
         return request;
     }
 
-    static Request postData(PostDataRequest postDataRequest) throws IOException {
+    static Request postData(PostDataRequest postDataRequest) {
         String endpoint = new EndpointBuilder()
             .addPathPartAsIs("_xpack")
             .addPathPartAsIs("ml")
@@ -359,4 +360,16 @@ final class MLRequestConverters {
         request.setEntity(createEntity(putCalendarRequest, REQUEST_BODY_CONTENT_TYPE));
         return request;
     }
+
+    static Request getCalendars(GetCalendarsRequest getCalendarsRequest) throws IOException {
+        String endpoint = new EndpointBuilder()
+                .addPathPartAsIs("_xpack")
+                .addPathPartAsIs("ml")
+                .addPathPartAsIs("calendars")
+                .addPathPart(getCalendarsRequest.getCalendarId())
+                .build();
+        Request request = new Request(HttpGet.METHOD_NAME, endpoint);
+        request.setEntity(createEntity(getCalendarsRequest, REQUEST_BODY_CONTENT_TYPE));
+        return request;
+    }
 }

+ 40 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/MachineLearningClient.java

@@ -31,6 +31,8 @@ import org.elasticsearch.client.ml.ForecastJobRequest;
 import org.elasticsearch.client.ml.ForecastJobResponse;
 import org.elasticsearch.client.ml.GetBucketsRequest;
 import org.elasticsearch.client.ml.GetBucketsResponse;
+import org.elasticsearch.client.ml.GetCalendarsRequest;
+import org.elasticsearch.client.ml.GetCalendarsResponse;
 import org.elasticsearch.client.ml.GetCategoriesRequest;
 import org.elasticsearch.client.ml.GetCategoriesResponse;
 import org.elasticsearch.client.ml.GetDatafeedRequest;
@@ -792,6 +794,44 @@ public final class MachineLearningClient {
                 Collections.emptySet());
     }
 
+    /**
+     * Gets a single or multiple calendars.
+     * <p>
+     * For additional info
+     * see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-get-calendar.html">ML GET calendars documentation</a>
+     *
+     * @param request The calendars request
+     * @param options Additional request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
+     * @return {@link GetCalendarsResponse} response object containing the {@link org.elasticsearch.client.ml.calendars.Calendar}
+     * objects and the number of calendars found
+     */
+    public GetCalendarsResponse getCalendars(GetCalendarsRequest request, RequestOptions options) throws IOException {
+        return restHighLevelClient.performRequestAndParseEntity(request,
+                MLRequestConverters::getCalendars,
+                options,
+                GetCalendarsResponse::fromXContent,
+                Collections.emptySet());
+    }
+
+    /**
+     * Gets a single or multiple calendars, notifies listener once the requested records are retrieved.
+     * <p>
+     * For additional info
+     * see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-get-calendar.html">ML GET calendars documentation</a>
+     *
+     * @param request The calendars request
+     * @param options Additional request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
+     * @param listener Listener to be notified upon request completion
+     */
+    public void getCalendarsAsync(GetCalendarsRequest request, RequestOptions options, ActionListener<GetCalendarsResponse> listener) {
+        restHighLevelClient.performRequestAsyncAndParseEntity(request,
+                MLRequestConverters::getCalendars,
+                options,
+                GetCalendarsResponse::fromXContent,
+                listener,
+                Collections.emptySet());
+    }
+
     /**
      * Gets the influencers for a Machine Learning Job.
      * <p>

+ 104 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/ml/GetCalendarsRequest.java

@@ -0,0 +1,104 @@
+/*
+ * 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 org.elasticsearch.client.ml;
+
+import org.elasticsearch.action.ActionRequest;
+import org.elasticsearch.action.ActionRequestValidationException;
+import org.elasticsearch.client.ml.calendars.Calendar;
+import org.elasticsearch.common.xcontent.ObjectParser;
+import org.elasticsearch.common.xcontent.ToXContentObject;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+
+import org.elasticsearch.client.ml.job.util.PageParams;
+
+import java.io.IOException;
+import java.util.Objects;
+
+public class GetCalendarsRequest extends ActionRequest implements ToXContentObject {
+
+    public static final ObjectParser<GetCalendarsRequest, Void> PARSER =
+            new ObjectParser<>("get_calendars_request", GetCalendarsRequest::new);
+
+    static {
+        PARSER.declareString(GetCalendarsRequest::setCalendarId, Calendar.ID);
+        PARSER.declareObject(GetCalendarsRequest::setPageParams, PageParams.PARSER, PageParams.PAGE);
+    }
+
+    private String calendarId;
+    private PageParams pageParams;
+
+    public GetCalendarsRequest() {
+    }
+
+    public GetCalendarsRequest(String calendarId) {
+        this.calendarId = calendarId;
+    }
+
+    public String getCalendarId() {
+        return calendarId;
+    }
+
+    public void setCalendarId(String calendarId) {
+        this.calendarId = calendarId;
+    }
+
+    public PageParams getPageParams() {
+        return pageParams;
+    }
+
+    public void setPageParams(PageParams pageParams) {
+        this.pageParams = pageParams;
+    }
+
+    @Override
+    public ActionRequestValidationException validate() {
+        return null;
+    }
+
+    @Override
+    public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+        builder.startObject();
+        if (calendarId != null) {
+            builder.field(Calendar.ID.getPreferredName(), calendarId);
+        }
+        if (pageParams != null) {
+            builder.field(PageParams.PAGE.getPreferredName(), pageParams);
+        }
+        builder.endObject();
+        return builder;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(calendarId, pageParams);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        GetCalendarsRequest other = (GetCalendarsRequest) obj;
+        return Objects.equals(calendarId, other.calendarId) && Objects.equals(pageParams, other.pageParams);
+    }
+}

+ 86 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/ml/GetCalendarsResponse.java

@@ -0,0 +1,86 @@
+/*
+ * 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 org.elasticsearch.client.ml;
+
+import org.elasticsearch.client.ml.calendars.Calendar;
+import org.elasticsearch.common.ParseField;
+import org.elasticsearch.common.Strings;
+import org.elasticsearch.common.xcontent.ConstructingObjectParser;
+import org.elasticsearch.common.xcontent.XContentParser;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Objects;
+
+import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;
+
+public class GetCalendarsResponse extends AbstractResultResponse<Calendar> {
+
+    public static final ParseField RESULTS_FIELD = new ParseField("calendars");
+
+    @SuppressWarnings("unchecked")
+    public static final ConstructingObjectParser<GetCalendarsResponse, Void> PARSER =
+            new ConstructingObjectParser<>("calendars_response", true,
+                    a -> new GetCalendarsResponse((List<Calendar>) a[0], (long) a[1]));
+
+    static {
+        PARSER.declareObjectArray(constructorArg(), Calendar.PARSER, RESULTS_FIELD);
+        PARSER.declareLong(constructorArg(), AbstractResultResponse.COUNT);
+    }
+
+    public static GetCalendarsResponse fromXContent(XContentParser parser) throws IOException {
+        return PARSER.parse(parser, null);
+    }
+
+    GetCalendarsResponse(List<Calendar> calendars, long count) {
+        super(RESULTS_FIELD, calendars, count);
+    }
+
+    /**
+     * The collection of {@link Calendar} objects found in the query
+     */
+    public List<Calendar> calendars() {
+        return results;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(results, count);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+
+        GetCalendarsResponse other = (GetCalendarsResponse) obj;
+        return Objects.equals(results, other.results) && count == other.count;
+    }
+
+    @Override
+    public final String toString() {
+        return Strings.toString(this);
+    }
+}

+ 4 - 12
client/rest-high-level/src/main/java/org/elasticsearch/client/ml/job/results/AnomalyRecord.java

@@ -19,16 +19,14 @@
 package org.elasticsearch.client.ml.job.results;
 
 import org.elasticsearch.client.ml.job.config.Job;
+import org.elasticsearch.client.ml.job.util.TimeUtil;
 import org.elasticsearch.common.ParseField;
-import org.elasticsearch.common.time.DateFormatters;
 import org.elasticsearch.common.xcontent.ConstructingObjectParser;
 import org.elasticsearch.common.xcontent.ObjectParser.ValueType;
 import org.elasticsearch.common.xcontent.ToXContentObject;
 import org.elasticsearch.common.xcontent.XContentBuilder;
-import org.elasticsearch.common.xcontent.XContentParser.Token;
 
 import java.io.IOException;
-import java.time.format.DateTimeFormatter;
 import java.util.Collections;
 import java.util.Date;
 import java.util.List;
@@ -90,15 +88,9 @@ public class AnomalyRecord implements ToXContentObject {
 
     static {
         PARSER.declareString(ConstructingObjectParser.constructorArg(), Job.ID);
-        PARSER.declareField(ConstructingObjectParser.constructorArg(), p -> {
-            if (p.currentToken() == Token.VALUE_NUMBER) {
-                return new Date(p.longValue());
-            } else if (p.currentToken() == Token.VALUE_STRING) {
-                return new Date(DateFormatters.toZonedDateTime(DateTimeFormatter.ISO_INSTANT.parse(p.text())).toInstant().toEpochMilli());
-            }
-            throw new IllegalArgumentException("unexpected token [" + p.currentToken() + "] for ["
-                    + Result.TIMESTAMP.getPreferredName() + "]");
-        }, Result.TIMESTAMP, ValueType.VALUE);
+        PARSER.declareField(ConstructingObjectParser.constructorArg(),
+                (p) -> TimeUtil.parseTimeField(p, Result.TIMESTAMP.getPreferredName()),
+                Result.TIMESTAMP, ValueType.VALUE);
         PARSER.declareLong(ConstructingObjectParser.constructorArg(), BUCKET_SPAN);
         PARSER.declareString((anomalyRecord, s) -> {}, Result.RESULT_TYPE);
         PARSER.declareDouble(AnomalyRecord::setProbability, PROBABILITY);

+ 3 - 12
client/rest-high-level/src/main/java/org/elasticsearch/client/ml/job/results/Bucket.java

@@ -19,16 +19,14 @@
 package org.elasticsearch.client.ml.job.results;
 
 import org.elasticsearch.client.ml.job.config.Job;
+import org.elasticsearch.client.ml.job.util.TimeUtil;
 import org.elasticsearch.common.ParseField;
-import org.elasticsearch.common.time.DateFormatters;
 import org.elasticsearch.common.xcontent.ConstructingObjectParser;
 import org.elasticsearch.common.xcontent.ObjectParser.ValueType;
 import org.elasticsearch.common.xcontent.ToXContentObject;
 import org.elasticsearch.common.xcontent.XContentBuilder;
-import org.elasticsearch.common.xcontent.XContentParser.Token;
 
 import java.io.IOException;
-import java.time.format.DateTimeFormatter;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Date;
@@ -63,15 +61,8 @@ public class Bucket implements ToXContentObject {
 
     static {
         PARSER.declareString(ConstructingObjectParser.constructorArg(), Job.ID);
-        PARSER.declareField(ConstructingObjectParser.constructorArg(), p -> {
-            if (p.currentToken() == Token.VALUE_NUMBER) {
-                return new Date(p.longValue());
-            } else if (p.currentToken() == Token.VALUE_STRING) {
-                return new Date(DateFormatters.toZonedDateTime(DateTimeFormatter.ISO_INSTANT.parse(p.text())).toInstant().toEpochMilli());
-            }
-            throw new IllegalArgumentException("unexpected token [" + p.currentToken() + "] for ["
-                    + Result.TIMESTAMP.getPreferredName() + "]");
-        }, Result.TIMESTAMP, ValueType.VALUE);
+        PARSER.declareField(ConstructingObjectParser.constructorArg(),
+                (p) -> TimeUtil.parseTimeField(p, Result.TIMESTAMP.getPreferredName()), Result.TIMESTAMP, ValueType.VALUE);
         PARSER.declareLong(ConstructingObjectParser.constructorArg(), BUCKET_SPAN);
         PARSER.declareDouble(Bucket::setAnomalyScore, ANOMALY_SCORE);
         PARSER.declareDouble(Bucket::setInitialAnomalyScore, INITIAL_ANOMALY_SCORE);

+ 4 - 12
client/rest-high-level/src/main/java/org/elasticsearch/client/ml/job/results/BucketInfluencer.java

@@ -19,16 +19,14 @@
 package org.elasticsearch.client.ml.job.results;
 
 import org.elasticsearch.client.ml.job.config.Job;
+import org.elasticsearch.client.ml.job.util.TimeUtil;
 import org.elasticsearch.common.ParseField;
-import org.elasticsearch.common.time.DateFormatters;
 import org.elasticsearch.common.xcontent.ConstructingObjectParser;
 import org.elasticsearch.common.xcontent.ObjectParser.ValueType;
 import org.elasticsearch.common.xcontent.ToXContentObject;
 import org.elasticsearch.common.xcontent.XContentBuilder;
-import org.elasticsearch.common.xcontent.XContentParser.Token;
 
 import java.io.IOException;
-import java.time.format.DateTimeFormatter;
 import java.util.Date;
 import java.util.Objects;
 
@@ -56,15 +54,9 @@ public class BucketInfluencer implements ToXContentObject {
 
     static {
         PARSER.declareString(ConstructingObjectParser.constructorArg(), Job.ID);
-        PARSER.declareField(ConstructingObjectParser.constructorArg(), p -> {
-            if (p.currentToken() == Token.VALUE_NUMBER) {
-                return new Date(p.longValue());
-            } else if (p.currentToken() == Token.VALUE_STRING) {
-                return new Date(DateFormatters.toZonedDateTime(DateTimeFormatter.ISO_INSTANT.parse(p.text())).toInstant().toEpochMilli());
-            }
-            throw new IllegalArgumentException("unexpected token [" + p.currentToken() + "] for ["
-                    + Result.TIMESTAMP.getPreferredName() + "]");
-        }, Result.TIMESTAMP, ValueType.VALUE);
+        PARSER.declareField(ConstructingObjectParser.constructorArg(),
+                (p) -> TimeUtil.parseTimeField(p, Result.TIMESTAMP.getPreferredName()),
+                Result.TIMESTAMP, ValueType.VALUE);
         PARSER.declareLong(ConstructingObjectParser.constructorArg(), BUCKET_SPAN);
         PARSER.declareString((bucketInfluencer, s) -> {}, Result.RESULT_TYPE);
         PARSER.declareString(BucketInfluencer::setInfluencerFieldName, INFLUENCER_FIELD_NAME);

+ 4 - 12
client/rest-high-level/src/main/java/org/elasticsearch/client/ml/job/results/Influencer.java

@@ -19,16 +19,14 @@
 package org.elasticsearch.client.ml.job.results;
 
 import org.elasticsearch.client.ml.job.config.Job;
+import org.elasticsearch.client.ml.job.util.TimeUtil;
 import org.elasticsearch.common.ParseField;
-import org.elasticsearch.common.time.DateFormatters;
 import org.elasticsearch.common.xcontent.ConstructingObjectParser;
 import org.elasticsearch.common.xcontent.ObjectParser.ValueType;
 import org.elasticsearch.common.xcontent.ToXContentObject;
 import org.elasticsearch.common.xcontent.XContentBuilder;
-import org.elasticsearch.common.xcontent.XContentParser.Token;
 
 import java.io.IOException;
-import java.time.format.DateTimeFormatter;
 import java.util.Date;
 import java.util.Objects;
 
@@ -61,15 +59,9 @@ public class Influencer implements ToXContentObject {
         PARSER.declareString(ConstructingObjectParser.constructorArg(), Job.ID);
         PARSER.declareString(ConstructingObjectParser.constructorArg(), INFLUENCER_FIELD_NAME);
         PARSER.declareString(ConstructingObjectParser.constructorArg(), INFLUENCER_FIELD_VALUE);
-        PARSER.declareField(ConstructingObjectParser.constructorArg(), p -> {
-            if (p.currentToken() == Token.VALUE_NUMBER) {
-                return new Date(p.longValue());
-            } else if (p.currentToken() == Token.VALUE_STRING) {
-                return new Date(DateFormatters.toZonedDateTime(DateTimeFormatter.ISO_INSTANT.parse(p.text())).toInstant().toEpochMilli());
-            }
-            throw new IllegalArgumentException("unexpected token [" + p.currentToken() + "] for ["
-                    + Result.TIMESTAMP.getPreferredName() + "]");
-        }, Result.TIMESTAMP, ValueType.VALUE);
+        PARSER.declareField(ConstructingObjectParser.constructorArg(),
+                (p) -> TimeUtil.parseTimeField(p, Result.TIMESTAMP.getPreferredName()),
+                Result.TIMESTAMP, ValueType.VALUE);
         PARSER.declareLong(ConstructingObjectParser.constructorArg(), BUCKET_SPAN);
         PARSER.declareString((influencer, s) -> {}, Result.RESULT_TYPE);
         PARSER.declareDouble(Influencer::setProbability, PROBABILITY);

+ 4 - 12
client/rest-high-level/src/main/java/org/elasticsearch/client/ml/job/results/OverallBucket.java

@@ -19,16 +19,14 @@
 package org.elasticsearch.client.ml.job.results;
 
 import org.elasticsearch.client.ml.job.config.Job;
+import org.elasticsearch.client.ml.job.util.TimeUtil;
 import org.elasticsearch.common.ParseField;
-import org.elasticsearch.common.time.DateFormatters;
 import org.elasticsearch.common.xcontent.ConstructingObjectParser;
 import org.elasticsearch.common.xcontent.ObjectParser;
 import org.elasticsearch.common.xcontent.ToXContentObject;
 import org.elasticsearch.common.xcontent.XContentBuilder;
-import org.elasticsearch.common.xcontent.XContentParser;
 
 import java.io.IOException;
-import java.time.format.DateTimeFormatter;
 import java.util.Collections;
 import java.util.Date;
 import java.util.List;
@@ -56,15 +54,9 @@ public class OverallBucket implements ToXContentObject {
             a -> new OverallBucket((Date) a[0], (long) a[1], (double) a[2], (boolean) a[3]));
 
     static {
-        PARSER.declareField(ConstructingObjectParser.constructorArg(), p -> {
-            if (p.currentToken() == XContentParser.Token.VALUE_NUMBER) {
-                return new Date(p.longValue());
-            } else if (p.currentToken() == XContentParser.Token.VALUE_STRING) {
-                return new Date(DateFormatters.toZonedDateTime(DateTimeFormatter.ISO_INSTANT.parse(p.text())).toInstant().toEpochMilli());
-            }
-            throw new IllegalArgumentException("unexpected token [" + p.currentToken() + "] for ["
-                + Result.TIMESTAMP.getPreferredName() + "]");
-        }, Result.TIMESTAMP, ObjectParser.ValueType.VALUE);
+        PARSER.declareField(ConstructingObjectParser.constructorArg(),
+                (p) -> TimeUtil.parseTimeField(p, Result.TIMESTAMP.getPreferredName()),
+                Result.TIMESTAMP, ObjectParser.ValueType.VALUE);
         PARSER.declareLong(ConstructingObjectParser.constructorArg(), BUCKET_SPAN);
         PARSER.declareDouble(ConstructingObjectParser.constructorArg(), OVERALL_SCORE);
         PARSER.declareBoolean(ConstructingObjectParser.constructorArg(), Result.IS_INTERIM);

+ 0 - 1
client/rest-high-level/src/main/java/org/elasticsearch/client/ml/job/results/Result.java

@@ -28,7 +28,6 @@ public final class Result {
     /**
      * Serialisation fields
      */
-    public static final ParseField TYPE = new ParseField("result");
     public static final ParseField RESULT_TYPE = new ParseField("result_type");
     public static final ParseField TIMESTAMP = new ParseField("timestamp");
     public static final ParseField IS_INTERIM = new ParseField("is_interim");

+ 24 - 1
client/rest-high-level/src/test/java/org/elasticsearch/client/MLRequestConvertersTests.java

@@ -30,6 +30,7 @@ import org.elasticsearch.client.ml.DeleteJobRequest;
 import org.elasticsearch.client.ml.FlushJobRequest;
 import org.elasticsearch.client.ml.ForecastJobRequest;
 import org.elasticsearch.client.ml.GetBucketsRequest;
+import org.elasticsearch.client.ml.GetCalendarsRequest;
 import org.elasticsearch.client.ml.GetCategoriesRequest;
 import org.elasticsearch.client.ml.GetDatafeedRequest;
 import org.elasticsearch.client.ml.GetInfluencersRequest;
@@ -259,7 +260,7 @@ public class MLRequestConvertersTests extends ESTestCase {
         assertEquals(Boolean.toString(true), request.getParameters().get("force"));
     }
 
-    public void testDeleteForecast() throws Exception {
+    public void testDeleteForecast() {
         String jobId = randomAlphaOfLength(10);
         DeleteForecastRequest deleteForecastRequest = new DeleteForecastRequest(jobId);
 
@@ -415,6 +416,28 @@ public class MLRequestConvertersTests extends ESTestCase {
         }
     }
 
+    public void testGetCalendars() throws IOException {
+        GetCalendarsRequest getCalendarsRequest = new GetCalendarsRequest();
+        String expectedEndpoint = "/_xpack/ml/calendars";
+
+        if (randomBoolean()) {
+            String calendarId = randomAlphaOfLength(10);
+            getCalendarsRequest.setCalendarId(calendarId);
+            expectedEndpoint += "/" + calendarId;
+        }
+        if (randomBoolean()) {
+            getCalendarsRequest.setPageParams(new PageParams(10, 20));
+        }
+
+        Request request = MLRequestConverters.getCalendars(getCalendarsRequest);
+        assertEquals(HttpGet.METHOD_NAME, request.getMethod());
+        assertEquals(expectedEndpoint, request.getEndpoint());
+        try (XContentParser parser = createParser(JsonXContent.jsonXContent, request.getEntity().getContent())) {
+            GetCalendarsRequest parsedRequest = GetCalendarsRequest.PARSER.apply(parser, null);
+            assertThat(parsedRequest, equalTo(getCalendarsRequest));
+        }
+    }
+
     private static Job createValidJob(String jobId) {
         AnalysisConfig.Builder analysisConfig = AnalysisConfig.builder(Collections.singletonList(
                 Detector.builder().setFunction("count").build()));

+ 26 - 1
client/rest-high-level/src/test/java/org/elasticsearch/client/MachineLearningIT.java

@@ -32,6 +32,8 @@ import org.elasticsearch.client.ml.FlushJobRequest;
 import org.elasticsearch.client.ml.FlushJobResponse;
 import org.elasticsearch.client.ml.ForecastJobRequest;
 import org.elasticsearch.client.ml.ForecastJobResponse;
+import org.elasticsearch.client.ml.GetCalendarsRequest;
+import org.elasticsearch.client.ml.GetCalendarsResponse;
 import org.elasticsearch.client.ml.GetDatafeedRequest;
 import org.elasticsearch.client.ml.GetDatafeedResponse;
 import org.elasticsearch.client.ml.GetJobRequest;
@@ -483,7 +485,6 @@ public class MachineLearningIT extends ESRestHighLevelClientTestCase {
     }
 
     public void testPutCalendar() throws IOException {
-
         Calendar calendar = CalendarTests.testInstance();
         MachineLearningClient machineLearningClient = highLevelClient().machineLearning();
         PutCalendarResponse putCalendarResponse = execute(new PutCalendarRequest(calendar), machineLearningClient::putCalendar,
@@ -492,6 +493,30 @@ public class MachineLearningIT extends ESRestHighLevelClientTestCase {
         assertThat(putCalendarResponse.getCalendar(), equalTo(calendar));
     }
 
+    public void testGetCalendars() throws Exception {
+        Calendar calendar1 = CalendarTests.testInstance();
+        Calendar calendar2 = CalendarTests.testInstance();
+
+        MachineLearningClient machineLearningClient = highLevelClient().machineLearning();
+        machineLearningClient.putCalendar(new PutCalendarRequest(calendar1), RequestOptions.DEFAULT);
+        machineLearningClient.putCalendar(new PutCalendarRequest(calendar2), RequestOptions.DEFAULT);
+
+        GetCalendarsRequest getCalendarsRequest = new GetCalendarsRequest();
+        getCalendarsRequest.setCalendarId("_all");
+        GetCalendarsResponse getCalendarsResponse = execute(getCalendarsRequest, machineLearningClient::getCalendars,
+                machineLearningClient::getCalendarsAsync);
+        assertEquals(2, getCalendarsResponse.count());
+        assertEquals(2, getCalendarsResponse.calendars().size());
+        assertThat(getCalendarsResponse.calendars().stream().map(Calendar::getId).collect(Collectors.toList()),
+                hasItems(calendar1.getId(), calendar1.getId()));
+
+        getCalendarsRequest.setCalendarId(calendar1.getId());
+        getCalendarsResponse = execute(getCalendarsRequest, machineLearningClient::getCalendars,
+                machineLearningClient::getCalendarsAsync);
+        assertEquals(1, getCalendarsResponse.count());
+        assertEquals(calendar1, getCalendarsResponse.calendars().get(0));
+    }
+
     public static String randomValidJobId() {
         CodepointSetGenerator generator = new CodepointSetGenerator("abcdefghijklmnopqrstuvwxyz0123456789".toCharArray());
         return generator.ofCodePointsLength(random(), 10, 10);

+ 65 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/MlClientDocumentationIT.java

@@ -43,6 +43,8 @@ import org.elasticsearch.client.ml.ForecastJobRequest;
 import org.elasticsearch.client.ml.ForecastJobResponse;
 import org.elasticsearch.client.ml.GetBucketsRequest;
 import org.elasticsearch.client.ml.GetBucketsResponse;
+import org.elasticsearch.client.ml.GetCalendarsRequest;
+import org.elasticsearch.client.ml.GetCalendarsResponse;
 import org.elasticsearch.client.ml.GetCategoriesRequest;
 import org.elasticsearch.client.ml.GetCategoriesResponse;
 import org.elasticsearch.client.ml.GetDatafeedRequest;
@@ -880,6 +882,7 @@ public class MlClientDocumentationIT extends ESRestHighLevelClientTestCase {
         PostDataRequest postDataRequest = new PostDataRequest(job.getId(), builder);
         client.machineLearning().postData(postDataRequest, RequestOptions.DEFAULT);
         client.machineLearning().flushJob(new FlushJobRequest(job.getId()), RequestOptions.DEFAULT);
+
         ForecastJobResponse forecastJobResponse = client.machineLearning().
             forecastJob(new ForecastJobRequest(job.getId()), RequestOptions.DEFAULT);
         String forecastId = forecastJobResponse.getForecastId();
@@ -1526,4 +1529,66 @@ public class MlClientDocumentationIT extends ESRestHighLevelClientTestCase {
 
         assertTrue(latch.await(30L, TimeUnit.SECONDS));
     }
+
+    public void testGetCalendar() throws IOException, InterruptedException {
+        RestHighLevelClient client = highLevelClient();
+
+        Calendar calendar = new Calendar("holidays", Collections.singletonList("job_1"), "A calendar for public holidays");
+        PutCalendarRequest putRequest = new PutCalendarRequest(calendar);
+        client.machineLearning().putCalendar(putRequest, RequestOptions.DEFAULT);
+        {
+            //tag::x-pack-ml-get-calendars-request
+            GetCalendarsRequest request = new GetCalendarsRequest(); // <1>
+            //end::x-pack-ml-get-calendars-request
+
+            //tag::x-pack-ml-get-calendars-id
+            request.setCalendarId("holidays"); // <1>
+            //end::x-pack-ml-get-calendars-id
+
+            //tag::x-pack-ml-get-calendars-page
+            request.setPageParams(new PageParams(10, 20)); // <1>
+            //end::x-pack-ml-get-calendars-page
+
+            // reset page params
+            request.setPageParams(null);
+
+            //tag::x-pack-ml-get-calendars-execution
+            GetCalendarsResponse response = client.machineLearning().getCalendars(request, RequestOptions.DEFAULT);
+            //end::x-pack-ml-get-calendars-execution
+
+            // tag::x-pack-ml-get-calendars-response
+            long count = response.count(); // <1>
+            List<Calendar> calendars = response.calendars(); // <2>
+            // end::x-pack-ml-get-calendars-response
+            assertEquals(1, calendars.size());
+        }
+        {
+            GetCalendarsRequest request = new GetCalendarsRequest("holidays");
+
+            // tag::x-pack-ml-get-calendars-listener
+            ActionListener<GetCalendarsResponse> listener =
+                    new ActionListener<GetCalendarsResponse>() {
+                        @Override
+                        public void onResponse(GetCalendarsResponse getCalendarsResponse) {
+                            // <1>
+                        }
+
+                        @Override
+                        public void onFailure(Exception e) {
+                            // <2>
+                        }
+                    };
+            // end::x-pack-ml-get-calendars-listener
+
+            // Replace the empty listener by a blocking listener in test
+            final CountDownLatch latch = new CountDownLatch(1);
+            listener = new LatchedActionListener<>(listener, latch);
+
+            // tag::x-pack-ml-get-calendars-execute-async
+            client.machineLearning().getCalendarsAsync(request, RequestOptions.DEFAULT, listener); // <1>
+            // end::x-pack-ml-get-calendars-execute-async
+
+            assertTrue(latch.await(30L, TimeUnit.SECONDS));
+        }
+    }
 }

+ 46 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/ml/GetCalendarsRequestTests.java

@@ -0,0 +1,46 @@
+/*
+ * 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 org.elasticsearch.client.ml;
+
+import org.elasticsearch.client.ml.job.util.PageParams;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.test.AbstractXContentTestCase;
+
+public class GetCalendarsRequestTests extends AbstractXContentTestCase<GetCalendarsRequest> {
+
+    @Override
+    protected GetCalendarsRequest createTestInstance() {
+        GetCalendarsRequest request = new GetCalendarsRequest();
+        request.setCalendarId(randomAlphaOfLength(9));
+        if (randomBoolean()) {
+            request.setPageParams(new PageParams(1, 2));
+        }
+        return request;
+    }
+
+    @Override
+    protected GetCalendarsRequest doParseInstance(XContentParser parser) {
+        return GetCalendarsRequest.PARSER.apply(parser, null);
+    }
+
+    @Override
+    protected boolean supportsUnknownFields() {
+        return false;
+    }
+}

+ 52 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/ml/GetCalendarsResponseTests.java

@@ -0,0 +1,52 @@
+/*
+ * 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 org.elasticsearch.client.ml;
+
+import org.elasticsearch.client.ml.calendars.Calendar;
+import org.elasticsearch.client.ml.calendars.CalendarTests;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.test.AbstractXContentTestCase;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class GetCalendarsResponseTests extends AbstractXContentTestCase<GetCalendarsResponse> {
+
+    @Override
+    protected GetCalendarsResponse createTestInstance() {
+        List<Calendar> calendars = new ArrayList<>();
+        int count = randomIntBetween(0, 3);
+        for (int i=0; i<count; i++) {
+            calendars.add(CalendarTests.testInstance());
+        }
+        return new GetCalendarsResponse(calendars, count);
+    }
+
+    @Override
+    protected GetCalendarsResponse doParseInstance(XContentParser parser) throws IOException {
+        return GetCalendarsResponse.fromXContent(parser);
+    }
+
+    @Override
+    protected boolean supportsUnknownFields() {
+        return true;
+    }
+}

+ 83 - 0
docs/java-rest/high-level/ml/get-calendars.asciidoc

@@ -0,0 +1,83 @@
+[[java-rest-high-x-pack-ml-get-calendars]]
+=== Get Calendars API
+Retrieves one or more calendar objects.
+It accepts a `GetCalendarsRequest` and responds
+with a `GetCalendarsResponse` object.
+
+[[java-rest-high-x-pack-ml-get-calendars-request]]
+==== Get Calendars Request
+
+By default a `GetCalendarsRequest` with no calendar Id set will return all
+calendars. Using the literal `_all` also returns all calendars.
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/MlClientDocumentationIT.java[x-pack-ml-get-calendars-request]
+--------------------------------------------------
+<1> Constructing a new request for all calendars
+
+
+==== Optional Arguments
+The following arguments are optional:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/MlClientDocumentationIT.java[x-pack-ml-get-calendars-id]
+--------------------------------------------------
+<1> Construct a request for the single calendar `holidays`
+
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/MlClientDocumentationIT.java[x-pack-ml-get-calendars-page]
+--------------------------------------------------
+<1> The page parameters `from` and `size`. `from` specifies the number of calendars to skip.
+`size` specifies the maximum number of calendars to get. Defaults to `0` and `100` respectively.
+
+[[java-rest-high-x-pack-ml-get-calendars-execution]]
+==== Execution
+The request can be executed through the `MachineLearningClient` contained
+in the `RestHighLevelClient` object, accessed via the `machineLearningClient()` method.
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/MlClientDocumentationIT.java[x-pack-ml-get-calendars-execution]
+--------------------------------------------------
+
+[[java-rest-high-x-pack-ml-get-calendars-execution-async]]
+==== Asynchronous Execution
+
+The request can also be executed asynchronously:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/MlClientDocumentationIT.java[x-pack-ml-get-calendars-execute-async]
+--------------------------------------------------
+<1> The `GetCalendarsRequest` to execute and the `ActionListener` to use when
+the execution completes
+
+The asynchronous method does not block and returns immediately. Once it is
+completed the `ActionListener` is called back with the `onResponse` method
+if the execution is successful or the `onFailure` method if the execution
+failed.
+
+A typical listener for `GetCalendarsResponse` looks like:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/MlClientDocumentationIT.java[x-pack-ml-get-calendars-listener]
+--------------------------------------------------
+<1> `onResponse` is called back when the action is completed successfully
+<2> `onFailure` is called back when some unexpected error occurs
+
+[[java-rest-high-x-pack-ml-get-calendars-response]]
+==== Get calendars Response
+
+The returned `GetCalendarsResponse` contains the requested calendars:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/MlClientDocumentationIT.java[x-pack-ml-get-calendars-response]
+--------------------------------------------------
+<1> The count of calendars that were matched
+<2> The calendars retrieved

+ 1 - 1
docs/java-rest/high-level/ml/put-calendar.asciidoc

@@ -4,7 +4,7 @@ Creates a new {ml} calendar.
 The API accepts a `PutCalendarRequest` and responds
 with a `PutCalendarResponse` object.
 
-[[java-rest-high-x-pack-ml-get-calendars-request]]
+[[java-rest-high-x-pack-ml-put-calendar-request]]
 ==== Put Calendar Request
 
 A `PutCalendarRequest` is constructed with a Calendar object

+ 2 - 0
docs/java-rest/high-level/supported-apis.asciidoc

@@ -231,6 +231,7 @@ The Java High Level REST Client supports the following Machine Learning APIs:
 * <<java-rest-high-x-pack-ml-post-data>>
 * <<java-rest-high-x-pack-ml-get-influencers>>
 * <<java-rest-high-x-pack-ml-get-categories>>
+* <<java-rest-high-x-pack-ml-get-calendars>>
 * <<java-rest-high-x-pack-ml-put-calendar>>
 
 include::ml/put-job.asciidoc[]
@@ -252,6 +253,7 @@ include::ml/get-records.asciidoc[]
 include::ml/post-data.asciidoc[]
 include::ml/get-influencers.asciidoc[]
 include::ml/get-categories.asciidoc[]
+include::ml/get-calendars.asciidoc[]
 include::ml/put-calendar.asciidoc[]
 
 == Migration APIs