{"action":"created","comment":{"url":"https://api.github.com/repos/caskdata/cdap/pulls/comments/24476224","id":24476224,"diff_hunk":"@@ -0,0 +1,466 @@\n+/*\n+ * Copyright © 2015 Cask Data, Inc.\n+ *\n+ * Licensed under the Apache License, Version 2.0 (the \"License\"); you may not\n+ * use this file except in compliance with the License. You may obtain a copy of\n+ * the License at\n+ *\n+ * http://www.apache.org/licenses/LICENSE-2.0\n+ *\n+ * Unless required by applicable law or agreed to in writing, software\n+ * distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n+ * License for the specific language governing permissions and limitations under\n+ * the License.\n+ */\n+\n+package co.cask.cdap.data2.dataset2.lib.partitioned;\n+\n+import co.cask.cdap.api.common.Bytes;\n+import co.cask.cdap.api.dataset.DataSetException;\n+import co.cask.cdap.api.dataset.DatasetSpecification;\n+import co.cask.cdap.api.dataset.lib.AbstractDataset;\n+import co.cask.cdap.api.dataset.lib.FileSet;\n+import co.cask.cdap.api.dataset.lib.FileSetProperties;\n+import co.cask.cdap.api.dataset.lib.PartitionFilter;\n+import co.cask.cdap.api.dataset.lib.PartitionKey;\n+import co.cask.cdap.api.dataset.lib.PartitionedFileSet;\n+import co.cask.cdap.api.dataset.lib.PartitionedFileSetArguments;\n+import co.cask.cdap.api.dataset.lib.Partitioning;\n+import co.cask.cdap.api.dataset.lib.Partitioning.FieldType;\n+import co.cask.cdap.api.dataset.lib.TimePartitionedFileSetArguments;\n+import co.cask.cdap.api.dataset.table.Put;\n+import co.cask.cdap.api.dataset.table.Row;\n+import co.cask.cdap.api.dataset.table.Scanner;\n+import co.cask.cdap.api.dataset.table.Table;\n+import co.cask.cdap.explore.client.ExploreFacade;\n+import com.google.common.annotations.VisibleForTesting;\n+import com.google.common.collect.ImmutableMap;\n+import com.google.common.collect.Lists;\n+import com.google.common.collect.Maps;\n+import com.google.common.collect.Sets;\n+import com.google.inject.Provider;\n+import org.apache.twill.filesystem.Location;\n+import org.slf4j.Logger;\n+import org.slf4j.LoggerFactory;\n+\n+import java.io.IOException;\n+import java.util.ArrayList;\n+import java.util.Collection;\n+import java.util.List;\n+import java.util.Map;\n+import java.util.Set;\n+import javax.annotation.Nullable;\n+\n+/**\n+ * Implementation of partitioned datasets using a Table to store the meta data.\n+ */\n+public class PartitionedFileSetDataset extends AbstractDataset implements PartitionedFileSet {\n+\n+  private static final Logger LOG = LoggerFactory.getLogger(PartitionedFileSetDataset.class);\n+\n+  private static final byte[] RELATIVE_PATH = { 'p' };\n+  private static final byte[] FIELD_PREFIX = { 'f', '.' };\n+\n+  private final FileSet files;\n+  private final Table partitions;\n+  private final Map<String, String> runtimeArguments;\n+  private final DatasetSpecification spec;\n+  private final Provider<ExploreFacade> exploreFacadeProvider;\n+  private final Partitioning partitioning;\n+\n+  public PartitionedFileSetDataset(String name, Partitioning partitioning,\n+                                   FileSet fileSet, Table partitionTable,\n+                                   DatasetSpecification spec, Map<String, String> arguments,\n+                                   Provider<ExploreFacade> exploreFacadeProvider) {\n+    super(name, partitionTable);\n+    this.files = fileSet;\n+    this.partitions = partitionTable;\n+    this.spec = spec;\n+    this.exploreFacadeProvider = exploreFacadeProvider;\n+    this.runtimeArguments = arguments;\n+    this.partitioning = partitioning;\n+  }\n+\n+  @Override\n+  public Partitioning getPartitioning() {\n+    return partitioning;\n+  }\n+\n+  @Override\n+  public void addPartition(PartitionKey key, String path) {\n+    final byte[] rowKey = generateRowKey(key, partitioning);\n+    Row row = partitions.get(rowKey);\n+    if (row != null && !row.isEmpty()) {\n+      throw new DataSetException(String.format(\"Dataset '%s' already has a partition with the same key: %s\",\n+                                               getName(), key.toString()));\n+    }\n+    Put put = new Put(rowKey);\n+    put.add(RELATIVE_PATH, Bytes.toBytes(path));\n+    for (Map.Entry<String, ? extends Comparable> entry : key.getFields().entrySet()) {\n+      put.add(Bytes.add(FIELD_PREFIX, Bytes.toBytes(entry.getKey())), // \"f.<field name>\"\n+              Bytes.toBytes(entry.getValue().toString()));            // \"<string rep. of value>\"\n+    }\n+    partitions.put(put);\n+\n+    if (FileSetProperties.isExploreEnabled(spec.getProperties())) {\n+      ExploreFacade exploreFacade = exploreFacadeProvider.get();\n+      if (exploreFacade != null) {\n+        try {\n+          // TODO add partition in Hive (next PR)\n+          // exploreFacade.addPartition(getName(), key, files.getLocation(path).toURI().getPath());\n+        } catch (Exception e) {\n+          throw new DataSetException(String.format(\n+            \"Unable to add partition for time %s with path %s to explore table.\", key.toString(), path), e);\n+        }\n+      }\n+    }\n+  }\n+\n+  @Override\n+  public void dropPartition(PartitionKey key) {\n+    final byte[] rowKey = generateRowKey(key, partitioning);\n+    partitions.delete(rowKey);\n+\n+    if (FileSetProperties.isExploreEnabled(spec.getProperties())) {\n+      ExploreFacade exploreFacade = exploreFacadeProvider.get();\n+      if (exploreFacade != null) {\n+        try {\n+          // TODO drop partition in Hive (next PR)\n+          // exploreFacade.dropPartition(getName(), key);\n+        } catch (Exception e) {\n+          throw new DataSetException(String.format(\n+            \"Unable to drop partition for time %s from explore table.\", key.toString()), e);\n+        }\n+      }\n+    }\n+  }\n+\n+  @Override\n+  public String getPartition(PartitionKey key) {\n+    final byte[] rowKey = generateRowKey(key, partitioning);\n+    Row row = partitions.get(rowKey);\n+    if (row == null) {\n+      return null;\n+    }\n+    byte[] pathBytes = row.get(RELATIVE_PATH);\n+    if (pathBytes == null) {\n+      return null;\n+    }\n+    return Bytes.toString(pathBytes);\n+  }\n+\n+  @Override\n+  public Set<String> getPartitionPaths(@Nullable PartitionFilter filter) {\n+    // this is the same as getPartitions(startTime, endTime).values(), but we want to avoid construction of the map\n+    final byte[] startKey = generateStartKey(filter);\n+    final byte[] endKey = generateStopKey(filter);\n+    Set<String> paths = Sets.newHashSet();\n+    Scanner scanner = partitions.scan(startKey, endKey);\n+    try {\n+      while (true) {\n+        Row row = scanner.next();\n+        if (row == null) {\n+          break;\n+        }\n+        if (filter != null && !validateFilter(filter, row)) {\n+          continue;\n+        }\n+        byte[] pathBytes = row.get(RELATIVE_PATH);\n+        if (pathBytes != null) {\n+          paths.add(Bytes.toString(pathBytes));\n+        }\n+      }\n+      return paths;\n+    } finally {\n+      scanner.close();\n+    }\n+  }\n+\n+  @Override\n+  public Map<PartitionKey, String> getPartitions(@Nullable PartitionFilter filter) {\n+    final byte[] startKey = generateStartKey(filter);\n+    final byte[] endKey = generateStopKey(filter);\n+    Map<PartitionKey, String> paths = Maps.newHashMap();\n+    Scanner scanner = partitions.scan(startKey, endKey);\n+    try {\n+      while (true) {\n+        Row row = scanner.next();\n+        if (row == null) {\n+          break;\n+        }\n+        if (filter != null && !validateFilter(filter, row)) {\n+          continue;\n+        }\n+        PartitionKey key = parseRowKey(row.getRow(), partitioning);\n+        byte[] pathBytes = row.get(RELATIVE_PATH);\n+        if (pathBytes != null) {\n+          paths.put(key, Bytes.toString(pathBytes));\n+        }\n+      }\n+      return paths;\n+    } finally {\n+      scanner.close();\n+    }\n+  }\n+\n+  @Override\n+  public void close() throws IOException {\n+    try {\n+      files.close();\n+    } finally {\n+      partitions.close();\n+    }\n+  }\n+\n+  @Override\n+  public <T> Class<? extends T> getInputFormatClass() {\n+    return files.getInputFormatClass();\n+  }\n+\n+  @Override\n+  public Map<String, String> getInputFormatConfiguration() {\n+    PartitionFilter filter;\n+    try {\n+      filter = PartitionedFileSetArguments.getInputPartitionFilter(runtimeArguments, partitioning);\n+    } catch (Exception e) {\n+      throw new DataSetException(\"Partition filter must be correctly specified in arguments.\");\n+    }\n+    Collection<String> inputPaths = getPartitionPaths(filter);\n+    List<Location> inputLocations = Lists.newArrayListWithExpectedSize(inputPaths.size());\n+    for (String path : inputPaths) {\n+      inputLocations.add(files.getLocation(path));\n+    }\n+    return files.getInputFormatConfiguration(inputLocations);\n+  }\n+\n+  @Override\n+  public <T> Class<? extends T> getOutputFormatClass() {\n+    // we verify that the output partition time is configured in getOutputFormatConfiguration()\n+    // todo use a wrapper that adds the new partition when the job is committed - that means we must serialize the\n+    //      cconf into the hadoop conf to be able to instantiate this dataset in the output committer\n+    return files.getOutputFormatClass();\n+  }\n+\n+  @Override\n+  public Map<String, String> getOutputFormatConfiguration() {\n+    // all runtime arguments are passed on to the file set, so we can expect the partition time in the file set's\n+    // output format configuration. If it is not there, the output format will fail to register this partition.\n+    Map<String, String> config = files.getOutputFormatConfiguration();\n+    Long time = TimePartitionedFileSetArguments.getOutputPartitionTime(runtimeArguments);\n+    if (time == null) {\n+      throw new DataSetException(\"Time must be given for the new output partition as a runtime argument.\");\n+    }\n+    // add the output partition time to the output arguments of the embedded file set\n+    Map<String, String> outputArgs = Maps.newHashMap();\n+    outputArgs.putAll(config);\n+    TimePartitionedFileSetArguments.setOutputPartitionTime(outputArgs, time);\n+    return ImmutableMap.copyOf(outputArgs);\n+  }\n+\n+  @Override\n+  public FileSet getEmbeddedFileSet() {\n+    return files;\n+  }\n+\n+  @Override\n+  public Map<String, String> getRuntimeArguments() {\n+    return runtimeArguments;\n+  }\n+\n+  //------ private helpers below here --------------------------------------------------------------\n+\n+  @VisibleForTesting\n+  static byte[] generateRowKey(PartitionKey key, Partitioning partitioning) {\n+    // validate partition key, convert values, and compute size of output\n+    Map<String, FieldType> partitionFields = partitioning.getFields();\n+    int totalSize = partitionFields.size() - 1; // one \\0 between each of the fields\n+    ArrayList<byte[]> values = Lists.newArrayListWithCapacity(partitionFields.size());\n+    for (Map.Entry<String, FieldType> entry : partitionFields.entrySet()) {\n+      String fieldName = entry.getKey();\n+      FieldType fieldType = entry.getValue();\n+      Comparable fieldValue = key.getField(fieldName);\n+      if (fieldValue == null) {\n+        throw new IllegalArgumentException(\n+          String.format(\"Incomplete partition key: value for field '%s' is missing\", fieldName));\n+      }\n+      if (!FieldTypes.validateType(fieldValue, fieldType)) {\n+        throw new IllegalArgumentException(\n+          String.format(\"Invalid partition key: value for %s field '%s' has incompatible type %s\",\n+                        fieldType.name(), fieldName, fieldValue.getClass().getName()));\n+      }\n+      byte[] bytes = FieldTypes.toBytes(fieldValue, fieldType);\n+      totalSize += bytes.length;\n+      values.add(bytes);\n+    }\n+    byte[] rowKey = new byte[totalSize];\n+    int offset = 0;\n+    for (byte[] bytes : values) {\n+      System.arraycopy(bytes, 0, rowKey, offset, bytes.length);\n+      offset += bytes.length;\n+      if (offset < rowKey.length) {\n+        rowKey[offset] = 0;\n+        offset++;\n+      }\n+    }\n+    return rowKey;\n+  }\n+\n+  private byte[] generateStartKey(PartitionFilter filter) {\n+    if (null == filter) {\n+      return null;\n+    }\n+    // validate partition filter, convert values, and compute size of output\n+    Map<String, FieldType> partitionFields = partitioning.getFields();\n+    int totalSize = 0;\n+    ArrayList<byte[]> values = Lists.newArrayListWithCapacity(partitionFields.size());\n+    for (Map.Entry<String, FieldType> entry : partitionFields.entrySet()) {\n+      String fieldName = entry.getKey();\n+      FieldType fieldType = entry.getValue();\n+      PartitionFilter.Condition<? extends Comparable> condition = filter.getCondition(fieldName);\n+      if (condition == null) {\n+        break; // this field is not present; we can't include any more fields in the start key\n+      }\n+      Comparable lowerValue = condition.getLower();\n+      if (lowerValue == null) {\n+        break; // this field has no lower bound; we can't include any more fields in the start key\n+      }\n+      if (!FieldTypes.validateType(lowerValue, fieldType)) {\n+        throw new IllegalArgumentException(\n+          String.format(\"Invalid partition filter: lower bound for %s field '%s' has incompatible type %s\",\n+                        fieldType.name(), fieldName, lowerValue.getClass().getName()));\n+      }\n+      byte[] bytes = FieldTypes.toBytes(lowerValue, fieldType);\n+      totalSize += bytes.length;\n+      values.add(bytes);\n+    }\n+    if (values.isEmpty()) {\n+      return null;\n+    }\n+    totalSize += values.size() - 1; // one \\0 between each of the fields\n+    byte[] startKey = new byte[totalSize];\n+    int offset = 0;\n+    for (byte[] bytes : values) {\n+      System.arraycopy(bytes, 0, startKey, offset, bytes.length);\n+      offset += bytes.length;\n+      if (offset < startKey.length) {","path":"cdap-data-fabric/src/main/java/co/cask/cdap/data2/dataset2/lib/partitioned/PartitionedFileSetDataset.java","position":346,"original_position":346,"commit_id":"bb14073145eb21196a10aea48eb72e0aa5a233e9","original_commit_id":"bb14073145eb21196a10aea48eb72e0aa5a233e9","user":{"login":"anew","id":483004,"avatar_url":"https://avatars.githubusercontent.com/u/483004?v=3","gravatar_id":"","url":"https://api.github.com/users/anew","html_url":"https://github.com/anew","followers_url":"https://api.github.com/users/anew/followers","following_url":"https://api.github.com/users/anew/following{/other_user}","gists_url":"https://api.github.com/users/anew/gists{/gist_id}","starred_url":"https://api.github.com/users/anew/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/anew/subscriptions","organizations_url":"https://api.github.com/users/anew/orgs","repos_url":"https://api.github.com/users/anew/repos","events_url":"https://api.github.com/users/anew/events{/privacy}","received_events_url":"https://api.github.com/users/anew/received_events","type":"User","site_admin":false},"body":"yep. And also in genStopKey(). Done","created_at":"2015-02-11T06:20:16Z","updated_at":"2015-02-11T06:20:16Z","html_url":"https://github.com/caskdata/cdap/pull/1259#discussion_r24476224","pull_request_url":"https://api.github.com/repos/caskdata/cdap/pulls/1259","_links":{"self":{"href":"https://api.github.com/repos/caskdata/cdap/pulls/comments/24476224"},"html":{"href":"https://github.com/caskdata/cdap/pull/1259#discussion_r24476224"},"pull_request":{"href":"https://api.github.com/repos/caskdata/cdap/pulls/1259"}}},"pull_request":{"url":"https://api.github.com/repos/caskdata/cdap/pulls/1259","id":28968250,"html_url":"https://github.com/caskdata/cdap/pull/1259","diff_url":"https://github.com/caskdata/cdap/pull/1259.diff","patch_url":"https://github.com/caskdata/cdap/pull/1259.patch","issue_url":"https://api.github.com/repos/caskdata/cdap/issues/1259","number":1259,"state":"open","locked":false,"title":"Feature/cdap 939 partitioned impl","user":{"login":"anew","id":483004,"avatar_url":"https://avatars.githubusercontent.com/u/483004?v=3","gravatar_id":"","url":"https://api.github.com/users/anew","html_url":"https://github.com/anew","followers_url":"https://api.github.com/users/anew/followers","following_url":"https://api.github.com/users/anew/following{/other_user}","gists_url":"https://api.github.com/users/anew/gists{/gist_id}","starred_url":"https://api.github.com/users/anew/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/anew/subscriptions","organizations_url":"https://api.github.com/users/anew/orgs","repos_url":"https://api.github.com/users/anew/repos","events_url":"https://api.github.com/users/anew/events{/privacy}","received_events_url":"https://api.github.com/users/anew/received_events","type":"User","site_admin":false},"body":"Adds an implementation for PartitionedFileSet, and a unit test.\r\nThe unit test may run long because it generates a large list of possible partition filters to test, it is there marked as a slow test.","created_at":"2015-02-09T23:47:38Z","updated_at":"2015-02-11T06:20:16Z","closed_at":null,"merged_at":null,"merge_commit_sha":"f5756ebedf44eeb32d1a19f63577a9d7da7283ca","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/caskdata/cdap/pulls/1259/commits","review_comments_url":"https://api.github.com/repos/caskdata/cdap/pulls/1259/comments","review_comment_url":"https://api.github.com/repos/caskdata/cdap/pulls/comments/{number}","comments_url":"https://api.github.com/repos/caskdata/cdap/issues/1259/comments","statuses_url":"https://api.github.com/repos/caskdata/cdap/statuses/bb14073145eb21196a10aea48eb72e0aa5a233e9","head":{"label":"caskdata:feature/cdap-939-partitioned-impl","ref":"feature/cdap-939-partitioned-impl","sha":"bb14073145eb21196a10aea48eb72e0aa5a233e9","user":{"login":"caskdata","id":8123023,"avatar_url":"https://avatars.githubusercontent.com/u/8123023?v=3","gravatar_id":"","url":"https://api.github.com/users/caskdata","html_url":"https://github.com/caskdata","followers_url":"https://api.github.com/users/caskdata/followers","following_url":"https://api.github.com/users/caskdata/following{/other_user}","gists_url":"https://api.github.com/users/caskdata/gists{/gist_id}","starred_url":"https://api.github.com/users/caskdata/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/caskdata/subscriptions","organizations_url":"https://api.github.com/users/caskdata/orgs","repos_url":"https://api.github.com/users/caskdata/repos","events_url":"https://api.github.com/users/caskdata/events{/privacy}","received_events_url":"https://api.github.com/users/caskdata/received_events","type":"Organization","site_admin":false},"repo":{"id":22542759,"name":"cdap","full_name":"caskdata/cdap","owner":{"login":"caskdata","id":8123023,"avatar_url":"https://avatars.githubusercontent.com/u/8123023?v=3","gravatar_id":"","url":"https://api.github.com/users/caskdata","html_url":"https://github.com/caskdata","followers_url":"https://api.github.com/users/caskdata/followers","following_url":"https://api.github.com/users/caskdata/following{/other_user}","gists_url":"https://api.github.com/users/caskdata/gists{/gist_id}","starred_url":"https://api.github.com/users/caskdata/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/caskdata/subscriptions","organizations_url":"https://api.github.com/users/caskdata/orgs","repos_url":"https://api.github.com/users/caskdata/repos","events_url":"https://api.github.com/users/caskdata/events{/privacy}","received_events_url":"https://api.github.com/users/caskdata/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/caskdata/cdap","description":"Cask Data Application Platform (CDAP)","fork":false,"url":"https://api.github.com/repos/caskdata/cdap","forks_url":"https://api.github.com/repos/caskdata/cdap/forks","keys_url":"https://api.github.com/repos/caskdata/cdap/keys{/key_id}","collaborators_url":"https://api.github.com/repos/caskdata/cdap/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/caskdata/cdap/teams","hooks_url":"https://api.github.com/repos/caskdata/cdap/hooks","issue_events_url":"https://api.github.com/repos/caskdata/cdap/issues/events{/number}","events_url":"https://api.github.com/repos/caskdata/cdap/events","assignees_url":"https://api.github.com/repos/caskdata/cdap/assignees{/user}","branches_url":"https://api.github.com/repos/caskdata/cdap/branches{/branch}","tags_url":"https://api.github.com/repos/caskdata/cdap/tags","blobs_url":"https://api.github.com/repos/caskdata/cdap/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/caskdata/cdap/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/caskdata/cdap/git/refs{/sha}","trees_url":"https://api.github.com/repos/caskdata/cdap/git/trees{/sha}","statuses_url":"https://api.github.com/repos/caskdata/cdap/statuses/{sha}","languages_url":"https://api.github.com/repos/caskdata/cdap/languages","stargazers_url":"https://api.github.com/repos/caskdata/cdap/stargazers","contributors_url":"https://api.github.com/repos/caskdata/cdap/contributors","subscribers_url":"https://api.github.com/repos/caskdata/cdap/subscribers","subscription_url":"https://api.github.com/repos/caskdata/cdap/subscription","commits_url":"https://api.github.com/repos/caskdata/cdap/commits{/sha}","git_commits_url":"https://api.github.com/repos/caskdata/cdap/git/commits{/sha}","comments_url":"https://api.github.com/repos/caskdata/cdap/comments{/number}","issue_comment_url":"https://api.github.com/repos/caskdata/cdap/issues/comments/{number}","contents_url":"https://api.github.com/repos/caskdata/cdap/contents/{+path}","compare_url":"https://api.github.com/repos/caskdata/cdap/compare/{base}...{head}","merges_url":"https://api.github.com/repos/caskdata/cdap/merges","archive_url":"https://api.github.com/repos/caskdata/cdap/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/caskdata/cdap/downloads","issues_url":"https://api.github.com/repos/caskdata/cdap/issues{/number}","pulls_url":"https://api.github.com/repos/caskdata/cdap/pulls{/number}","milestones_url":"https://api.github.com/repos/caskdata/cdap/milestones{/number}","notifications_url":"https://api.github.com/repos/caskdata/cdap/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/caskdata/cdap/labels{/name}","releases_url":"https://api.github.com/repos/caskdata/cdap/releases{/id}","created_at":"2014-08-02T09:00:10Z","updated_at":"2015-02-11T03:03:49Z","pushed_at":"2015-02-11T06:05:36Z","git_url":"git://github.com/caskdata/cdap.git","ssh_url":"git@github.com:caskdata/cdap.git","clone_url":"https://github.com/caskdata/cdap.git","svn_url":"https://github.com/caskdata/cdap","homepage":"http://cdap.io","size":775029,"stargazers_count":24,"watchers_count":24,"language":"Java","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":true,"forks_count":9,"mirror_url":null,"open_issues_count":14,"forks":9,"open_issues":14,"watchers":24,"default_branch":"develop"}},"base":{"label":"caskdata:develop","ref":"develop","sha":"4a23ac637ac869d598ad937f6168ee26d948df92","user":{"login":"caskdata","id":8123023,"avatar_url":"https://avatars.githubusercontent.com/u/8123023?v=3","gravatar_id":"","url":"https://api.github.com/users/caskdata","html_url":"https://github.com/caskdata","followers_url":"https://api.github.com/users/caskdata/followers","following_url":"https://api.github.com/users/caskdata/following{/other_user}","gists_url":"https://api.github.com/users/caskdata/gists{/gist_id}","starred_url":"https://api.github.com/users/caskdata/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/caskdata/subscriptions","organizations_url":"https://api.github.com/users/caskdata/orgs","repos_url":"https://api.github.com/users/caskdata/repos","events_url":"https://api.github.com/users/caskdata/events{/privacy}","received_events_url":"https://api.github.com/users/caskdata/received_events","type":"Organization","site_admin":false},"repo":{"id":22542759,"name":"cdap","full_name":"caskdata/cdap","owner":{"login":"caskdata","id":8123023,"avatar_url":"https://avatars.githubusercontent.com/u/8123023?v=3","gravatar_id":"","url":"https://api.github.com/users/caskdata","html_url":"https://github.com/caskdata","followers_url":"https://api.github.com/users/caskdata/followers","following_url":"https://api.github.com/users/caskdata/following{/other_user}","gists_url":"https://api.github.com/users/caskdata/gists{/gist_id}","starred_url":"https://api.github.com/users/caskdata/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/caskdata/subscriptions","organizations_url":"https://api.github.com/users/caskdata/orgs","repos_url":"https://api.github.com/users/caskdata/repos","events_url":"https://api.github.com/users/caskdata/events{/privacy}","received_events_url":"https://api.github.com/users/caskdata/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/caskdata/cdap","description":"Cask Data Application Platform (CDAP)","fork":false,"url":"https://api.github.com/repos/caskdata/cdap","forks_url":"https://api.github.com/repos/caskdata/cdap/forks","keys_url":"https://api.github.com/repos/caskdata/cdap/keys{/key_id}","collaborators_url":"https://api.github.com/repos/caskdata/cdap/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/caskdata/cdap/teams","hooks_url":"https://api.github.com/repos/caskdata/cdap/hooks","issue_events_url":"https://api.github.com/repos/caskdata/cdap/issues/events{/number}","events_url":"https://api.github.com/repos/caskdata/cdap/events","assignees_url":"https://api.github.com/repos/caskdata/cdap/assignees{/user}","branches_url":"https://api.github.com/repos/caskdata/cdap/branches{/branch}","tags_url":"https://api.github.com/repos/caskdata/cdap/tags","blobs_url":"https://api.github.com/repos/caskdata/cdap/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/caskdata/cdap/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/caskdata/cdap/git/refs{/sha}","trees_url":"https://api.github.com/repos/caskdata/cdap/git/trees{/sha}","statuses_url":"https://api.github.com/repos/caskdata/cdap/statuses/{sha}","languages_url":"https://api.github.com/repos/caskdata/cdap/languages","stargazers_url":"https://api.github.com/repos/caskdata/cdap/stargazers","contributors_url":"https://api.github.com/repos/caskdata/cdap/contributors","subscribers_url":"https://api.github.com/repos/caskdata/cdap/subscribers","subscription_url":"https://api.github.com/repos/caskdata/cdap/subscription","commits_url":"https://api.github.com/repos/caskdata/cdap/commits{/sha}","git_commits_url":"https://api.github.com/repos/caskdata/cdap/git/commits{/sha}","comments_url":"https://api.github.com/repos/caskdata/cdap/comments{/number}","issue_comment_url":"https://api.github.com/repos/caskdata/cdap/issues/comments/{number}","contents_url":"https://api.github.com/repos/caskdata/cdap/contents/{+path}","compare_url":"https://api.github.com/repos/caskdata/cdap/compare/{base}...{head}","merges_url":"https://api.github.com/repos/caskdata/cdap/merges","archive_url":"https://api.github.com/repos/caskdata/cdap/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/caskdata/cdap/downloads","issues_url":"https://api.github.com/repos/caskdata/cdap/issues{/number}","pulls_url":"https://api.github.com/repos/caskdata/cdap/pulls{/number}","milestones_url":"https://api.github.com/repos/caskdata/cdap/milestones{/number}","notifications_url":"https://api.github.com/repos/caskdata/cdap/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/caskdata/cdap/labels{/name}","releases_url":"https://api.github.com/repos/caskdata/cdap/releases{/id}","created_at":"2014-08-02T09:00:10Z","updated_at":"2015-02-11T03:03:49Z","pushed_at":"2015-02-11T06:05:36Z","git_url":"git://github.com/caskdata/cdap.git","ssh_url":"git@github.com:caskdata/cdap.git","clone_url":"https://github.com/caskdata/cdap.git","svn_url":"https://github.com/caskdata/cdap","homepage":"http://cdap.io","size":775029,"stargazers_count":24,"watchers_count":24,"language":"Java","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":true,"forks_count":9,"mirror_url":null,"open_issues_count":14,"forks":9,"open_issues":14,"watchers":24,"default_branch":"develop"}},"_links":{"self":{"href":"https://api.github.com/repos/caskdata/cdap/pulls/1259"},"html":{"href":"https://github.com/caskdata/cdap/pull/1259"},"issue":{"href":"https://api.github.com/repos/caskdata/cdap/issues/1259"},"comments":{"href":"https://api.github.com/repos/caskdata/cdap/issues/1259/comments"},"review_comments":{"href":"https://api.github.com/repos/caskdata/cdap/pulls/1259/comments"},"review_comment":{"href":"https://api.github.com/repos/caskdata/cdap/pulls/comments/{number}"},"commits":{"href":"https://api.github.com/repos/caskdata/cdap/pulls/1259/commits"},"statuses":{"href":"https://api.github.com/repos/caskdata/cdap/statuses/bb14073145eb21196a10aea48eb72e0aa5a233e9"}}}}