{"action":"created","comment":{"url":"https://api.github.com/repos/codice/ddf/pulls/comments/56086689","id":56086689,"diff_hunk":"@@ -0,0 +1,249 @@\n+/**\n+ * Copyright (c) Codice Foundation\n+ * <p>\n+ * This is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser\n+ * General Public License as published by the Free Software Foundation, either version 3 of the\n+ * License, or any later version.\n+ * <p>\n+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without\n+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n+ * Lesser General Public License for more details. A copy of the GNU Lesser General Public License\n+ * is distributed along with this program and can be found at\n+ * <http://www.gnu.org/licenses/lgpl.html>.\n+ */\n+package ddf.catalog.data.impl;\n+\n+import java.time.Instant;\n+import java.util.ArrayList;\n+import java.util.Collections;\n+import java.util.Date;\n+import java.util.HashMap;\n+import java.util.HashSet;\n+import java.util.Map;\n+import java.util.Set;\n+import java.util.UUID;\n+import java.util.stream.Collectors;\n+\n+import org.apache.shiro.SecurityUtils;\n+import org.apache.shiro.subject.Subject;\n+\n+import ddf.catalog.data.Attribute;\n+import ddf.catalog.data.AttributeDescriptor;\n+import ddf.catalog.data.Metacard;\n+import ddf.catalog.data.MetacardType;\n+import ddf.security.SubjectUtils;\n+\n+public class HistoryMetacardImpl extends MetacardImpl {\n+\n+    public enum Action {\n+        CREATED(\"Created\"),\n+        UPDATED(\"Updated\"),\n+        DELETED(\"Deleted\");\n+\n+        private static Map<String, Action> keyMap = new HashMap<>();\n+\n+        static {\n+            for (Action action : Action.values()) {\n+                keyMap.put(action.getKey(), action);\n+            }\n+        }\n+\n+        private String key;\n+\n+        Action(String key) {\n+            this.key = key;\n+        }\n+\n+        public String getKey() {\n+            return this.key;\n+        }\n+\n+        public static Action fromKey(String key) {\n+            return keyMap.get(key);\n+        }\n+    }\n+\n+    /**\n+     * {@link ddf.catalog.data.Attribute} value for {@link ddf.catalog.data.Metacard#TAGS} when\n+     * a metacard is a History Metacard.\n+     */\n+    public static final String HISTORY_TAG = \"history\";\n+\n+    /**\n+     * {@link ddf.catalog.data.Attribute} name for state of the current {@link Metacard}.\n+     * Can be one of <code>Active</code>, <code>Inactive</code>, or <code>Deleted</code>.\n+     * Default value is <code>Inactive</code>.\n+     *\n+     * @since DDF-2.9.0\n+     */\n+    public static final String STATE = \"state\";\n+\n+    /**\n+     * {@link ddf.catalog.data.Attribute} name for the editor of this {@link Metacard} revision.\n+     *\n+     * @since DDF-2.9.0\n+     */\n+    public static final String EDITED_BY = \"edited-by\";\n+\n+    /**\n+     * {@link ddf.catalog.data.Attribute} name for version date of this {@link Metacard} revision.\n+     *\n+     * @since DDF-2.9.0\n+     */\n+    public static final String VERSIONED = \"versioned\";\n+\n+    /**\n+     * {@link ddf.catalog.data.Attribute} name for metacard ID on a history item of this {@link Metacard} revision.\n+     *\n+     * @since DDF-2.9.0\n+     */\n+    public static final String ID_HISTORY = \"history-id\";\n+\n+    public static final String TAGS_HISTORY = \"history-tags\";\n+\n+    private static MetacardType versionHistoryMetacard;\n+\n+    public static final String METACARD_TYPE_NAME = \"ddf.history.metacard\";\n+\n+    static {\n+        HashSet<AttributeDescriptor> historyDescriptors =\n+                new HashSet<>(BasicTypes.BASIC_METACARD.getAttributeDescriptors());\n+        historyDescriptors.add(new AttributeDescriptorImpl(STATE,\n+                true /* indexed */,\n+                true /* stored */,\n+                false /* tokenized */,\n+                false /* multivalued */,\n+                BasicTypes.STRING_TYPE));\n+        historyDescriptors.add(new AttributeDescriptorImpl(EDITED_BY,\n+                true /* indexed */,\n+                true /* stored */,\n+                false /* tokenized */,\n+                false /* multivalued */,\n+                BasicTypes.STRING_TYPE));\n+        historyDescriptors.add(new AttributeDescriptorImpl(VERSIONED,\n+                true /* indexed */,\n+                true /* stored */,\n+                false /* tokenized */,\n+                false /* multivalued */,\n+                BasicTypes.DATE_TYPE));\n+        historyDescriptors.add(new AttributeDescriptorImpl(ID_HISTORY,\n+                true /* indexed */,\n+                true /* stored */,\n+                false /* tokenized */,\n+                false /* multivalued */,\n+                BasicTypes.STRING_TYPE));\n+        historyDescriptors.add(new AttributeDescriptorImpl(TAGS_HISTORY,\n+                true /* indexed */,\n+                true /* stored */,\n+                false /* tokenized */,\n+                true /* multivalued */,\n+                BasicTypes.STRING_TYPE));\n+\n+        versionHistoryMetacard = new MetacardTypeImpl(METACARD_TYPE_NAME, historyDescriptors);\n+    }\n+\n+    /**\n+     * Will convert the given {@link Metacard} to a {@link HistoryMetacardImpl} by cloning\n+     * it and adding the current subject, time, and a random UUID. Cannot take a\n+     * {@link HistoryMetacardImpl} as the sourceMetacard.\n+     *\n+     * @param sourceMetacard Metacard to clone and create a history item from\n+     * @param action         Which action was done to modify the metacard\n+     * @throws IllegalArgumentException\n+     */\n+    public HistoryMetacardImpl(Metacard sourceMetacard, Action action, Subject subject) {\n+        super(sourceMetacard, versionHistoryMetacard);\n+        if (sourceMetacard instanceof HistoryMetacardImpl) {\n+            throw new IllegalArgumentException(\n+                    \"Cannot create a history item from a history metacard.\");\n+        }\n+\n+        this.setState(action);\n+        this.setIdHistory(sourceMetacard.getId());\n+        this.setTagsHistory(sourceMetacard.getTags());\n+\n+        String editedBy = SubjectUtils.getEmailAddress(subject);\n+        if (editedBy == null || editedBy.equals(\"\")) {\n+            editedBy = SubjectUtils.getName(subject);\n+        }\n+        this.setEditedBy(editedBy);\n+\n+        this.setVersioned(Date.from(Instant.now()));\n+        this.setId(UUID.randomUUID()\n+                .toString()\n+                .replace(\"-\", \"\"));\n+        this.setTags(Collections.singleton(HISTORY_TAG));\n+    }\n+\n+    /**\n+     * Returns a {@link BasicTypes#BASIC_METACARD} version of the given\n+     * {@link HistoryMetacardImpl}\n+     *\n+     * @return The converted metacard\n+     * @throws IllegalStateException\n+     */\n+    public Metacard toBasicMetacard() {\n+        String id = this.getIdHistory();\n+        if (id == null || id.matches(\"\\\\s*\")) {","path":"catalog/core/catalog-core-api-impl/src/main/java/ddf/catalog/data/impl/HistoryMetacardImpl.java","position":187,"original_position":187,"commit_id":"e2c4e0d9a4eb238f65b97557b8b607812a3aa341","original_commit_id":"e2c4e0d9a4eb238f65b97557b8b607812a3aa341","user":{"login":"coyotesqrl","id":1469860,"avatar_url":"https://avatars.githubusercontent.com/u/1469860?v=3","gravatar_id":"","url":"https://api.github.com/users/coyotesqrl","html_url":"https://github.com/coyotesqrl","followers_url":"https://api.github.com/users/coyotesqrl/followers","following_url":"https://api.github.com/users/coyotesqrl/following{/other_user}","gists_url":"https://api.github.com/users/coyotesqrl/gists{/gist_id}","starred_url":"https://api.github.com/users/coyotesqrl/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/coyotesqrl/subscriptions","organizations_url":"https://api.github.com/users/coyotesqrl/orgs","repos_url":"https://api.github.com/users/coyotesqrl/repos","events_url":"https://api.github.com/users/coyotesqrl/events{/privacy}","received_events_url":"https://api.github.com/users/coyotesqrl/received_events","type":"User","site_admin":false},"body":"Apache's `StringUtils` specifically would be great for this as it returns true for nulls or any whitespace/empty strings.","created_at":"2016-03-14T22:22:27Z","updated_at":"2016-03-14T22:22:27Z","html_url":"https://github.com/codice/ddf/pull/613#discussion_r56086689","pull_request_url":"https://api.github.com/repos/codice/ddf/pulls/613","_links":{"self":{"href":"https://api.github.com/repos/codice/ddf/pulls/comments/56086689"},"html":{"href":"https://github.com/codice/ddf/pull/613#discussion_r56086689"},"pull_request":{"href":"https://api.github.com/repos/codice/ddf/pulls/613"}}},"pull_request":{"url":"https://api.github.com/repos/codice/ddf/pulls/613","id":62304824,"html_url":"https://github.com/codice/ddf/pull/613","diff_url":"https://github.com/codice/ddf/pull/613.diff","patch_url":"https://github.com/codice/ddf/pull/613.patch","issue_url":"https://api.github.com/repos/codice/ddf/issues/613","number":613,"state":"open","locked":false,"title":"DDF-1897 - Metacard Versioning","user":{"login":"rzwiefel","id":9013416,"avatar_url":"https://avatars.githubusercontent.com/u/9013416?v=3","gravatar_id":"","url":"https://api.github.com/users/rzwiefel","html_url":"https://github.com/rzwiefel","followers_url":"https://api.github.com/users/rzwiefel/followers","following_url":"https://api.github.com/users/rzwiefel/following{/other_user}","gists_url":"https://api.github.com/users/rzwiefel/gists{/gist_id}","starred_url":"https://api.github.com/users/rzwiefel/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rzwiefel/subscriptions","organizations_url":"https://api.github.com/users/rzwiefel/orgs","repos_url":"https://api.github.com/users/rzwiefel/repos","events_url":"https://api.github.com/users/rzwiefel/events{/privacy}","received_events_url":"https://api.github.com/users/rzwiefel/received_events","type":"User","site_admin":false},"body":"#### What does this PR do?\r\nAdds Support for Metacard Versioning\r\n#### Who is reviewing it?\r\n@jlcsmith @jrnorth @stustison @pklinef @kcwire @bdeining and if a fireman wants to.. @coyotesqrl \r\n#### How should this be tested?\r\nBuild and install, Enable Metacard Versioning with `feature:install catalog-versioning-plugin`, ingest a record, update a record, delete a record, confirm there is a history metacard created for each action.\r\n#### Any background context you want to provide?\r\nSupport added by use of a `PostIngestPlugin` and a new type of metacard (`HistoryMetacardImpl`)\r\n#### What are the relevant tickets?\r\n[DDF-1897](https://codice.atlassian.net/browse/DDF-1897)\r\n#### Checklist:\r\n- [ ] Documentation Updated\r\n- [x] Update / Add Unit Tests\r\n- [x] Update / Add Integration Tests","created_at":"2016-03-09T22:10:27Z","updated_at":"2016-03-14T22:22:27Z","closed_at":null,"merged_at":null,"merge_commit_sha":"106a07a27e32d7d1bd6934174b90696181db4208","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/codice/ddf/pulls/613/commits","review_comments_url":"https://api.github.com/repos/codice/ddf/pulls/613/comments","review_comment_url":"https://api.github.com/repos/codice/ddf/pulls/comments{/number}","comments_url":"https://api.github.com/repos/codice/ddf/issues/613/comments","statuses_url":"https://api.github.com/repos/codice/ddf/statuses/e2c4e0d9a4eb238f65b97557b8b607812a3aa341","head":{"label":"rzwiefel:DDF-1897-metacard-versioning","ref":"DDF-1897-metacard-versioning","sha":"e2c4e0d9a4eb238f65b97557b8b607812a3aa341","user":{"login":"rzwiefel","id":9013416,"avatar_url":"https://avatars.githubusercontent.com/u/9013416?v=3","gravatar_id":"","url":"https://api.github.com/users/rzwiefel","html_url":"https://github.com/rzwiefel","followers_url":"https://api.github.com/users/rzwiefel/followers","following_url":"https://api.github.com/users/rzwiefel/following{/other_user}","gists_url":"https://api.github.com/users/rzwiefel/gists{/gist_id}","starred_url":"https://api.github.com/users/rzwiefel/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rzwiefel/subscriptions","organizations_url":"https://api.github.com/users/rzwiefel/orgs","repos_url":"https://api.github.com/users/rzwiefel/repos","events_url":"https://api.github.com/users/rzwiefel/events{/privacy}","received_events_url":"https://api.github.com/users/rzwiefel/received_events","type":"User","site_admin":false},"repo":{"id":37286795,"name":"ddf","full_name":"rzwiefel/ddf","owner":{"login":"rzwiefel","id":9013416,"avatar_url":"https://avatars.githubusercontent.com/u/9013416?v=3","gravatar_id":"","url":"https://api.github.com/users/rzwiefel","html_url":"https://github.com/rzwiefel","followers_url":"https://api.github.com/users/rzwiefel/followers","following_url":"https://api.github.com/users/rzwiefel/following{/other_user}","gists_url":"https://api.github.com/users/rzwiefel/gists{/gist_id}","starred_url":"https://api.github.com/users/rzwiefel/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rzwiefel/subscriptions","organizations_url":"https://api.github.com/users/rzwiefel/orgs","repos_url":"https://api.github.com/users/rzwiefel/repos","events_url":"https://api.github.com/users/rzwiefel/events{/privacy}","received_events_url":"https://api.github.com/users/rzwiefel/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/rzwiefel/ddf","description":"The Distributed Data Framework - Distribution, SDK, and Test","fork":true,"url":"https://api.github.com/repos/rzwiefel/ddf","forks_url":"https://api.github.com/repos/rzwiefel/ddf/forks","keys_url":"https://api.github.com/repos/rzwiefel/ddf/keys{/key_id}","collaborators_url":"https://api.github.com/repos/rzwiefel/ddf/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/rzwiefel/ddf/teams","hooks_url":"https://api.github.com/repos/rzwiefel/ddf/hooks","issue_events_url":"https://api.github.com/repos/rzwiefel/ddf/issues/events{/number}","events_url":"https://api.github.com/repos/rzwiefel/ddf/events","assignees_url":"https://api.github.com/repos/rzwiefel/ddf/assignees{/user}","branches_url":"https://api.github.com/repos/rzwiefel/ddf/branches{/branch}","tags_url":"https://api.github.com/repos/rzwiefel/ddf/tags","blobs_url":"https://api.github.com/repos/rzwiefel/ddf/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/rzwiefel/ddf/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/rzwiefel/ddf/git/refs{/sha}","trees_url":"https://api.github.com/repos/rzwiefel/ddf/git/trees{/sha}","statuses_url":"https://api.github.com/repos/rzwiefel/ddf/statuses/{sha}","languages_url":"https://api.github.com/repos/rzwiefel/ddf/languages","stargazers_url":"https://api.github.com/repos/rzwiefel/ddf/stargazers","contributors_url":"https://api.github.com/repos/rzwiefel/ddf/contributors","subscribers_url":"https://api.github.com/repos/rzwiefel/ddf/subscribers","subscription_url":"https://api.github.com/repos/rzwiefel/ddf/subscription","commits_url":"https://api.github.com/repos/rzwiefel/ddf/commits{/sha}","git_commits_url":"https://api.github.com/repos/rzwiefel/ddf/git/commits{/sha}","comments_url":"https://api.github.com/repos/rzwiefel/ddf/comments{/number}","issue_comment_url":"https://api.github.com/repos/rzwiefel/ddf/issues/comments{/number}","contents_url":"https://api.github.com/repos/rzwiefel/ddf/contents/{+path}","compare_url":"https://api.github.com/repos/rzwiefel/ddf/compare/{base}...{head}","merges_url":"https://api.github.com/repos/rzwiefel/ddf/merges","archive_url":"https://api.github.com/repos/rzwiefel/ddf/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/rzwiefel/ddf/downloads","issues_url":"https://api.github.com/repos/rzwiefel/ddf/issues{/number}","pulls_url":"https://api.github.com/repos/rzwiefel/ddf/pulls{/number}","milestones_url":"https://api.github.com/repos/rzwiefel/ddf/milestones{/number}","notifications_url":"https://api.github.com/repos/rzwiefel/ddf/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/rzwiefel/ddf/labels{/name}","releases_url":"https://api.github.com/repos/rzwiefel/ddf/releases{/id}","deployments_url":"https://api.github.com/repos/rzwiefel/ddf/deployments","created_at":"2015-06-11T21:19:40Z","updated_at":"2016-02-01T21:59:06Z","pushed_at":"2016-03-14T19:47:43Z","git_url":"git://github.com/rzwiefel/ddf.git","ssh_url":"git@github.com:rzwiefel/ddf.git","clone_url":"https://github.com/rzwiefel/ddf.git","svn_url":"https://github.com/rzwiefel/ddf","homepage":"","size":137638,"stargazers_count":0,"watchers_count":0,"language":"Java","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"codice:master","ref":"master","sha":"21e6c2759604a31c8b469dce02b230f4c08b877b","user":{"login":"codice","id":2386734,"avatar_url":"https://avatars.githubusercontent.com/u/2386734?v=3","gravatar_id":"","url":"https://api.github.com/users/codice","html_url":"https://github.com/codice","followers_url":"https://api.github.com/users/codice/followers","following_url":"https://api.github.com/users/codice/following{/other_user}","gists_url":"https://api.github.com/users/codice/gists{/gist_id}","starred_url":"https://api.github.com/users/codice/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/codice/subscriptions","organizations_url":"https://api.github.com/users/codice/orgs","repos_url":"https://api.github.com/users/codice/repos","events_url":"https://api.github.com/users/codice/events{/privacy}","received_events_url":"https://api.github.com/users/codice/received_events","type":"Organization","site_admin":false},"repo":{"id":15173563,"name":"ddf","full_name":"codice/ddf","owner":{"login":"codice","id":2386734,"avatar_url":"https://avatars.githubusercontent.com/u/2386734?v=3","gravatar_id":"","url":"https://api.github.com/users/codice","html_url":"https://github.com/codice","followers_url":"https://api.github.com/users/codice/followers","following_url":"https://api.github.com/users/codice/following{/other_user}","gists_url":"https://api.github.com/users/codice/gists{/gist_id}","starred_url":"https://api.github.com/users/codice/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/codice/subscriptions","organizations_url":"https://api.github.com/users/codice/orgs","repos_url":"https://api.github.com/users/codice/repos","events_url":"https://api.github.com/users/codice/events{/privacy}","received_events_url":"https://api.github.com/users/codice/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/codice/ddf","description":"The Distributed Data Framework - Federated Open Geospatial Consortium (OGC) filter powered metadata catalog with SAML Web SSO secured container","fork":false,"url":"https://api.github.com/repos/codice/ddf","forks_url":"https://api.github.com/repos/codice/ddf/forks","keys_url":"https://api.github.com/repos/codice/ddf/keys{/key_id}","collaborators_url":"https://api.github.com/repos/codice/ddf/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/codice/ddf/teams","hooks_url":"https://api.github.com/repos/codice/ddf/hooks","issue_events_url":"https://api.github.com/repos/codice/ddf/issues/events{/number}","events_url":"https://api.github.com/repos/codice/ddf/events","assignees_url":"https://api.github.com/repos/codice/ddf/assignees{/user}","branches_url":"https://api.github.com/repos/codice/ddf/branches{/branch}","tags_url":"https://api.github.com/repos/codice/ddf/tags","blobs_url":"https://api.github.com/repos/codice/ddf/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/codice/ddf/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/codice/ddf/git/refs{/sha}","trees_url":"https://api.github.com/repos/codice/ddf/git/trees{/sha}","statuses_url":"https://api.github.com/repos/codice/ddf/statuses/{sha}","languages_url":"https://api.github.com/repos/codice/ddf/languages","stargazers_url":"https://api.github.com/repos/codice/ddf/stargazers","contributors_url":"https://api.github.com/repos/codice/ddf/contributors","subscribers_url":"https://api.github.com/repos/codice/ddf/subscribers","subscription_url":"https://api.github.com/repos/codice/ddf/subscription","commits_url":"https://api.github.com/repos/codice/ddf/commits{/sha}","git_commits_url":"https://api.github.com/repos/codice/ddf/git/commits{/sha}","comments_url":"https://api.github.com/repos/codice/ddf/comments{/number}","issue_comment_url":"https://api.github.com/repos/codice/ddf/issues/comments{/number}","contents_url":"https://api.github.com/repos/codice/ddf/contents/{+path}","compare_url":"https://api.github.com/repos/codice/ddf/compare/{base}...{head}","merges_url":"https://api.github.com/repos/codice/ddf/merges","archive_url":"https://api.github.com/repos/codice/ddf/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/codice/ddf/downloads","issues_url":"https://api.github.com/repos/codice/ddf/issues{/number}","pulls_url":"https://api.github.com/repos/codice/ddf/pulls{/number}","milestones_url":"https://api.github.com/repos/codice/ddf/milestones{/number}","notifications_url":"https://api.github.com/repos/codice/ddf/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/codice/ddf/labels{/name}","releases_url":"https://api.github.com/repos/codice/ddf/releases{/id}","deployments_url":"https://api.github.com/repos/codice/ddf/deployments","created_at":"2013-12-13T20:38:28Z","updated_at":"2016-03-06T12:38:48Z","pushed_at":"2016-03-14T21:53:15Z","git_url":"git://github.com/codice/ddf.git","ssh_url":"git@github.com:codice/ddf.git","clone_url":"https://github.com/codice/ddf.git","svn_url":"https://github.com/codice/ddf","homepage":"","size":145698,"stargazers_count":34,"watchers_count":34,"language":"Java","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":true,"forks_count":71,"mirror_url":null,"open_issues_count":15,"forks":71,"open_issues":15,"watchers":34,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/codice/ddf/pulls/613"},"html":{"href":"https://github.com/codice/ddf/pull/613"},"issue":{"href":"https://api.github.com/repos/codice/ddf/issues/613"},"comments":{"href":"https://api.github.com/repos/codice/ddf/issues/613/comments"},"review_comments":{"href":"https://api.github.com/repos/codice/ddf/pulls/613/comments"},"review_comment":{"href":"https://api.github.com/repos/codice/ddf/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/codice/ddf/pulls/613/commits"},"statuses":{"href":"https://api.github.com/repos/codice/ddf/statuses/e2c4e0d9a4eb238f65b97557b8b607812a3aa341"}}}}