Store JIRA status ID instead of status text (#156)

* Fixed issue when bot didn't detect branches due to translated JIRA issue status

* Added license headers
diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/visa/TcBotTriggerAndSignOffService.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/visa/TcBotTriggerAndSignOffService.java
index 4b77551..c97fd66 100644
--- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/visa/TcBotTriggerAndSignOffService.java
+++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcbot/visa/TcBotTriggerAndSignOffService.java
@@ -60,6 +60,7 @@
 import org.apache.ignite.internal.util.typedef.T2;
 import org.apache.ignite.jiraignited.IJiraIgnited;
 import org.apache.ignite.jiraignited.IJiraIgnitedProvider;
+import org.apache.ignite.jiraservice.JiraTicketStatusCode;
 import org.apache.ignite.jiraservice.Ticket;
 import org.apache.ignite.tcbot.common.conf.IGitHubConfig;
 import org.apache.ignite.tcbot.common.conf.IJiraServerConfig;
@@ -246,7 +247,7 @@
                 String muteTicket = mute.assignment.text.substring(pos + browseUrl.length());
 
                 if (ticket.key.equals(muteTicket)) {
-                    mute.ticketStatus = ticket.status();
+                    mute.ticketStatus = JiraTicketStatusCode.text(ticket.status());
 
                     break;
                 }
@@ -444,7 +445,7 @@
                 }
 
                 c.jiraIssueId = ticket == null ? null : ticket.key;
-                c.jiraStatusName = ticket == null ? null : ticket.status();
+                c.jiraStatusName = ticket == null ? null : JiraTicketStatusCode.text(ticket.status());
 
                 if (!Strings.isNullOrEmpty(c.jiraIssueId)
                         && jiraCfg.getUrl() != null)
@@ -462,7 +463,9 @@
 
         List<String> branches = gitHubConnIgnited.getBranches();
 
-        List<Ticket> activeTickets = tickets.stream().filter(Ticket::isActiveContribution).collect(Collectors.toList());
+        List<Ticket> activeTickets = tickets.stream()
+            .filter(ticket -> JiraTicketStatusCode.isActiveContribution(ticket.status()))
+            .collect(Collectors.toList());
 
         activeTickets.forEach(ticket -> {
             String branch = ticketMatcher.resolveTcBranchForPrLess(ticket,
@@ -480,7 +483,7 @@
             ContributionToCheck contribution = new ContributionToCheck();
 
             contribution.jiraIssueId = ticket.key;
-            contribution.jiraStatusName = ticket.status();
+            contribution.jiraStatusName = JiraTicketStatusCode.text(ticket.status());
             contribution.jiraIssueUrl = jiraIntegration.generateTicketUrl(ticket.key);
             contribution.tcBranchName = branch;
 
diff --git a/tcbot-jira-ignited/src/main/java/org/apache/ignite/ci/jira/ignited/TicketCompacted.java b/tcbot-jira-ignited/src/main/java/org/apache/ignite/ci/jira/ignited/TicketCompacted.java
index c11c8b6..4c62717 100644
--- a/tcbot-jira-ignited/src/main/java/org/apache/ignite/ci/jira/ignited/TicketCompacted.java
+++ b/tcbot-jira-ignited/src/main/java/org/apache/ignite/ci/jira/ignited/TicketCompacted.java
@@ -17,15 +17,15 @@
 
 package org.apache.ignite.ci.jira.ignited;
 
-import java.util.Objects;
+import org.apache.ignite.ci.tcbot.common.StringFieldCompacted;
 import org.apache.ignite.jiraservice.Fields;
 import org.apache.ignite.jiraservice.Status;
 import org.apache.ignite.jiraservice.Ticket;
-import org.apache.ignite.ci.tcbot.common.StringFieldCompacted;
 import org.apache.ignite.tcbot.persistence.IStringCompactor;
 import org.apache.ignite.tcbot.persistence.Persisted;
 
 import javax.annotation.Nullable;
+import java.util.Objects;
 
 /**
  *
@@ -38,8 +38,12 @@
     /** Ticket number, integer value like 123 from name like "IGNITE-123". */
     public int igniteId;
 
-    /** Id of string: Fields/status/name, value compacted. */
-    public int status;
+    /**
+     * Id of string: Fields/status/name, value compacted.
+     * @deprecated {@link #statusCodeId} is used for storing status now,
+     * field is kept to prevent accidental binary schema compatibility breaking in the future.
+     */
+    @Deprecated public int status;
 
     /** Summary, nullable because of older entries. */
     @Nullable private StringFieldCompacted summary = new StringFieldCompacted();
@@ -50,6 +54,9 @@
     /** Full description, nullable because of older entry versions. */
     @Nullable private StringFieldCompacted description = new StringFieldCompacted();
 
+    /** Internal JIRA id of ticket status, see {@link org.apache.ignite.jiraservice.JiraTicketStatusCode}. */
+    public int statusCodeId;
+
     /**
      * @param ticket Jira ticket.
      * @param comp Compactor.
@@ -58,7 +65,7 @@
     public TicketCompacted(Ticket ticket, IStringCompactor comp, String projectCode) {
         id = ticket.id;
         igniteId = ticket.keyWithoutProject(projectCode);
-        status = comp.getStringId(ticket.fields.status.name);
+        statusCodeId = ticket.fields.status.id;
         summary.setValue(ticket.fields.summary);
         customfield_11050.setValue(ticket.fields.customfield_11050);
         description.setValue(ticket.fields.description);
@@ -74,7 +81,7 @@
         ticket.id = id;
         ticket.key = projectCode + Ticket.PROJECT_DELIM + igniteId;
         ticket.fields = new Fields();
-        ticket.fields.status = new Status(comp.getStringFromId(status));
+        ticket.fields.status = new Status(statusCodeId);
         ticket.fields.summary = summary != null ? summary.getValue() : null;
         ticket.fields.customfield_11050 = customfield_11050 != null ? customfield_11050.getValue() : null;
         ticket.fields.description = description != null ? description.getValue() : null;
@@ -94,7 +101,7 @@
 
         return id == compacted.id &&
             igniteId == compacted.igniteId &&
-            status == compacted.status &&
+            statusCodeId == compacted.statusCodeId &&
             Objects.equals(summary, compacted.summary) &&
             Objects.equals(customfield_11050, compacted.customfield_11050) &&
             Objects.equals(description, compacted.description);
@@ -102,6 +109,6 @@
 
     /** {@inheritDoc} */
     @Override public int hashCode() {
-        return Objects.hash(id, igniteId, status, summary, customfield_11050, description);
+        return Objects.hash(id, igniteId, statusCodeId, summary, customfield_11050, description);
     }
 }
diff --git a/tcbot-jira/src/main/java/org/apache/ignite/jiraservice/Jira.java b/tcbot-jira/src/main/java/org/apache/ignite/jiraservice/Jira.java
index b2d4943..968a5be 100644
--- a/tcbot-jira/src/main/java/org/apache/ignite/jiraservice/Jira.java
+++ b/tcbot-jira/src/main/java/org/apache/ignite/jiraservice/Jira.java
@@ -19,6 +19,7 @@
 
 import com.google.common.base.Preconditions;
 import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
 import org.apache.ignite.tcbot.common.conf.IDataSourcesConfigSupplier;
 import org.apache.ignite.tcbot.common.interceptor.AutoProfiling;
 import org.apache.ignite.tcbot.common.conf.IJiraServerConfig;
@@ -53,7 +54,9 @@
     /** {@inheritDoc} */
     @Override public Tickets getTicketsPage(String url) {
         try {
-            return new Gson().fromJson(sendGetToJira(url), Tickets.class);
+            Gson gson = new GsonBuilder().registerTypeAdapterFactory(new ReadResolveFactory()).create();
+
+            return gson.fromJson(sendGetToJira(url), Tickets.class);
         }
         catch (Exception e) {
             String errMsg = "Exception happened during receiving JIRA tickets " +
diff --git a/tcbot-jira/src/main/java/org/apache/ignite/jiraservice/JiraTicketStatusCode.java b/tcbot-jira/src/main/java/org/apache/ignite/jiraservice/JiraTicketStatusCode.java
new file mode 100644
index 0000000..da0977f
--- /dev/null
+++ b/tcbot-jira/src/main/java/org/apache/ignite/jiraservice/JiraTicketStatusCode.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ignite.jiraservice;
+
+/**
+ * Enumerates well-known JIRA issue statuses and its internal IDs.
+ */
+public enum JiraTicketStatusCode {
+    OPEN(1, "Open"),
+    IN_PROGRESS(3, "In Progress"),
+    REOPENED(4, "Reopened"),
+    RESOLVED(5, "Resolved"),
+    CLOSED(6, "Closed"),
+    BACKLOG(10010, "Backlog"),
+    PATCH_AVAILABLE(10012, "Patch Available"),
+    PENDING(10402, "Pending"),
+    PATCH_REVIEWED(10800, "Patch Reviewed");
+
+    JiraTicketStatusCode(int id, String name) {
+       this.id = id;
+       this.name = name;
+    }
+
+    private final int id;
+
+    private final String name;
+
+    public int getId() {
+        return id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * @param id Status ID.
+     * @return Resolved status or <code>null</code> if status not found.
+     */
+    public static JiraTicketStatusCode fromId(int id) {
+        for (JiraTicketStatusCode code : values()) {
+            if (code.id == id)
+                return code;
+        }
+
+        return null;
+    }
+
+    /**
+     * @return Text representation of given status code.
+     */
+    public static String text(JiraTicketStatusCode statusCode) {
+        if (statusCode == null)
+            return "<unknown>";
+
+        return statusCode.getName();
+    }
+
+    /**
+     * Checks if ticket relates to some Active (In progress/Patch Available) Contribution.
+     */
+    public static boolean isActiveContribution(JiraTicketStatusCode statusCode) {
+        if (statusCode == null)
+            return false;
+
+        return JiraTicketStatusCode.PATCH_AVAILABLE == statusCode ||
+            JiraTicketStatusCode.IN_PROGRESS == statusCode ||
+            JiraTicketStatusCode.OPEN == statusCode ||
+            JiraTicketStatusCode.BACKLOG == statusCode ||
+            JiraTicketStatusCode.REOPENED == statusCode ||
+            JiraTicketStatusCode.PATCH_REVIEWED == statusCode;
+    }
+}
diff --git a/tcbot-jira/src/main/java/org/apache/ignite/jiraservice/ReadResolveFactory.java b/tcbot-jira/src/main/java/org/apache/ignite/jiraservice/ReadResolveFactory.java
new file mode 100644
index 0000000..441c3c1
--- /dev/null
+++ b/tcbot-jira/src/main/java/org/apache/ignite/jiraservice/ReadResolveFactory.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ignite.jiraservice;
+
+import com.google.gson.Gson;
+import com.google.gson.TypeAdapter;
+import com.google.gson.TypeAdapterFactory;
+import com.google.gson.reflect.TypeToken;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+
+import java.io.IOException;
+
+/**
+ * Type adapter factory which is intended to trigger readResolve() after Gson deserialization.
+ */
+public class ReadResolveFactory implements TypeAdapterFactory {
+    @Override
+    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
+        final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
+
+        return new TypeAdapter<T>() {
+            public void write(JsonWriter out, T value) throws IOException {
+                delegate.write(out, value);
+            }
+
+            public T read(JsonReader in) throws IOException {
+                T obj = delegate.read(in);
+
+                if (obj instanceof Status)
+                    ((Status)obj).readResolve();
+                // Add more classes for post-processing if necessary.
+
+                return obj;
+            }
+        };
+    }
+}
diff --git a/tcbot-jira/src/main/java/org/apache/ignite/jiraservice/Status.java b/tcbot-jira/src/main/java/org/apache/ignite/jiraservice/Status.java
index 3cc37e9..3ddc43e 100644
--- a/tcbot-jira/src/main/java/org/apache/ignite/jiraservice/Status.java
+++ b/tcbot-jira/src/main/java/org/apache/ignite/jiraservice/Status.java
@@ -17,39 +17,44 @@
 
 package org.apache.ignite.jiraservice;
 
+import java.io.ObjectStreamException;
+
 /**
  * Status for Jira ticket.
- *
- * "Closed" "Open" "Backlog" "Resolved" "In Progress" "Pending" "Patch Available" "Reopened" "Patch Reviewed"
  */
 public class Status {
-    /** Open status name. */
-    public static final String OPEN_NAME = "Open";
-
-    /** Reopened status name. */
-    public static final String REOPENED_NAME = "Reopened";
-
-    /** Backlog status name. */
-    public static final String BACKLOG_NAME = "Backlog";
-
-    /** Patch Available status name. */
-    public static final String PA_NAME = "Patch Available";
-
-    /** In Progress status name. */
-    public static final String IP_NAME = "In Progress";
-
     /** Status text (open, resolved, etc). */
     public String name;
 
+    /** Internal JIRA status ID. */
+    public int id;
+
+    /** Resolved well-known status code. */
+    public JiraTicketStatusCode statusCode;
+
     /**
-     * @param name Name.
+     * @param id JIRA status ID.
      */
-    public Status(String name) {
-        this.name = name;
+    public Status(int id) {
+        this.id = id;
+
+        try {
+            readResolve();
+        } catch (ObjectStreamException e) {
+            // Should be never thrown.
+        }
     }
 
-    /** {@inheritDoc} */
-    @Override public String toString() {
-        return name;
+    /**
+     * Reconstructs object on unmarshalling.
+     *
+     * @return Reconstructed object.
+     * @throws ObjectStreamException Thrown in case of unmarshalling error.
+     */
+    protected Object readResolve() throws ObjectStreamException {
+        statusCode = JiraTicketStatusCode.fromId(id);
+        name = JiraTicketStatusCode.text(statusCode);
+
+        return this;
     }
 }
\ No newline at end of file
diff --git a/tcbot-jira/src/main/java/org/apache/ignite/jiraservice/Ticket.java b/tcbot-jira/src/main/java/org/apache/ignite/jiraservice/Ticket.java
index 2221c6c..244ddc8 100644
--- a/tcbot-jira/src/main/java/org/apache/ignite/jiraservice/Ticket.java
+++ b/tcbot-jira/src/main/java/org/apache/ignite/jiraservice/Ticket.java
@@ -56,11 +56,11 @@
      * @return Ticket status (open, resolved, etc);
      */
     @Nullable
-    public String status() {
+    public JiraTicketStatusCode status() {
         if (fields == null || fields.status == null)
             return null;
 
-        return fields.status.name;
+        return fields.status.statusCode;
     }
 
     /** {@inheritDoc} */
@@ -71,17 +71,4 @@
             .add("fields", fields)
             .toString();
     }
-
-    /**
-     * Checks if ticket relates to some Active (In progress/Patch Available) Contribution.
-     */
-    public boolean isActiveContribution() {
-        String status = status();
-
-        return Status.PA_NAME.equals(status)
-            || Status.IP_NAME.equals(status)
-            || Status.OPEN_NAME.equals(status)
-            || Status.BACKLOG_NAME.equals(status)
-            || Status.REOPENED_NAME.equals(status);
-    }
 }
\ No newline at end of file