{"action":"created","comment":{"url":"https://api.github.com/repos/caskdata/cdap/pulls/comments/23208682","id":23208682,"diff_hunk":"@@ -0,0 +1,509 @@\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.internal.app.runtime.adapter;\n+\n+import co.cask.cdap.adapter.AdapterSpecification;\n+import co.cask.cdap.adapter.Sink;\n+import co.cask.cdap.adapter.Source;\n+import co.cask.cdap.api.dataset.DatasetProperties;\n+import co.cask.cdap.api.schedule.SchedulableProgramType;\n+import co.cask.cdap.api.schedule.Schedule;\n+import co.cask.cdap.api.schedule.ScheduleSpecification;\n+import co.cask.cdap.api.workflow.ScheduleProgramInfo;\n+import co.cask.cdap.api.workflow.WorkflowSpecification;\n+import co.cask.cdap.app.ApplicationSpecification;\n+import co.cask.cdap.app.deploy.Manager;\n+import co.cask.cdap.app.deploy.ManagerFactory;\n+import co.cask.cdap.app.store.Store;\n+import co.cask.cdap.app.store.StoreFactory;\n+import co.cask.cdap.common.conf.CConfiguration;\n+import co.cask.cdap.common.conf.Constants;\n+import co.cask.cdap.data2.dataset2.DatasetFramework;\n+import co.cask.cdap.data2.dataset2.DatasetManagementException;\n+import co.cask.cdap.data2.transaction.stream.StreamAdmin;\n+import co.cask.cdap.internal.app.deploy.ProgramTerminator;\n+import co.cask.cdap.internal.app.deploy.pipeline.ApplicationWithPrograms;\n+import co.cask.cdap.internal.app.deploy.pipeline.DeploymentInfo;\n+import co.cask.cdap.internal.app.runtime.schedule.Scheduler;\n+import co.cask.cdap.proto.Id;\n+import co.cask.cdap.proto.ProgramType;\n+import com.google.common.annotations.VisibleForTesting;\n+import com.google.common.base.Preconditions;\n+import com.google.common.base.Strings;\n+import com.google.common.collect.ImmutableMap;\n+import com.google.common.collect.Maps;\n+import com.google.common.reflect.TypeToken;\n+import com.google.common.util.concurrent.AbstractIdleService;\n+import com.google.gson.Gson;\n+import com.google.inject.Inject;\n+import org.apache.commons.io.FileUtils;\n+import org.apache.commons.lang.StringUtils;\n+import org.apache.twill.filesystem.Location;\n+import org.apache.twill.filesystem.LocationFactory;\n+import org.quartz.DateBuilder;\n+import org.slf4j.Logger;\n+import org.slf4j.LoggerFactory;\n+\n+import java.io.File;\n+import java.io.IOException;\n+import java.lang.reflect.Type;\n+import java.util.Collection;\n+import java.util.Map;\n+import java.util.Set;\n+import java.util.concurrent.ExecutionException;\n+import java.util.jar.Attributes;\n+import java.util.jar.JarFile;\n+import java.util.jar.Manifest;\n+import javax.annotation.Nullable;\n+\n+/**\n+ * Service that manages managing lifecycle of Adapters.\n+ */\n+public class AdapterService extends AbstractIdleService {\n+  private static final Logger LOG = LoggerFactory.getLogger(AdapterService.class);\n+  private static final Gson GSON = new Gson();\n+  private static final Type STRING_STRING_MAP_TYPE = new TypeToken<Map<String, String>>() { }.getType();\n+  private static final String DATASET_CLASS = \"dataset.class\";\n+  private final LocationFactory locationFactory;\n+  private final ManagerFactory<DeploymentInfo, ApplicationWithPrograms> managerFactory;\n+  private final CConfiguration configuration;\n+  private final DatasetFramework datasetFramework;\n+  private final StreamAdmin streamAdmin;\n+  private final Scheduler scheduler;\n+  private final Store store;\n+  private Map<String, AdapterTypeInfo> adapterTypeInfos;\n+  private final String archiveDir;\n+\n+  @Inject\n+  public AdapterService(CConfiguration configuration, DatasetFramework datasetFramework, Scheduler scheduler,\n+                        StreamAdmin streamAdmin, StoreFactory storeFactory, LocationFactory locationFactory,\n+                        ManagerFactory<DeploymentInfo, ApplicationWithPrograms> managerFactory) {\n+    this.configuration = configuration;\n+    this.datasetFramework = datasetFramework;\n+    this.scheduler = scheduler;\n+    this.streamAdmin = streamAdmin;\n+    this.store = storeFactory.create();\n+    this.locationFactory = locationFactory;\n+    this.managerFactory = managerFactory;\n+    archiveDir = configuration.get(Constants.AppFabric.OUTPUT_DIR) + \"/archive\";\n+  }\n+\n+  @Override\n+  protected void startUp() throws Exception {\n+    LOG.info(\"Starting AdapterService\");\n+    this.adapterTypeInfos = Maps.newHashMap();\n+    registerAdapters();\n+  }\n+\n+  @Override\n+  protected void shutDown() throws Exception {\n+    LOG.info(\"Shutting down AdapterService\");\n+  }\n+\n+  /**\n+   * Get the {@link AdapterTypeInfo} for a given adapter type.\n+   *\n+   * @param adapterType adapter type\n+   * @return instance of {@link AdapterTypeInfo} if available, null otherwise\n+   */\n+  @Nullable\n+  public AdapterTypeInfo getAdapterTypeInfo(String adapterType) {\n+    return this.adapterTypeInfos.get(adapterType);\n+  }\n+\n+  /**\n+   * Retrieves the {@link AdapterSpecification} specified by the name in a given namespace.\n+   *\n+   * @param namespace namespace to lookup the adapter\n+   * @param adapterName name of the adapter\n+   * @return requested {@link AdapterSpecification} or null if no such AdapterInfo exists\n+   */\n+  @Nullable\n+  public AdapterSpecification getAdapter(String namespace, String adapterName) {\n+    return store.getAdapter(Id.Namespace.from(namespace), adapterName);\n+  }\n+\n+  public Collection<AdapterSpecification> getAdapters(String namespace) {\n+    return store.getAllAdapters(Id.Namespace.from(namespace));\n+  }\n+\n+  /**\n+   * Creates the adapter\n+   *\n+   * @param namespaceId namespace\n+   * @param adapterSpec instance of {@link AdapterSpecification}\n+   * @throws IllegalArgumentException\n+   */\n+  public void createAdapter(String namespaceId, AdapterSpecification adapterSpec) throws IllegalArgumentException {\n+\n+    AdapterTypeInfo adapterTypeInfo = adapterTypeInfos.get(adapterSpec.getType());\n+    Preconditions.checkNotNull(adapterTypeInfo, \"Adapter type %s not found\", adapterSpec.getType());\n+\n+    try {\n+      ApplicationSpecification appSpec = deployApplication(namespaceId, adapterTypeInfo);\n+\n+      validateSources(adapterSpec.getName(), adapterSpec.getSources());\n+      createSinks(adapterSpec.getSinks(), adapterTypeInfo);\n+\n+      // If the adapter already exists, remove existing schedule to replace with the new one.\n+      AdapterSpecification existingSpec = store.getAdapter(Id.Namespace.from(namespaceId), adapterSpec.getName());\n+      if (existingSpec != null) {\n+        stopPrograms(namespaceId, appSpec, adapterTypeInfo, existingSpec);\n+      }\n+\n+      startPrograms(namespaceId, appSpec, adapterTypeInfo, adapterSpec);\n+      store.addAdapter(Id.Namespace.from(namespaceId), adapterSpec);\n+\n+    } catch (Exception e) {\n+      throw new RuntimeException(\"Error deploying adapter application \" + e.getMessage());\n+    }\n+  }\n+\n+  /**\n+   * Remove adapter identified by the namespace and name.\n+   *\n+   * @param namespace namespace id\n+   * @param adapterName adapter name\n+   * @throws AdapterNotFoundException if the adapter to be removed is not found.\n+   */\n+  public void removeAdapter(String namespace, String adapterName) throws AdapterNotFoundException {\n+    Id.Namespace namespaceId = Id.Namespace.from(namespace);\n+    AdapterSpecification adapterSpec = store.getAdapter(namespaceId, adapterName);\n+    if (adapterSpec == null) {\n+      throw new AdapterNotFoundException(String.format(\"Adapter %s not found.\", adapterName));\n+    }\n+\n+    ApplicationSpecification appSpec = store.getApplication(Id.Application.from(namespaceId, adapterSpec.getType()));\n+    stopPrograms(namespace, appSpec, adapterTypeInfos.get(adapterSpec.getType()), adapterSpec);\n+    store.removeAdapter(namespaceId, adapterName);\n+\n+    // TODO: Delete the application if this is the last adapter\n+  }\n+\n+  public void stopAdapter(String namespace, String adapterName) {\n+    //TODO:\n+  }\n+\n+  public void startAdapter(String namespace, String adapterName) {\n+    //TODO:\n+  }\n+\n+  // Deploys adapter application if it is not already deployed.\n+  private ApplicationSpecification deployApplication(String namespaceId, AdapterTypeInfo adapterTypeInfo)\n+    throws Exception {\n+\n+    ApplicationSpecification spec = store.getApplication(Id.Application.from(namespaceId, adapterTypeInfo.getType()));\n+    // Application is already deployed.\n+    if (spec != null) {\n+      return spec;\n+    }\n+\n+    Manager<DeploymentInfo, ApplicationWithPrograms> manager = managerFactory.create(new ProgramTerminator() {\n+      @Override\n+      public void stop(Id.Namespace id, Id.Program programId, ProgramType type) throws ExecutionException {\n+        // no-op\n+      }\n+    });\n+\n+    Location destination = locationFactory.create(archiveDir).append(namespaceId).append(adapterTypeInfo.getType());\n+    DeploymentInfo deploymentInfo = new DeploymentInfo(adapterTypeInfo.getFile(), destination);\n+    ApplicationWithPrograms applicationWithPrograms = manager.deploy(Id.Namespace.from(namespaceId),\n+                                                                     adapterTypeInfo.getType(), deploymentInfo).get();\n+    return applicationWithPrograms.getSpecification();\n+  }\n+\n+  // Start all the programs needed for the adapter. Currently, only scheduling of workflow is supported.\n+  private void startPrograms(String namespaceId, ApplicationSpecification spec, AdapterTypeInfo adapterTypeInfo,\n+                             AdapterSpecification adapterSpec) {\n+    ProgramType programType = adapterTypeInfo.getProgramType();\n+    if (programType.equals(ProgramType.WORKFLOW)) {\n+      Map<String, WorkflowSpecification> workflowSpecs = spec.getWorkflows();\n+      for (Map.Entry<String, WorkflowSpecification> entry : workflowSpecs.entrySet()) {\n+        Id.Program programId = Id.Program.from(namespaceId, spec.getName(), entry.getValue().getName());\n+        addSchedule(programId, adapterSpec);\n+      }\n+    } else {\n+      // Only Workflows are supported to be scheduled in the current implementation\n+      throw new UnsupportedOperationException(String.format(\"Unsupported program type %s for adapter\",\n+                                                            programType.toString()));\n+    }\n+  }\n+\n+  // Stop all the programs needed for the adapter. Currently, only unscheduling of workflow is supported.\n+  private void stopPrograms(String namespaceId, ApplicationSpecification spec, AdapterTypeInfo adapterTypeInfo,\n+                            AdapterSpecification adapterSpec) {\n+    ProgramType programType = adapterTypeInfo.getProgramType();\n+    if (programType.equals(ProgramType.WORKFLOW)) {\n+      Map<String, WorkflowSpecification> workflowSpecs = spec.getWorkflows();\n+      for (Map.Entry<String, WorkflowSpecification> entry : workflowSpecs.entrySet()) {\n+        Id.Program programId = Id.Program.from(namespaceId, adapterSpec.getType(), entry.getValue().getName());\n+        deleteSchedule(programId, SchedulableProgramType.WORKFLOW, adapterSpec.getScheduleName());\n+      }\n+    } else {\n+      // Only Workflows are supported to be unscheduled in the current implementation\n+      throw new UnsupportedOperationException(String.format(\"Unsupported program type %s for adapter\",\n+                                                            programType.toString()));\n+    }\n+  }\n+\n+  // Adds a schedule to the scheduler as well as to the appspec\n+  private void addSchedule(Id.Program programId, AdapterSpecification adapterSpec) {\n+    String cronExpr = toCronExpr(adapterSpec.getProperties().get(\"frequency\"));\n+    Preconditions.checkNotNull(cronExpr, \"Frequency of running the adapter is missing. Cannot schedule program\");\n+    Schedule schedule = new Schedule(adapterSpec.getScheduleName(), adapterSpec.getScheduleDescription(), cronExpr);\n+    ScheduleSpecification scheduleSpec = new ScheduleSpecification(schedule,\n+                                           new ScheduleProgramInfo(SchedulableProgramType.WORKFLOW, programId.getId()),\n+                                           adapterSpec.getProperties());\n+\n+    scheduler.schedule(programId, scheduleSpec.getProgram().getProgramType(), scheduleSpec.getSchedule());\n+    //TODO: Scheduler API should also manage the MDS.\n+    store.addSchedule(programId, scheduleSpec);\n+  }\n+\n+  // Deletes schedule from the scheduler as well as from the app spec\n+  private void deleteSchedule(Id.Program programId, SchedulableProgramType programType, String scheduleName) {\n+    scheduler.deleteSchedule(programId, programType, scheduleName);\n+    //TODO: Scheduler API should also manage the MDS.\n+    store.deleteSchedule(programId, programType, scheduleName);\n+  }\n+\n+  // Sources for all adapters should exists before creating the adapters.\n+  private void validateSources(String adapterName, Set<Source> sources) throws IllegalArgumentException {\n+    // Ensure all sources exist\n+    for (Source source : sources) {\n+      if (Source.Type.STREAM.equals(source.getType())) {\n+        if (!streamExists(source.getName())) {\n+          throw new IllegalArgumentException(String.format(\"Stream %s must exist during create of adapter: %s\",\n+                                                           source.getName(), adapterName));\n+        }\n+      } else {\n+        throw new IllegalArgumentException(String.format(\"Unknown Source type: %s\", source.getType()));\n+      }\n+    }\n+  }\n+\n+  private boolean streamExists(String streamName) {\n+    try {\n+      return streamAdmin.exists(streamName);\n+    } catch (Exception e) {\n+      throw new RuntimeException(e);\n+    }\n+  }\n+\n+  // create the required sinks for the adapters. Currently only DATASET sink type is supported.\n+  private void createSinks(Set<Sink> sinks, AdapterTypeInfo adapterTypeInfo) {\n+    // create sinks if it does not exist\n+    for (Sink sink : sinks) {\n+      if (Sink.Type.DATASET.equals(sink.getType())) {\n+        String datasetName = sink.getName();\n+        // add all properties that were defined in the manifest (default sink properties), override that with sink\n+        // properties passed while creating the sinks.\n+        DatasetProperties properties = DatasetProperties.builder()\n+                                            .addAll(adapterTypeInfo.getDefaultSinkProperties())\n+                                            .addAll(sink.getProperties())\n+                                            .build();\n+        createDataset(datasetName, properties.getProperties().get(DATASET_CLASS), properties);\n+      } else {\n+        throw new IllegalArgumentException(String.format(\"Unknown Sink type: %s\", sink.getType()));\n+      }\n+    }\n+  }\n+\n+  private void createDataset(String datasetName, String datasetClass, DatasetProperties properties) {\n+    Preconditions.checkNotNull(datasetClass, \"Dataset class cannot be null\");\n+    try {\n+      if (!datasetFramework.hasInstance(datasetName)) {\n+        datasetFramework.addInstance(datasetClass, datasetName, properties);\n+        LOG.debug(\"Dataset instance {} created with properties: {}.\", datasetName, properties);\n+      } else {\n+        LOG.debug(\"Dataset instance {} already exists; not creating a new one.\", datasetName);\n+      }\n+    } catch (DatasetManagementException e) {\n+      LOG.error(\"Error while creating dataset {}\", datasetName, e);\n+      throw new RuntimeException(e);\n+    } catch (IOException e) {\n+      LOG.error(\"Error while creating dataset {}\", datasetName, e);\n+      throw new RuntimeException(e);\n+    }\n+  }\n+\n+  // Reads all the jars from the adapter directory and sets up required internal structures.\n+  @VisibleForTesting\n+  void registerAdapters() {\n+    try {\n+      File baseDir = new File(configuration.get(Constants.AppFabric.ADAPTER_DIR));\n+      Collection<File> files = FileUtils.listFiles(baseDir, new String[]{\"jar\"}, true);\n+      for (File file : files) {\n+        try {\n+          Manifest manifest = new JarFile(file.getAbsolutePath()).getManifest();\n+          AdapterTypeInfo adapterTypeInfo = createAdapterTypeInfo(file, manifest);\n+          if (adapterTypeInfo != null) {\n+            adapterTypeInfos.put(adapterTypeInfo.getType(), adapterTypeInfo);\n+          } else {\n+            LOG.error(\"Missing required information to create adapter {}\", file.getAbsolutePath());\n+          }\n+        } catch (IOException e) {\n+          LOG.warn(String.format(\"Unable to read adapter jar %s\", file.getAbsolutePath()));\n+        }\n+      }\n+    } catch (Exception e) {\n+      LOG.warn(\"Unable to read the plugins directory\");\n+    }\n+  }\n+\n+\n+  private AdapterTypeInfo createAdapterTypeInfo(File file, Manifest manifest) {\n+    if (manifest != null) {\n+      Attributes mainAttributes = manifest.getMainAttributes();\n+\n+      String adapterType = mainAttributes.getValue(AdapterManifestAttributes.ADAPTER_TYPE);\n+      String adapterProgramType = mainAttributes.getValue(AdapterManifestAttributes.ADAPTER_PROGRAM_TYPE);\n+      String defaultAdapterProperties = mainAttributes.getValue(AdapterManifestAttributes.ADAPTER_PROPERTIES);\n+\n+      String sourceType = mainAttributes.getValue(AdapterManifestAttributes.SOURCE_TYPE);\n+      String sinkType = mainAttributes.getValue(AdapterManifestAttributes.SINK_TYPE);\n+      String defaultSourceProperties = mainAttributes.getValue(AdapterManifestAttributes.SOURCE_PROPERTIES);\n+      String defaultSinkProperties = mainAttributes.getValue(AdapterManifestAttributes.SINK_PROPERTIES);\n+\n+      if (adapterType != null && sourceType != null && sinkType != null && adapterProgramType != null) {\n+        return new AdapterTypeInfo(file, adapterType, Source.Type.valueOf(sourceType.toUpperCase()),\n+                                   Sink.Type.valueOf(sinkType.toUpperCase()),\n+                                   propertiesFromString(defaultSourceProperties),\n+                                   propertiesFromString(defaultSinkProperties),\n+                                   propertiesFromString(defaultAdapterProperties),\n+                                   ProgramType.valueOf(adapterProgramType.toUpperCase()));\n+      }\n+    }\n+    return null;\n+  }\n+\n+  protected Map<String, String> propertiesFromString(String gsonEncodedMap) {\n+    Map<String, String> properties = GSON.fromJson(gsonEncodedMap, STRING_STRING_MAP_TYPE);\n+    return properties == null ? Maps.<String, String>newHashMap() : properties;\n+  }\n+\n+  /**\n+   * Holds information about an Adapter\n+   */\n+  public static final class AdapterTypeInfo {\n+\n+    private final File file;\n+    private final String type;\n+    private final Source.Type sourceType;\n+    private final Sink.Type sinkType;\n+    private final Map<String, String> defaultSourceProperties;\n+    private final Map<String, String> defaultSinkProperties;\n+    private final Map<String, String> defaultAdapterProperties;\n+    private final ProgramType programType;\n+\n+    public AdapterTypeInfo(File file, String adapterType, Source.Type sourceType, Sink.Type sinkType,\n+                           Map<String, String> defaultSourceProperties,\n+                           Map<String, String> defaultSinkProperties,\n+                           Map<String, String> defaultAdapterProperties,\n+                           ProgramType programType) {\n+      this.file = file;\n+      this.type = adapterType;\n+      this.sourceType = sourceType;\n+      this.sinkType = sinkType;\n+      this.defaultSourceProperties = ImmutableMap.copyOf(defaultSourceProperties);\n+      this.defaultSinkProperties = ImmutableMap.copyOf(defaultSinkProperties);\n+      this.defaultAdapterProperties = ImmutableMap.copyOf(defaultAdapterProperties);\n+      this.programType = programType;\n+    }\n+\n+    public File getFile() {\n+      return file;\n+    }\n+\n+    public String getType() {\n+      return type;\n+    }\n+\n+    public Source.Type getSourceType() {\n+      return sourceType;\n+    }\n+\n+    public Sink.Type getSinkType() {\n+      return sinkType;\n+    }\n+\n+    public Map<String, String> getDefaultSourceProperties() {\n+      return defaultSourceProperties;\n+    }\n+\n+    public Map<String, String> getDefaultSinkProperties() {\n+      return defaultSinkProperties;\n+    }\n+\n+    public Map<String, String> getDefaultAdapterProperties() {\n+      return defaultAdapterProperties;\n+    }\n+\n+    public ProgramType getProgramType() {\n+      return programType;\n+    }\n+  }\n+\n+  private static class AdapterManifestAttributes {\n+    private static final String ADAPTER_TYPE = \"CDAP-Adapter-Type\";\n+    private static final String ADAPTER_PROPERTIES = \"CDAP-Adapter-Properties\";\n+    private static final String ADAPTER_PROGRAM_TYPE = \"CDAP-Adapter-Program-Type\";\n+    private static final String SOURCE_TYPE = \"CDAP-Source-Type\";\n+    private static final String SINK_TYPE = \"CDAP-Sink-Type\";\n+    private static final String SOURCE_PROPERTIES = \"CDAP-Source-Properties\";\n+    private static final String SINK_PROPERTIES = \"CDAP-Sink-Properties\";\n+  }\n+\n+  /**\n+   * Converts a frequency expression into cronExpression that is usable by quartz.\n+   * Supports frequency expressions with the following resolutions: minutes, hours, days.\n+   * Example conversions:\n+   * '10m' -> '*{@literal /}10 * * * ?'\n+   * '3d' -> '0 0 *{@literal /}3 * ?'\n+   *\n+   * @return a cron expression\n+   */\n+  @VisibleForTesting\n+  static String toCronExpr(String frequency) {\n+    Preconditions.checkArgument(!Strings.isNullOrEmpty(frequency));\n+    // remove all whitespace\n+    frequency = frequency.replaceAll(\"\\\\s+\", \"\");","path":"cdap-app-fabric/src/main/java/co/cask/cdap/internal/app/runtime/adapter/AdapterService.java","position":484,"original_position":484,"commit_id":"c790152337fcc25a5dda899f6bf9837c58e06ff8","original_commit_id":"c790152337fcc25a5dda899f6bf9837c58e06ff8","user":{"login":"anwar6953","id":2440977,"avatar_url":"https://avatars.githubusercontent.com/u/2440977?v=3","gravatar_id":"","url":"https://api.github.com/users/anwar6953","html_url":"https://github.com/anwar6953","followers_url":"https://api.github.com/users/anwar6953/followers","following_url":"https://api.github.com/users/anwar6953/following{/other_user}","gists_url":"https://api.github.com/users/anwar6953/gists{/gist_id}","starred_url":"https://api.github.com/users/anwar6953/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/anwar6953/subscriptions","organizations_url":"https://api.github.com/users/anwar6953/orgs","repos_url":"https://api.github.com/users/anwar6953/repos","events_url":"https://api.github.com/users/anwar6953/events{/privacy}","received_events_url":"https://api.github.com/users/anwar6953/received_events","type":"User","site_admin":false},"body":"a frequency like ' 12 m' would be valid. meaning every 12 minutes.\r\n'1 2 3 m'  would resolve to every 123 minutes, which isn't supported (because 123 is larger than 60).","created_at":"2015-01-20T08:44:25Z","updated_at":"2015-01-20T08:44:25Z","html_url":"https://github.com/caskdata/cdap/pull/1037#discussion_r23208682","pull_request_url":"https://api.github.com/repos/caskdata/cdap/pulls/1037","_links":{"self":{"href":"https://api.github.com/repos/caskdata/cdap/pulls/comments/23208682"},"html":{"href":"https://github.com/caskdata/cdap/pull/1037#discussion_r23208682"},"pull_request":{"href":"https://api.github.com/repos/caskdata/cdap/pulls/1037"}}},"pull_request":{"url":"https://api.github.com/repos/caskdata/cdap/pulls/1037","id":27656806,"html_url":"https://github.com/caskdata/cdap/pull/1037","diff_url":"https://github.com/caskdata/cdap/pull/1037.diff","patch_url":"https://github.com/caskdata/cdap/pull/1037.patch","issue_url":"https://api.github.com/repos/caskdata/cdap/issues/1037","number":1037,"state":"open","locked":false,"title":"Feature/adapter service","user":{"login":"sreevatsanraman","id":1148913,"avatar_url":"https://avatars.githubusercontent.com/u/1148913?v=3","gravatar_id":"","url":"https://api.github.com/users/sreevatsanraman","html_url":"https://github.com/sreevatsanraman","followers_url":"https://api.github.com/users/sreevatsanraman/followers","following_url":"https://api.github.com/users/sreevatsanraman/following{/other_user}","gists_url":"https://api.github.com/users/sreevatsanraman/gists{/gist_id}","starred_url":"https://api.github.com/users/sreevatsanraman/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sreevatsanraman/subscriptions","organizations_url":"https://api.github.com/users/sreevatsanraman/orgs","repos_url":"https://api.github.com/users/sreevatsanraman/repos","events_url":"https://api.github.com/users/sreevatsanraman/events{/privacy}","received_events_url":"https://api.github.com/users/sreevatsanraman/received_events","type":"User","site_admin":false},"body":"Feature Adapter\r\n- [x] Modify deploy pipeline for re-use <a href = \"https://github.com/caskdata/cdap/pull/1033\" >PR 1033</a>\r\n- [x] AdapterService - Current PR\r\n- [ ] Adapter Lifecycle management (HTTP)\r\n- [ ] Negative tests for deploying adapters\r\n\r\nNote: This PR contains Changes from PR 1033. Please ignore the diffs from that PR. ","created_at":"2015-01-20T06:37:29Z","updated_at":"2015-01-20T08:44:26Z","closed_at":null,"merged_at":null,"merge_commit_sha":"1ebc4db38531315f49586cfe2a61177025d500d3","assignee":{"login":"albertshau","id":5215004,"avatar_url":"https://avatars.githubusercontent.com/u/5215004?v=3","gravatar_id":"","url":"https://api.github.com/users/albertshau","html_url":"https://github.com/albertshau","followers_url":"https://api.github.com/users/albertshau/followers","following_url":"https://api.github.com/users/albertshau/following{/other_user}","gists_url":"https://api.github.com/users/albertshau/gists{/gist_id}","starred_url":"https://api.github.com/users/albertshau/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/albertshau/subscriptions","organizations_url":"https://api.github.com/users/albertshau/orgs","repos_url":"https://api.github.com/users/albertshau/repos","events_url":"https://api.github.com/users/albertshau/events{/privacy}","received_events_url":"https://api.github.com/users/albertshau/received_events","type":"User","site_admin":false},"milestone":null,"commits_url":"https://api.github.com/repos/caskdata/cdap/pulls/1037/commits","review_comments_url":"https://api.github.com/repos/caskdata/cdap/pulls/1037/comments","review_comment_url":"https://api.github.com/repos/caskdata/cdap/pulls/comments/{number}","comments_url":"https://api.github.com/repos/caskdata/cdap/issues/1037/comments","statuses_url":"https://api.github.com/repos/caskdata/cdap/statuses/c790152337fcc25a5dda899f6bf9837c58e06ff8","head":{"label":"caskdata:feature/adapter-service","ref":"feature/adapter-service","sha":"c790152337fcc25a5dda899f6bf9837c58e06ff8","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-01-20T07:41:08Z","pushed_at":"2015-01-20T08:31:51Z","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":701222,"stargazers_count":23,"watchers_count":23,"language":"Java","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":true,"forks_count":7,"mirror_url":null,"open_issues_count":16,"forks":7,"open_issues":16,"watchers":23,"default_branch":"develop"}},"base":{"label":"caskdata:develop","ref":"develop","sha":"2599865db8d7c441302f7328586e9268ac8f0337","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-01-20T07:41:08Z","pushed_at":"2015-01-20T08:31:51Z","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":701222,"stargazers_count":23,"watchers_count":23,"language":"Java","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":true,"forks_count":7,"mirror_url":null,"open_issues_count":16,"forks":7,"open_issues":16,"watchers":23,"default_branch":"develop"}},"_links":{"self":{"href":"https://api.github.com/repos/caskdata/cdap/pulls/1037"},"html":{"href":"https://github.com/caskdata/cdap/pull/1037"},"issue":{"href":"https://api.github.com/repos/caskdata/cdap/issues/1037"},"comments":{"href":"https://api.github.com/repos/caskdata/cdap/issues/1037/comments"},"review_comments":{"href":"https://api.github.com/repos/caskdata/cdap/pulls/1037/comments"},"review_comment":{"href":"https://api.github.com/repos/caskdata/cdap/pulls/comments/{number}"},"commits":{"href":"https://api.github.com/repos/caskdata/cdap/pulls/1037/commits"},"statuses":{"href":"https://api.github.com/repos/caskdata/cdap/statuses/c790152337fcc25a5dda899f6bf9837c58e06ff8"}}}}