diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..cd62d2e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,54 @@
+
+*.class
+.*.swp
+.beamer
+# Package Files #
+*.jar
+*.war
+*.ear
+*.versionsBackup
+
+# Intellij Files & Dir #
+**/*.iml
+*.ipr
+*.iws
+atlassian-ide-plugin.xml
+out/
+.DS_Store
+./lib/
+.idea
+
+# Gradle Files & Dir #
+build/
+.gradle/
+.stickyStorage
+.build/
+target/
+
+# Node log
+npm-*.log
+logs/
+
+# Singlenode and test data files.
+/templates/
+/data/
+/data-fabric-tests/data/
+
+# ANTLR4
+/core/gen
+*.tokens
+DirectivesLexer.java
+DirectivesParser.java
+DirectivesBaseListener.java
+DirectivesBaseVisitor.java
+DirectivesListener.java
+DirectivesVisitor.java
+
+# generated by docs build
+*.py
+
+# Remove release.properties
+release.properties
+
+# Remove dev directory.
+dev
\ No newline at end of file
diff --git a/checkstyle.xml b/checkstyle.xml
new file mode 100644
index 0000000..04c6eda
--- /dev/null
+++ b/checkstyle.xml
@@ -0,0 +1,397 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/contrib/pom.xml b/contrib/pom.xml
new file mode 100644
index 0000000..1c8fba3
--- /dev/null
+++ b/contrib/pom.xml
@@ -0,0 +1,66 @@
+
+
+
+ 4.0.0
+
+ io.cdap.plugin
+ marketo-entity-generator
+ 1.0.0-SNAPSHOT
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ 8
+ 8
+
+
+
+
+
+
+ 6.1.0-SNAPSHOT
+
+
+
+
+ io.cdap.cdap
+ cdap-api-common
+ ${cdap.version}
+
+
+ com.google.guava
+ guava
+ 28.1-jre
+
+
+ com.squareup
+ javapoet
+ 1.11.1
+
+
+ io.cdap.plugin
+ marketo-entity-plugin
+ system
+ 1.0.0-SNAPSHOT
+ ${basedir}/../target/marketo-entity-plugin-1.0.0-SNAPSHOT.jar
+
+
+
\ No newline at end of file
diff --git a/contrib/src/main/java/io/cdap/plugin/generate/Generate.java b/contrib/src/main/java/io/cdap/plugin/generate/Generate.java
new file mode 100644
index 0000000..90642fb
--- /dev/null
+++ b/contrib/src/main/java/io/cdap/plugin/generate/Generate.java
@@ -0,0 +1,445 @@
+/*
+ * Copyright © 2019 Cask Data, Inc.
+ *
+ * Licensed 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 io.cdap.plugin.generate;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.reflect.ClassPath;
+import com.squareup.javapoet.ClassName;
+import com.squareup.javapoet.FieldSpec;
+import com.squareup.javapoet.JavaFile;
+import com.squareup.javapoet.MethodSpec;
+import com.squareup.javapoet.TypeSpec;
+import io.cdap.cdap.api.data.format.StructuredRecord;
+import io.cdap.cdap.api.data.schema.Schema;
+import io.cdap.plugin.marketo.common.api.Marketo;
+import io.cdap.plugin.marketo.common.api.entities.asset.gen.Entity;
+import io.cdap.plugin.marketo.common.api.entities.asset.gen.Response;
+
+import java.io.IOException;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.lang.reflect.ParameterizedType;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.stream.Collectors;
+import javax.lang.model.element.Modifier;
+
+/**
+ * Class that generates schema for entities.
+ * This class is not required anymore in near future and not intended to be used by end users...
+ * ...but can be used later again if some massive changes to schema will be required.
+ *
+ * Requires guava.
+ */
+public class Generate {
+ public static void main(String... args) throws IOException {
+ List methods = new ArrayList<>();
+ List withoutPaging = getResponseClasses().stream()
+ .filter(Generate::isNotPaged)
+ .map(Generate::getItemClsForResponseCls)
+ .map(aClass -> "EntityType."+aClass.getSimpleName().toUpperCase())
+ .collect(Collectors.toList());
+
+ FieldSpec withoutPagingField = FieldSpec.builder(ClassName.bestGuess("List"), "ENTITIES_WITHOUT_PAGING")
+ .addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
+ .initializer("$T.of(\n$L\n)", ImmutableList.class, String.join(",\n", withoutPaging))
+ .build();
+
+ MethodSpec supportPagingMethod = MethodSpec.methodBuilder("supportPaging")
+ .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
+ .returns(boolean.class)
+ .addParameter(ClassName.bestGuess("EntityType"), "entityType")
+ .addStatement("return !ENTITIES_WITHOUT_PAGING.contains(entityType)")
+ .build();
+
+ MethodSpec.Builder iteratorForEntityTypeBuilder = MethodSpec.methodBuilder("iteratorForEntityType")
+ .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
+ .returns(Iterator.class)
+ .addParameter(Marketo.class, "marketo")
+ .addParameter(ClassName.bestGuess("EntityType"), "entityType")
+ .addParameter(int.class, "offset")
+ .addParameter(int.class, "maxResults")
+ .beginControlFlow("switch (entityType)");
+
+ getResponseClasses().forEach(aClass -> {
+ ParameterizedType baseT = (ParameterizedType) aClass.getGenericSuperclass();
+ Class responseItemClass = (Class>) baseT.getActualTypeArguments()[0];
+ iteratorForEntityTypeBuilder.beginControlFlow("case $L:",
+ responseItemClass.getSimpleName().toUpperCase());
+ iteratorForEntityTypeBuilder.addStatement("return marketo.iteratePage(\n$S,\n $T.class,\n $T::getResult,\n " +
+ "$T.of(\n\"maxReturn\",\n Integer.toString(maxResults),\n" +
+ " \"offset\",\n Integer.toString(offset)\n)\n)",
+ getFetchUrl(aClass),
+ aClass, aClass,
+ ImmutableMap.class);
+ iteratorForEntityTypeBuilder.endControlFlow();
+ });
+ iteratorForEntityTypeBuilder.beginControlFlow("default:");
+ iteratorForEntityTypeBuilder.addStatement(
+ "throw new IllegalArgumentException(\"Unknown entity name:\" + entityType.getValue())");
+ iteratorForEntityTypeBuilder.endControlFlow();
+ iteratorForEntityTypeBuilder.endControlFlow();
+
+ methods.add(iteratorForEntityTypeBuilder.build());
+
+ MethodSpec.Builder schemaForEntityNameBuilder = MethodSpec.methodBuilder("schemaForEntityName")
+ .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
+ .returns(Schema.class)
+ .addParameter(String.class, "entityName");
+
+ schemaForEntityNameBuilder.beginControlFlow("switch(entityName)");
+ getEntityClasses().forEach(aClass -> {
+ schemaForEntityNameBuilder.beginControlFlow("case $S:", aClass.getSimpleName());
+ schemaForEntityNameBuilder.addStatement("return $L()", generateSchemaGetterMethod(aClass));
+ schemaForEntityNameBuilder.endControlFlow();
+ });
+ schemaForEntityNameBuilder.beginControlFlow("default:");
+ schemaForEntityNameBuilder.addStatement(
+ "throw new IllegalArgumentException(\"Unknown entity name:\" + entityName)");
+ schemaForEntityNameBuilder.endControlFlow();
+ schemaForEntityNameBuilder.endControlFlow();
+
+ methods.add(schemaForEntityNameBuilder.build());
+
+ // individual schemas
+ getEntityClasses().forEach(aClass -> {
+ MethodSpec.Builder entitySchemaGenBuilder = MethodSpec.methodBuilder(generateSchemaGetterMethod(aClass))
+ .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
+ .returns(Schema.class)
+ .addStatement("$T fields = new $T<>()", List.class, ArrayList.class);
+ for (Field field : aClass.getDeclaredFields()) {
+ if (isSimpleType(field)) {
+ entitySchemaGenBuilder.addStatement("fields.add(Schema.Field.of($S, $L))", field.getName(),
+ simpleTypeToCdapType(field));
+ } else if (isComplexType(field)) {
+ entitySchemaGenBuilder.addStatement(
+ "fields.add(Schema.Field.of($S, Schema.nullableOf(EntityHelper.$L())))",
+ field.getName(), generateSchemaGetterMethod(field.getType()));
+ } else if (isListType(field)) {
+ entitySchemaGenBuilder.addStatement(
+ "fields.add(Schema.Field.of($S, $L))",
+ field.getName(), listTypeToCdapType(field));
+ } else {
+ throw new RuntimeException("Unknown field type.");
+ }
+ }
+ entitySchemaGenBuilder.addStatement(
+ "return Schema.recordOf(\"Schema\" + UUID.randomUUID().toString().replace(\"-\", \"\"), fields)");
+ methods.add(entitySchemaGenBuilder.build());
+ });
+
+ // entity object to structured record
+ MethodSpec.Builder recordFromEntityBuilder = MethodSpec.methodBuilder("structuredRecordFromEntity")
+ .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
+ .returns(StructuredRecord.class)
+ .addParameter(String.class, "entityName")
+ .addParameter(Object.class, "entity")
+ .addParameter(Schema.class, "schema")
+ .addStatement("StructuredRecord.Builder builder = StructuredRecord.builder(schema)")
+ .beginControlFlow("switch(entityName)");
+ getEntityClasses().forEach(aClass -> {
+ recordFromEntityBuilder.beginControlFlow("case $S:", aClass.getSimpleName());
+ recordFromEntityBuilder.addStatement("$T $L = ($L) entity",
+ aClass, aClass.getSimpleName().toLowerCase(),
+ aClass.getSimpleName());
+
+ recordFromEntityBuilder.beginControlFlow("if (entity == null)");
+ recordFromEntityBuilder.addStatement("break");
+ recordFromEntityBuilder.endControlFlow();
+ for (Field field : aClass.getDeclaredFields()) {
+ if (isSimpleType(field)) {
+ recordFromEntityBuilder.addStatement("builder.set($S, $L.$L())",
+ field.getName(),
+ aClass.getSimpleName().toLowerCase(),
+ findGetterMethod(field, aClass));
+ } else if (isComplexType(field)) {
+ recordFromEntityBuilder.addStatement("builder.set($S, EntityHelper.structuredRecordFromEntity(\n" +
+ " $S,\n" +
+ " $L.$L(),\n" +
+ " schema.getField($S).getSchema().getNonNullable()))",
+ field.getName(),
+ field.getType().getSimpleName(),
+ aClass.getSimpleName().toLowerCase(),
+ findGetterMethod(field, aClass),
+ field.getName());
+ } else if (isListType(field)) {
+ ParameterizedType integerListType = (ParameterizedType) field.getGenericType();
+ Class> listType = (Class>) integerListType.getActualTypeArguments()[0];
+ if (isSimpleType(listType)) {
+ recordFromEntityBuilder.addStatement("builder.set($S, $L.$L())",
+ field.getName(),
+ aClass.getSimpleName().toLowerCase(),
+ findGetterMethod(field, aClass));
+ } else {
+ recordFromEntityBuilder.addStatement(
+ "builder.set(\n" +
+ " $S,\n" +
+ " $L.$L().stream()\n" +
+ " .map(ent -> EntityHelper.structuredRecordFromEntity(\n" +
+ " $S,\n" +
+ " ent,\n" +
+ " schema.getField($S).getSchema().getNonNullable().getComponentSchema()))\n" +
+ " .collect(Collectors.toList())\n" +
+ ")",
+ field.getName(), aClass.getSimpleName().toLowerCase(), findGetterMethod(field, aClass),
+ listType.getSimpleName(), field.getName()
+ );
+ }
+ } else {
+ throw new RuntimeException("Unknown field type.");
+ }
+ }
+ recordFromEntityBuilder.addStatement("break");
+ recordFromEntityBuilder.endControlFlow();
+ });
+
+ recordFromEntityBuilder.beginControlFlow("default:");
+ recordFromEntityBuilder.addStatement(
+ "throw new IllegalArgumentException(\"Unknown entity name:\" + entityName)");
+ recordFromEntityBuilder.endControlFlow();
+ recordFromEntityBuilder.endControlFlow(); // switch statement
+ recordFromEntityBuilder.addStatement("return builder.build()");
+ methods.add(recordFromEntityBuilder.build());
+
+ TypeSpec.Builder entityTypeEnumBuilder = TypeSpec.enumBuilder("EntityType")
+ .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
+ .addField(String.class, "value", Modifier.PRIVATE, Modifier.FINAL);
+
+ getTopLevelEntityClasses().forEach(aClass -> {
+ entityTypeEnumBuilder.addEnumConstant(
+ aClass.getSimpleName().toUpperCase(),
+ TypeSpec.anonymousClassBuilder("$S", aClass.getSimpleName()).build()
+ );
+ });
+
+ entityTypeEnumBuilder.addMethod(
+ MethodSpec.constructorBuilder()
+ .addParameter(String.class, "value")
+ .addStatement("this.$N = $N", "value", "value")
+ .build()
+ );
+
+ entityTypeEnumBuilder.addMethod(
+ MethodSpec.methodBuilder("getValue")
+ .addModifiers(Modifier.PUBLIC)
+ .returns(String.class)
+ .addStatement("return value")
+ .build()
+ );
+
+ entityTypeEnumBuilder.addMethod(
+ MethodSpec.methodBuilder("fromString")
+ .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
+ .returns(ClassName.bestGuess("EntityType"))
+ .addParameter(String.class, "value")
+ .beginControlFlow("for (EntityType entityType : EntityType.values())")
+ .beginControlFlow("if (entityType.value.equals(value))")
+ .addStatement("return entityType")
+ .endControlFlow()
+ .endControlFlow()
+ .addStatement("throw new IllegalArgumentException(\"Unknown entity type type: \" + value)")
+ .build()
+ );
+
+ TypeSpec schemaHelperSpec = TypeSpec.classBuilder("EntityHelper")
+ .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
+ .addField(withoutPagingField)
+ .addMethod(supportPagingMethod)
+ .addMethods(methods)
+ .addType(entityTypeEnumBuilder.build())
+ .build();
+
+ JavaFile javaFile = JavaFile.builder("io.cdap.plugin.marketo.source.batch.entity", schemaHelperSpec)
+ .skipJavaLangImports(true)
+ .build();
+ System.out.println(javaFile.toString());
+ }
+
+ private static final List SIMPLE_TYPES = ImmutableList.of(Boolean.class, Integer.class, int.class,
+ boolean.class, String.class);
+
+ private static final List COLLECTION_TYPES = ImmutableList.of(List.class);
+
+ public static boolean isSimpleType(Field field) {
+ return SIMPLE_TYPES.contains(field.getType());
+ }
+
+ public static boolean isSimpleType(Class cls) {
+ return SIMPLE_TYPES.contains(cls);
+ }
+
+ public static String generateSchemaGetterMethod(Class cls) {
+ return "get" + capitalize(cls.getSimpleName()) + "Schema";
+ }
+
+ public static boolean isListType(Field field) {
+ return COLLECTION_TYPES.contains(field.getType());
+ }
+
+ public static boolean isComplexType(Field field) {
+ return !SIMPLE_TYPES.contains(field.getType()) && !COLLECTION_TYPES.contains(field.getType());
+ }
+
+ public static String simpleTypeToCdapType(Field field) {
+ if (field.getType().equals(Boolean.class) || field.getType().equals(boolean.class)) {
+ return "Schema.nullableOf(Schema.of(Schema.Type.BOOLEAN))";
+ } else if (field.getType().equals(String.class)) {
+ return "Schema.nullableOf(Schema.of(Schema.Type.STRING))";
+ } else if (field.getType().equals(Integer.class) || field.getType().equals(int.class)) {
+ return "Schema.nullableOf(Schema.of(Schema.Type.INT))";
+ } else {
+ throw new RuntimeException("Unsupported simple type");
+ }
+ }
+
+ public static String listTypeToCdapType(Field field) {
+ ParameterizedType integerListType = (ParameterizedType) field.getGenericType();
+ Class> listType = (Class>) integerListType.getActualTypeArguments()[0];
+ if (listType.equals(Boolean.class) || listType.equals(boolean.class)) {
+ return "SSchema.nullableOf(Schema.arrayOf(Schema.of(Schema.Type.BOOLEAN)))";
+ } else if (listType.equals(String.class)) {
+ return "Schema.nullableOf(Schema.arrayOf(Schema.of(Schema.Type.STRING)))";
+ } else if (listType.equals(Integer.class) || listType.equals(int.class)) {
+ return "Schema.nullableOf(Schema.arrayOf(Schema.of(Schema.Type.INT)))";
+ } else {
+ return "Schema.nullableOf(Schema.arrayOf(EntityHelper." + generateSchemaGetterMethod(listType) + "()))";
+ }
+ }
+
+ public static String findGetterMethod(Field field, Class cls) {
+ if (field.getType().equals(Boolean.class) || field.getType().equals(boolean.class)) {
+ String getterName = "get" + capitalize(field.getName());
+ try {
+ cls.getMethod(getterName);
+ return getterName;
+ } catch (NoSuchMethodException e) {
+ if (field.getName().startsWith("is")) {
+ String alternateGetter = "get" + capitalize(field.getName().substring(2));
+ try {
+ cls.getMethod(alternateGetter);
+ return alternateGetter;
+ } catch (NoSuchMethodException altE) {
+ throw new RuntimeException(altE);
+ }
+ }
+ }
+ } else {
+ String getterName = "get" + capitalize(field.getName());
+ try {
+ cls.getMethod(getterName);
+ return getterName;
+ } catch (NoSuchMethodException altE) {
+ throw new RuntimeException(altE);
+ }
+ }
+ throw new RuntimeException();
+ }
+
+ public static String capitalize(String str) {
+ return str.substring(0, 1).toUpperCase() + str.substring(1);
+ }
+
+ public static List getEntityClasses() throws IOException {
+ return ClassPath.from(Generate.class.getClassLoader())
+ .getTopLevelClasses("io.cdap.plugin.marketo.common.api.entities.asset").stream()
+ .map(ClassPath.ClassInfo::load)
+ .filter(Generate::isEntity)
+ .sorted(Comparator.comparing(Class::getSimpleName))
+ .collect(Collectors.toList());
+ }
+
+ public static List getTopLevelEntityClasses() throws IOException {
+ return ClassPath.from(Generate.class.getClassLoader())
+ .getTopLevelClasses("io.cdap.plugin.marketo.common.api.entities.asset").stream()
+ .map(ClassPath.ClassInfo::load)
+ .filter(Generate::isTopLevelEntity)
+ .sorted(Comparator.comparing(Class::getSimpleName))
+ .collect(Collectors.toList());
+ }
+
+ public static List getResponseClasses() throws IOException {
+ return ClassPath.from(Generate.class.getClassLoader())
+ .getTopLevelClasses("io.cdap.plugin.marketo.common.api.entities.asset").stream()
+ .map(ClassPath.ClassInfo::load)
+ .filter(Generate::isResponses)
+ .sorted(Comparator.comparing(Class::getSimpleName))
+ .collect(Collectors.toList());
+ }
+
+ public static boolean isEntity(Class cls) {
+ for (Annotation a : cls.getAnnotations()) {
+ if (a.annotationType().equals(Entity.class)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static boolean isResponses(Class cls) {
+ for (Annotation a : cls.getAnnotations()) {
+ if (a.annotationType().equals(Response.class)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static String getFetchUrl(Class cls) {
+ for (Annotation a : cls.getAnnotations()) {
+ if (a.annotationType().equals(Response.class)) {
+ Response r = (Response) a;
+ return r.fetchUrl();
+ }
+ }
+ throw new RuntimeException();
+ }
+
+ public static boolean getPaged(Class cls) {
+ for (Annotation a : cls.getAnnotations()) {
+ if (a.annotationType().equals(Response.class)) {
+ Response r = (Response) a;
+ return r.paged();
+ }
+ }
+ throw new RuntimeException();
+ }
+
+ public static boolean isNotPaged(Class cls) {
+ return !getPaged(cls);
+ }
+
+ public static Class getItemClsForResponseCls(Class cls) {
+ ParameterizedType baseT = (ParameterizedType) cls.getGenericSuperclass();
+ Class responseItemClass = (Class>) baseT.getActualTypeArguments()[0];
+ return responseItemClass;
+ }
+
+ public static boolean isTopLevelEntity(Class cls) {
+ for (Annotation a : cls.getAnnotations()) {
+ if (a.annotationType().equals(Entity.class)) {
+ Entity e = (Entity) a;
+ if (e.topLevel()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+}
diff --git a/docs/MarketoEntityPlugin-batchsource.md b/docs/MarketoEntityPlugin-batchsource.md
new file mode 100644
index 0000000..4db2a24
--- /dev/null
+++ b/docs/MarketoEntityPlugin-batchsource.md
@@ -0,0 +1,36 @@
+# Marketo Entity Batch Source
+
+Description
+-----------
+This plugin is used to query Leads or Activities entities for specified date range from Marketo.
+
+Properties
+----------
+### General
+
+**Reference Name:** Name used to uniquely identify this source for lineage, annotating metadata, etc.
+
+**Rest API endpoint:** Marketo rest API endpoint, unique for each client.
+
+**Entity Type:** Type of entity.
+### Authentication
+
+**Client ID:** Client ID.
+
+**Client Secret:** Client secret.
+
+### Advanced
+
+**Splits Count:** Parallel splits count.
+
+**Max Results Per Page:** Maximum number of records to retrieve by single request.
+
+API limits notes
+----------------
+All entities will be fetched at once.
+
+Total requests count depends on **Max Results Per Page**, leave it with default
+value(200 - maximum possible value) to minimize risk of reaching limits.
+
+Default request limit is 50000 per day, this will allow to fetch ~10.000.000 entities, but you must consider this limit
+if there are several pipelines or other applications that uses API.
\ No newline at end of file
diff --git a/docs/MarketoReportingPlugin-batchsource.md b/docs/MarketoReportingPlugin-batchsource.md
new file mode 100644
index 0000000..5451214
--- /dev/null
+++ b/docs/MarketoReportingPlugin-batchsource.md
@@ -0,0 +1,26 @@
+# Marketo Reporting Batch Source
+
+Description
+-----------
+This plugin is used to query Leads or Activities entities for specified date range from Marketo.
+
+Properties
+----------
+### General
+
+**Reference Name:** Name used to uniquely identify this source for lineage, annotating metadata, etc.
+
+**Rest API endpoint:** Marketo rest API endpoint, unique for each client.
+### Authentication
+
+**Client ID:** Client ID.
+
+**Client Secret:** Client secret.
+
+### Report
+
+**Report Type:** Type of report. One of 'leads' or 'activities'.
+
+**Start Date:** Start date of report. In ISO 8601 format(1997-07-16T19:20:30+01:00).
+
+**End Date:** End date of report. In ISO 8601 format(1997-07-16T19:20:30+01:00).
\ No newline at end of file
diff --git a/icons/MarketoEntityPlugin-batchsource.png b/icons/MarketoEntityPlugin-batchsource.png
new file mode 100644
index 0000000..4abe7e7
Binary files /dev/null and b/icons/MarketoEntityPlugin-batchsource.png differ
diff --git a/icons/MarketoReportingPlugin-batchsource.png b/icons/MarketoReportingPlugin-batchsource.png
new file mode 100644
index 0000000..4abe7e7
Binary files /dev/null and b/icons/MarketoReportingPlugin-batchsource.png differ
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..a1d3cbf
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,446 @@
+
+
+
+ 4.0.0
+
+ io.cdap.plugin
+ marketo-entity-plugin
+ 1.0.0-SNAPSHOT
+
+
+
+ sonatype
+ https://oss.sonatype.org/content/groups/public
+
+
+ sonatype-snapshots
+ https://oss.sonatype.org/content/repositories/snapshots
+
+
+
+
+ 6.1.0-SNAPSHOT
+ 2.8.0
+ 4.5.9
+ 2.3.0-SNAPSHOT
+ 2.1.3
+
+
+
+
+ io.cdap.cdap
+ cdap-api
+ ${cdap.version}
+ provided
+
+
+ io.cdap.cdap
+ cdap-etl-api
+ ${cdap.version}
+ provided
+
+
+ io.cdap.cdap
+ cdap-formats
+ ${cdap.version}
+
+
+ org.apache.avro
+ avro
+
+
+ io.thekraken
+ grok
+
+
+
+
+ io.cdap.plugin
+ hydrator-common
+ ${hydrator.version}
+
+
+ org.apache.hadoop
+ hadoop-common
+ ${hadoop.version}
+ provided
+
+
+ commons-logging
+ commons-logging
+
+
+ log4j
+ log4j
+
+
+ org.slf4j
+ slf4j-log4j12
+
+
+ org.apache.avro
+ avro
+
+
+ org.apache.zookeeper
+ zookeeper
+
+
+ com.google.guava
+ guava
+
+
+ jersey-core
+ com.sun.jersey
+
+
+ jersey-json
+ com.sun.jersey
+
+
+ jersey-server
+ com.sun.jersey
+
+
+ servlet-api
+ javax.servlet
+
+
+ org.mortbay.jetty
+ jetty
+
+
+ org.mortbay.jetty
+ jetty-util
+
+
+ jasper-compiler
+ tomcat
+
+
+ jasper-runtime
+ tomcat
+
+
+ jsp-api
+ javax.servlet.jsp
+
+
+ slf4j-api
+ org.slf4j
+
+
+
+
+ org.apache.hadoop
+ hadoop-mapreduce-client-core
+ ${hadoop.version}
+ provided
+
+
+ org.slf4j
+ slf4j-log4j12
+
+
+ com.google.inject.extensions
+ guice-servlet
+
+
+ com.sun.jersey
+ jersey-core
+
+
+ com.sun.jersey
+ jersey-server
+
+
+ com.sun.jersey
+ jersey-json
+
+
+ com.sun.jersey.contribs
+ jersey-guice
+
+
+ javax.servlet
+ servlet-api
+
+
+ com.google.guava
+ guava
+
+
+
+
+
+ io.cdap.cdap
+ cdap-etl-api-spark
+ ${cdap.version}
+ provided
+
+
+ org.apache.spark
+ spark-streaming_2.11
+ ${spark2.version}
+ provided
+
+
+ org.apache.spark
+ spark-core_2.11
+ ${spark2.version}
+ provided
+
+
+ org.slf4j
+ slf4j-log4j12
+
+
+ log4j
+ log4j
+
+
+ org.apache.hadoop
+ hadoop-client
+
+
+ com.esotericsoftware.reflectasm
+ reflectasm
+
+
+ org.apache.curator
+ curator-recipes
+
+
+
+ org.scala-lang
+ scala-compiler
+
+
+ org.scala-lang
+ scala-reflect
+
+
+ org.eclipse.jetty.orbit
+ javax.servlet
+
+
+
+ net.java.dev.jets3t
+ jets3t
+
+
+ asm
+ asm
+
+
+
+
+
+ org.apache.httpcomponents
+ httpclient
+ ${httpcomponents.version}
+
+
+ com.google.code.gson
+ gson
+ 2.8.6
+
+
+ org.apache.commons
+ commons-csv
+ 1.7
+
+
+ commons-io
+ commons-io
+ 2.6
+
+
+ org.awaitility
+ awaitility
+ 4.0.1
+
+
+ junit
+ junit
+ 4.12
+ test
+
+
+ com.github.tomakehurst
+ wiremock-jre8-standalone
+ 2.25.1
+ test
+
+
+ io.cdap.cdap
+ cdap-data-pipeline
+ ${cdap.version}
+ test
+
+
+ io.cdap.cdap
+ hydrator-test
+ ${cdap.version}
+ test
+
+
+
+
+
+
+ org.apache.rat
+ apache-rat-plugin
+ 0.13
+
+
+ org.apache.maven.doxia
+ doxia-core
+ 1.6
+
+
+ xerces
+ xercesImpl
+
+
+
+
+
+
+ rat-check
+ validate
+
+ check
+
+
+
+ LICENSE*.txt
+
+ *.rst
+ *.md
+ **/*.cdap
+ **/*.yaml
+ **/*.md
+ logs/**
+ .git/**
+ .idea/**
+ **/grok/patterns/**
+ conf/**
+ data/**
+ plugins/**
+ **/*.patch
+ **/logrotate.d/**
+ **/limits.d/**
+ **/*.json
+ **/*.json.template
+ **/MANIFEST.MF
+
+ **/org/apache/hadoop/**
+
+ **/resources/**
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-checkstyle-plugin
+ 2.17
+
+
+ validate
+ process-test-classes
+
+ checkstyle.xml
+ suppressions.xml
+ UTF-8
+ true
+ true
+ true
+ **/org/apache/cassandra/**,**/org/apache/hadoop/**
+
+
+ check
+
+
+
+
+
+ com.puppycrawl.tools
+ checkstyle
+ 8.18
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ 8
+ 8
+
+
+
+ io.cdap
+ cdap-maven-plugin
+ 1.1.0
+
+
+ system:cdap-data-pipeline[6.1.0-SNAPSHOT,7.0.0-SNAPSHOT)
+
+
+
+
+ create-artifact-config
+ prepare-package
+
+ create-plugin-json
+
+
+
+
+
+ org.apache.felix
+ maven-bundle-plugin
+ 3.5.0
+ true
+
+
+ <_exportcontents>io.cdap.plugin.marketo.*
+ *;inline=false;scope=compile
+ true
+ lib
+
+
+
+
+ package
+
+ bundle
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/java/io/cdap/plugin/marketo/common/api/Helpers.java b/src/main/java/io/cdap/plugin/marketo/common/api/Helpers.java
new file mode 100644
index 0000000..bda2387
--- /dev/null
+++ b/src/main/java/io/cdap/plugin/marketo/common/api/Helpers.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright © 2019 Cask Data, Inc.
+ *
+ * Licensed 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 io.cdap.plugin.marketo.common.api;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+import io.cdap.plugin.marketo.common.api.entities.DateRange;
+import org.apache.commons.io.IOUtils;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.utils.URIBuilder;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
+import java.time.OffsetDateTime;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Various helper methods.
+ */
+public class Helpers {
+ public static String streamToString(InputStream inputStream) {
+ try {
+ return IOUtils.toString(inputStream, StandardCharsets.UTF_8);
+ } catch (IOException e) {
+ throw new RuntimeException(String.format("Failed to read stream completely due to '%s'", e.getMessage()));
+ }
+ }
+
+ public static T streamToObject(InputStream inputStream, Class cls) {
+ return Marketo.GSON.fromJson(new InputStreamReader(inputStream), cls);
+ }
+
+ public static RuntimeException failForMethodAndUri(String method, URI uri, Exception ex) {
+ String message = ex.getMessage();
+ if (Strings.isNullOrEmpty(message)) {
+ if (ex.getCause() != null) {
+ message = ex.getCause().getMessage();
+ if (Strings.isNullOrEmpty(message)) {
+ message = "Unknown failure";
+ }
+ }
+ }
+
+ URIBuilder uriBuilder = new URIBuilder(uri);
+ List queryParameters = uriBuilder.getQueryParams();
+ queryParameters.removeIf(queryParameter -> queryParameter.getName().equals("access_token"));
+ uriBuilder.setParameters(queryParameters);
+ try {
+ String uriString = uriBuilder.build().toString();
+ return new RuntimeException(String.format("Failed '%s' '%s' - '%s'", method, uriString, message));
+ } catch (URISyntaxException e) {
+ // this will never happen since we rebuilding already validated uri, just make compiler happy
+ return new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Splits date range in 30 day ranges.
+ * Return date range as is if difference is less or equals to 30 days.
+ *
+ * @param beginDate
+ * @param endDate
+ * @return
+ */
+ public static List getDateRanges(String beginDate, String endDate) {
+ OffsetDateTime start = OffsetDateTime.parse(beginDate);
+ OffsetDateTime end = OffsetDateTime.parse(endDate);
+
+ if (start.compareTo(end) > 0) {
+ throw new IllegalArgumentException("Start date cannot be greater than the end date.");
+ }
+
+ int compareResult = start.plusDays(30).compareTo(end);
+ if (compareResult >= 0) {
+ // we are in range of 30 days, dates are okay
+ return ImmutableList.of(new DateRange(start.toString(), end.toString()));
+ } else {
+ List result = new ArrayList<>();
+ OffsetDateTime currentStart = start;
+
+ while (currentStart.compareTo(end) < 0) {
+ OffsetDateTime nextEnd = currentStart.plusDays(30);
+ result.add(new DateRange(currentStart.toString(),
+ min(nextEnd.minusSeconds(1), end).toString()));
+ currentStart = nextEnd;
+ }
+
+ return result;
+ }
+ }
+
+ public static > T min(T o1, T o2) {
+ if (o1.compareTo(o2) < 0) {
+ return o1;
+ } else {
+ return o2;
+ }
+ }
+}
diff --git a/src/main/java/io/cdap/plugin/marketo/common/api/Marketo.java b/src/main/java/io/cdap/plugin/marketo/common/api/Marketo.java
new file mode 100644
index 0000000..c49e389
--- /dev/null
+++ b/src/main/java/io/cdap/plugin/marketo/common/api/Marketo.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright © 2019 Cask Data, Inc.
+ *
+ * Licensed 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 io.cdap.plugin.marketo.common.api;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import io.cdap.plugin.marketo.common.api.entities.Warning;
+import io.cdap.plugin.marketo.common.api.entities.WarningDeserializer;
+import io.cdap.plugin.marketo.common.api.entities.activities.ActivitiesExport;
+import io.cdap.plugin.marketo.common.api.entities.activities.ActivitiesExportRequest;
+import io.cdap.plugin.marketo.common.api.entities.activities.ActivitiesExportResponse;
+import io.cdap.plugin.marketo.common.api.entities.activities.ActivityTypeResponse;
+import io.cdap.plugin.marketo.common.api.entities.leads.LeadsDescribeResponse;
+import io.cdap.plugin.marketo.common.api.entities.leads.LeadsExport;
+import io.cdap.plugin.marketo.common.api.entities.leads.LeadsExportRequest;
+import io.cdap.plugin.marketo.common.api.entities.leads.LeadsExportResponse;
+import io.cdap.plugin.marketo.common.api.job.ActivitiesExportJob;
+import io.cdap.plugin.marketo.common.api.job.LeadsExportJob;
+import org.awaitility.Awaitility;
+import org.awaitility.core.ConditionTimeoutException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.List;
+import java.util.Spliterator;
+import java.util.Spliterators;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+
+/**
+ * Class that expose marketo rest api endpoints.
+ */
+public class Marketo extends MarketoHttp {
+ private static final Logger LOG = LoggerFactory.getLogger(Marketo.class);
+ static final Gson GSON = new GsonBuilder()
+ .registerTypeAdapter(Warning.class, new WarningDeserializer())
+ .create();
+ public static final List ACTIVITY_FIELDS = ImmutableList.of("marketoGUID", "leadId", "activityDate",
+ "activityTypeId", "campaignId",
+ "primaryAttributeValueId",
+ "primaryAttributeValue", "attributes");
+ /**
+ * Job queue will be checked every 60 seconds.
+ */
+ private static final long JOB_QUEUE_POLL_INTERVAL = 60;
+ /**
+ * Wait for 10 seconds before trying to enqueue job, this will minimize chance of race condition.
+ */
+ private static final long JOB_QUEUE_POLL_DELAY = 10;
+
+ public Marketo(String marketoEndpoint, String clientId, String clientSecret) {
+ super(marketoEndpoint, clientId, clientSecret);
+ }
+
+ public List describeLeads() {
+ return StreamSupport.stream(Spliterators.spliteratorUnknownSize(
+ iteratePage(Urls.LEADS_DESCRIBE, LeadsDescribeResponse.class, LeadsDescribeResponse::getResult),
+ Spliterator.ORDERED), false).collect(Collectors.toList());
+ }
+
+ public List describeBuildInActivities() {
+ return StreamSupport.stream(Spliterators.spliteratorUnknownSize(
+ iteratePage(Urls.BUILD_IN_ACTIVITIES_TYPES, ActivityTypeResponse.class, ActivityTypeResponse::getResult),
+ Spliterator.ORDERED), false).collect(Collectors.toList());
+ }
+
+ public LeadsExportJob exportLeads(LeadsExportRequest request) {
+ LeadsExportResponse export = validatedPost(Urls.BULK_EXPORT_LEADS_CREATE, Collections.emptyMap(),
+ Marketo::streamToLeadsExport,
+ request,
+ GSON::toJson);
+ return new LeadsExportJob(export.singleExport(), this);
+ }
+
+ public LeadsExport leadsExportJobStatus(String jobId) {
+ LeadsExportResponse currentResp = validatedGet(
+ String.format(Urls.BULK_EXPORT_LEADS_STATUS, jobId),
+ Collections.emptyMap(), Marketo::streamToLeadsExport);
+ return currentResp.singleExport();
+ }
+
+ public ActivitiesExportJob exportActivities(ActivitiesExportRequest request) {
+ ActivitiesExportResponse export = validatedPost(Urls.BULK_EXPORT_ACTIVITIES_CREATE, Collections.emptyMap(),
+ Marketo::streamToActivitiesExport,
+ request,
+ GSON::toJson);
+ return new ActivitiesExportJob(export.singleExport(), this);
+ }
+
+ public ActivitiesExport activitiesExportJobStatus(String jobId) {
+ ActivitiesExportResponse currentResp = validatedGet(
+ String.format(Urls.BULK_EXPORT_ACTIVITIES_STATUS, jobId),
+ Collections.emptyMap(), Marketo::streamToActivitiesExport);
+ return currentResp.singleExport();
+ }
+
+ /**
+ * Waits until bulk extract queue has available slot and executes given action.
+ *
+ * @param action action to execute once slot is available
+ * @param timeoutSeconds timeout in seconds
+ */
+ public void onBulkExtractQueueAvailable(Callable action, long timeoutSeconds) {
+ try {
+ Awaitility.given()
+ .ignoreException(TooManyJobsException.class) // ignore exception in case another reader took our slot
+ .atMost(timeoutSeconds, TimeUnit.SECONDS)
+ .pollInterval(JOB_QUEUE_POLL_INTERVAL, TimeUnit.SECONDS)
+ .pollDelay(JOB_QUEUE_POLL_DELAY, TimeUnit.SECONDS)
+ .until(action);
+ } catch (ConditionTimeoutException ex) {
+ throw new RuntimeException("Failed to get slot in bulk export queue due to timeout");
+ }
+ }
+
+ public static LeadsExportResponse streamToLeadsExport(InputStream inputStream) {
+ return Helpers.streamToObject(inputStream, LeadsExportResponse.class);
+ }
+
+ public static ActivitiesExportResponse streamToActivitiesExport(InputStream inputStream) {
+ return Helpers.streamToObject(inputStream, ActivitiesExportResponse.class);
+ }
+
+ /**
+ * Check if job can be enqueued.
+ *
+ * @return true, if job can be enqueued
+ */
+ public boolean canEnqueueJob() {
+ LeadsExportResponse leadsExportResponseJobs = validatedGet(Urls.BULK_EXPORT_LEADS_LIST,
+ ImmutableMap.of("status", "queued,processing"),
+ Marketo::streamToLeadsExport
+ );
+
+ int jobsInQueue = leadsExportResponseJobs.getResult().size();
+
+ ActivitiesExportResponse activitiesExportResponceJobs = validatedGet(Urls.BULK_EXPORT_ACTIVITIES_LIST,
+ ImmutableMap.of("status", "queued,processing"),
+ Marketo::streamToActivitiesExport
+ );
+ jobsInQueue += activitiesExportResponceJobs.getResult().size();
+
+ LOG.debug("Jobs in queue: {}", jobsInQueue);
+
+ return jobsInQueue < 10;
+ }
+}
diff --git a/src/main/java/io/cdap/plugin/marketo/common/api/MarketoHttp.java b/src/main/java/io/cdap/plugin/marketo/common/api/MarketoHttp.java
new file mode 100644
index 0000000..2b4df11
--- /dev/null
+++ b/src/main/java/io/cdap/plugin/marketo/common/api/MarketoHttp.java
@@ -0,0 +1,241 @@
+/*
+ * Copyright © 2019 Cask Data, Inc.
+ *
+ * Licensed 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 io.cdap.plugin.marketo.common.api;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableMap;
+import com.google.gson.Gson;
+import io.cdap.plugin.marketo.common.api.entities.BaseResponse;
+import io.cdap.plugin.marketo.common.api.entities.Error;
+import io.cdap.plugin.marketo.common.api.entities.MarketoToken;
+import org.apache.commons.io.IOUtils;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.protocol.HttpClientContext;
+import org.apache.http.client.utils.URIBuilder;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+/**
+ * Class that encapsulates common http functions for marketo rest api.
+ */
+class MarketoHttp {
+ private static final Logger LOG = LoggerFactory.getLogger(Marketo.class);
+ private static final Gson GSON = new Gson();
+ private String marketoEndpoint;
+ private String clientId;
+ private String clientSecret;
+ private MarketoToken token;
+ private HttpClientContext httpClientContext = HttpClientContext.create();
+
+ MarketoHttp(String marketoEndpoint, String clientId, String clientSecret) {
+ this.marketoEndpoint = marketoEndpoint;
+ this.clientId = clientId;
+ this.clientSecret = clientSecret;
+ token = refreshToken();
+ }
+
+ private T getPage(String queryUrl, Class pageClass, Map parameters) {
+ return validatedGet(queryUrl, parameters,
+ inputStream -> Helpers.streamToObject(inputStream, pageClass));
+ }
+
+ private T getPage(String queryUrl, Class pageClass) {
+ return validatedGet(queryUrl, Collections.emptyMap(),
+ inputStream -> Helpers.streamToObject(inputStream, pageClass));
+ }
+
+ T getNextPage(T currentPage, String queryUrl, Class pageClass) {
+ if (!Strings.isNullOrEmpty(currentPage.getNextPageToken())) {
+ return validatedGet(queryUrl,
+ ImmutableMap.of("nextPageToken", currentPage.getNextPageToken()),
+ inputStream -> Helpers.streamToObject(inputStream, pageClass));
+ }
+ return null;
+ }
+
+ public MarketoPageIterator iteratePage(String queryUrl,
+ Class pageClass,
+ Function> resultsGetter,
+ Map parameters) {
+ return new MarketoPageIterator<>(getPage(queryUrl, pageClass, parameters), this, queryUrl, pageClass,
+ resultsGetter);
+ }
+
+ public MarketoPageIterator iteratePage(String queryUrl,
+ Class pageClass,
+ Function> resultsGetter) {
+ return new MarketoPageIterator<>(getPage(queryUrl, pageClass), this, queryUrl, pageClass, resultsGetter);
+ }
+
+ T validatedGet(String queryUrl, Map parameters,
+ Function deserializer) {
+ String logUri = "GET " + buildUri(queryUrl, parameters, false).toString();
+ return retryableValidate(logUri, () -> {
+ URI queryUri = buildUri(queryUrl, parameters, true);
+ return get(queryUri, deserializer);
+ });
+ }
+
+ public T validatedPost(String queryUrl, Map parameters,
+ Function deserializer,
+ B body, Function qSerializer) {
+ String logUri = "POST " + buildUri(queryUrl, parameters, false).toString();
+ return retryableValidate(logUri, () -> {
+ URI queryUri = buildUri(queryUrl, parameters, true);
+ return post(queryUri, deserializer, body, qSerializer);
+ });
+ }
+
+ // code: 1029, message: Too many jobs (10) in queue
+ private T retryableValidate(String logUri, Supplier tryQuery) {
+ T result = tryQuery.get();
+ // check for expired token
+ if (!result.isSuccess()) {
+ for (Error error : result.getErrors()) {
+ if (error.getCode() == 602 && error.getMessage().equals("Access token expired")) {
+ // refresh token and retry
+ token = refreshToken();
+ LOG.info("Refreshed token");
+ return tryQuery.get();
+ }
+ }
+ }
+
+ // log warnings if required
+ if (result.getWarnings().size() > 0) {
+ String warnings = result.getWarnings().stream()
+ .map(error -> String.format("code: %s, message: %s", error.getCode(), error.getMessage()))
+ .collect(Collectors.joining("; "));
+ LOG.warn("Warnings when calling '{}' - {}", logUri, warnings);
+ }
+
+ if (!result.isSuccess()) {
+ String msg = String.format("Errors when calling '%s'", logUri);
+ // log errors if required
+ if (result.getErrors().size() > 0) {
+ String errors = result.getErrors().stream()
+ .map(error -> String.format("code: %s, message: %s", error.getCode(), error.getMessage()))
+ .collect(Collectors.joining("; "));
+ msg = msg + " - " + errors;
+ LOG.error(msg);
+ }
+ throw mapErrorsToException(result.getErrors(), msg);
+ }
+ return result;
+ }
+
+ private RuntimeException mapErrorsToException(List errors, String defaultMessage) {
+ if (errors.size() == 1) {
+ Error e = errors.get(0);
+ String message = e.getMessage();
+ if (e.getCode() == 1029 && message != null && message.contains("many jobs")) {
+ return new TooManyJobsException();
+ } else {
+ // this error don't require specific handling
+ return new RuntimeException(defaultMessage);
+ }
+ } else {
+ // something outstanding happened and we have more than one error, we can't handle this in specific way
+ return new RuntimeException(defaultMessage);
+ }
+ }
+
+ public T get(URI uri, Function deserializer) {
+ try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
+ HttpGet request = new HttpGet(uri);
+ try (CloseableHttpResponse response = httpClient.execute(request, httpClientContext)) {
+ checkResponseCode(response);
+ return deserializer.apply(response.getEntity().getContent());
+ }
+ } catch (Exception e) {
+ throw Helpers.failForMethodAndUri("GET", uri, e);
+ }
+ }
+
+ private T post(URI uri, Function respDeserializer, B body, Function qSerializer) {
+ try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
+ HttpPost request = new HttpPost(uri);
+ if (body != null) {
+ Objects.requireNonNull(qSerializer, "body serializer must be specified with body");
+ request.setEntity(new StringEntity(qSerializer.apply(body), ContentType.APPLICATION_JSON));
+ }
+ try (CloseableHttpResponse response = httpClient.execute(request, httpClientContext)) {
+ checkResponseCode(response);
+ return respDeserializer.apply(response.getEntity().getContent());
+ }
+ } catch (Exception e) {
+ throw Helpers.failForMethodAndUri("POST", uri, e);
+ }
+ }
+
+ private static void checkResponseCode(CloseableHttpResponse response) throws IOException {
+ int statusCode = response.getStatusLine().getStatusCode();
+ if (statusCode >= 300) {
+ String responseBody = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
+ throw new RuntimeException(String.format("Http code '%s', response '%s'", statusCode, responseBody));
+ }
+ }
+
+ public URI buildUri(String queryUrl, Map parameters) {
+ return buildUri(queryUrl, parameters, true);
+ }
+
+ URI buildUri(String queryUrl, Map parameters, boolean includeToken) {
+ try {
+ URIBuilder builder = new URIBuilder(marketoEndpoint + queryUrl);
+ parameters.forEach(builder::setParameter);
+ if (includeToken) {
+ builder.setParameter("access_token", token.getAccessToken());
+ }
+ return builder.build();
+ } catch (URISyntaxException e) {
+ throw new IllegalArgumentException(String.format("'%s' is invalid URI", marketoEndpoint + queryUrl));
+ }
+ }
+
+ MarketoToken getCurrentToken() {
+ return this.token;
+ }
+
+ private MarketoToken refreshToken() {
+ LOG.debug("Requesting marketo token");
+ URI getTokenUri = buildUri("/identity/oauth/token",
+ ImmutableMap.of("grant_type", "client_credentials", "client_id", clientId,
+ "client_secret", clientSecret), false);
+ return get(getTokenUri, inputStream -> GSON.fromJson(new InputStreamReader(inputStream), MarketoToken.class));
+ }
+}
diff --git a/src/main/java/io/cdap/plugin/marketo/common/api/MarketoPageIterator.java b/src/main/java/io/cdap/plugin/marketo/common/api/MarketoPageIterator.java
new file mode 100644
index 0000000..1de07bf
--- /dev/null
+++ b/src/main/java/io/cdap/plugin/marketo/common/api/MarketoPageIterator.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright © 2019 Cask Data, Inc.
+ *
+ * Licensed 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 io.cdap.plugin.marketo.common.api;
+
+import io.cdap.plugin.marketo.common.api.entities.BaseResponse;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.function.Function;
+
+/**
+ * Marketo page iterator.
+ *
+ * @param type of page response
+ * @param type of page item entity
+ */
+public class MarketoPageIterator implements Iterator {
+ private T currentPage;
+ private MarketoHttp marketo;
+ private String queryUrl;
+ private Class pageClass;
+ private Function> resultsGetter;
+ private Iterator currentPageResultIterator;
+
+ MarketoPageIterator(T page, MarketoHttp marketo, String queryUrl, Class pageClass,
+ Function> resultsGetter) {
+ this.currentPage = page;
+ this.marketo = marketo;
+ this.queryUrl = queryUrl;
+ this.pageClass = pageClass;
+ this.resultsGetter = resultsGetter;
+ currentPageResultIterator = resultsGetter.apply(this.currentPage).iterator();
+ }
+
+ @Override
+ public boolean hasNext() {
+ if (currentPageResultIterator.hasNext()) {
+ return true;
+ } else {
+ T nextPage = marketo.getNextPage(currentPage, queryUrl, pageClass);
+ if (nextPage != null) {
+ currentPage = nextPage;
+ currentPageResultIterator = resultsGetter.apply(this.currentPage).iterator();
+ return hasNext();
+ } else {
+ return false;
+ }
+ }
+ }
+
+ @Override
+ public I next() {
+ if (hasNext()) {
+ return currentPageResultIterator.next();
+ } else {
+ throw new NoSuchElementException();
+ }
+ }
+}
diff --git a/src/main/java/io/cdap/plugin/marketo/common/api/TooManyJobsException.java b/src/main/java/io/cdap/plugin/marketo/common/api/TooManyJobsException.java
new file mode 100644
index 0000000..ca23321
--- /dev/null
+++ b/src/main/java/io/cdap/plugin/marketo/common/api/TooManyJobsException.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright © 2019 Cask Data, Inc.
+ *
+ * Licensed 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 io.cdap.plugin.marketo.common.api;
+
+/**
+ * Exception thrown if too many jobs already queued.
+ */
+public class TooManyJobsException extends RuntimeException {
+}
diff --git a/src/main/java/io/cdap/plugin/marketo/common/api/Urls.java b/src/main/java/io/cdap/plugin/marketo/common/api/Urls.java
new file mode 100644
index 0000000..32479bb
--- /dev/null
+++ b/src/main/java/io/cdap/plugin/marketo/common/api/Urls.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright © 2019 Cask Data, Inc.
+ *
+ * Licensed 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 io.cdap.plugin.marketo.common.api;
+
+/**
+ * Marketo API urls.
+ */
+public class Urls {
+ public static final String LEADS_DESCRIBE = "/rest/v1/leads/describe.json";
+ public static final String BULK_EXPORT_LEADS_LIST = "/bulk/v1/leads/export.json";
+ public static final String BULK_EXPORT_LEADS_CREATE = "/bulk/v1/leads/export/create.json";
+ public static final String BULK_EXPORT_LEADS_ENQUEUE = "/bulk/v1/leads/export/%s/enqueue.json";
+ public static final String BULK_EXPORT_LEADS_STATUS = "/bulk/v1/leads/export/%s/status.json";
+ public static final String BULK_EXPORT_LEADS_FILE = "/bulk/v1/leads/export/%s/file.json";
+ public static final String BULK_EXPORT_ACTIVITIES_LIST = "/bulk/v1/activities/export.json";
+ public static final String BULK_EXPORT_ACTIVITIES_CREATE = "/bulk/v1/activities/export/create.json";
+ public static final String BULK_EXPORT_ACTIVITIES_ENQUEUE = "/bulk/v1/activities/export/%s/enqueue.json";
+ public static final String BULK_EXPORT_ACTIVITIES_STATUS = "/bulk/v1/activities/export/%s/status.json";
+ public static final String BULK_EXPORT_ACTIVITIES_FILE = "/bulk/v1/activities/export/%s/file.json";
+ public static final String BUILD_IN_ACTIVITIES_TYPES = "/rest/v1/activities/types.json";
+}
diff --git a/src/main/java/io/cdap/plugin/marketo/common/api/entities/BaseResponse.java b/src/main/java/io/cdap/plugin/marketo/common/api/entities/BaseResponse.java
new file mode 100644
index 0000000..2f679ca
--- /dev/null
+++ b/src/main/java/io/cdap/plugin/marketo/common/api/entities/BaseResponse.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright © 2019 Cask Data, Inc.
+ *
+ * Licensed 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 io.cdap.plugin.marketo.common.api.entities;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Represents common parts for all responses.
+ */
+public class BaseResponse {
+
+ private boolean success = false;
+ private List errors = Collections.emptyList();
+ private List warnings = Collections.emptyList();
+ private String requestId;
+ private boolean moreResult = false;
+ private String nextPageToken;
+
+ public boolean isSuccess() {
+ return success;
+ }
+
+ public void setSuccess(boolean success) {
+ this.success = success;
+ }
+
+ public List getErrors() {
+ return errors;
+ }
+
+ public void setErrors(List errors) {
+ this.errors = errors;
+ }
+
+ public List getWarnings() {
+ return warnings;
+ }
+
+ public void setWarnings(List warnings) {
+ this.warnings = warnings;
+ }
+
+ public String getRequestId() {
+ return requestId;
+ }
+
+ public void setRequestId(String requestId) {
+ this.requestId = requestId;
+ }
+
+ public boolean isMoreResult() {
+ return moreResult;
+ }
+
+ public void setMoreResult(boolean moreResult) {
+ this.moreResult = moreResult;
+ }
+
+ public String getNextPageToken() {
+ return nextPageToken;
+ }
+
+ public void setNextPageToken(String nextPageToken) {
+ this.nextPageToken = nextPageToken;
+ }
+}
diff --git a/src/main/java/io/cdap/plugin/marketo/common/api/entities/DateRange.java b/src/main/java/io/cdap/plugin/marketo/common/api/entities/DateRange.java
new file mode 100644
index 0000000..9e71503
--- /dev/null
+++ b/src/main/java/io/cdap/plugin/marketo/common/api/entities/DateRange.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright © 2019 Cask Data, Inc.
+ *
+ * Licensed 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 io.cdap.plugin.marketo.common.api.entities;
+
+/**
+ * Represents date range.
+ */
+public class DateRange {
+ String endAt;
+ String startAt;
+
+ public DateRange() {
+
+ }
+
+ public DateRange(String startAt, String endAt) {
+ this.endAt = endAt;
+ this.startAt = startAt;
+ }
+
+ public String getEndAt() {
+ return endAt;
+ }
+
+ public String getStartAt() {
+ return startAt;
+ }
+
+ @Override
+ public String toString() {
+ return startAt + " -- " + endAt;
+ }
+}
diff --git a/src/main/java/io/cdap/plugin/marketo/common/api/entities/Error.java b/src/main/java/io/cdap/plugin/marketo/common/api/entities/Error.java
new file mode 100644
index 0000000..594e9cb
--- /dev/null
+++ b/src/main/java/io/cdap/plugin/marketo/common/api/entities/Error.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright © 2019 Cask Data, Inc.
+ *
+ * Licensed 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 io.cdap.plugin.marketo.common.api.entities;
+
+/**
+ * Represents error message.
+ */
+public class Error {
+ private int code;
+ private String message;
+
+ public Error(int code, String message) {
+ this.code = code;
+ this.message = message;
+ }
+
+ public Error() {
+ }
+
+ public int getCode() {
+ return code;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("code: %d, message: %s", code, message);
+ }
+}
diff --git a/src/main/java/io/cdap/plugin/marketo/common/api/entities/MarketoToken.java b/src/main/java/io/cdap/plugin/marketo/common/api/entities/MarketoToken.java
new file mode 100644
index 0000000..258d93f
--- /dev/null
+++ b/src/main/java/io/cdap/plugin/marketo/common/api/entities/MarketoToken.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright © 2019 Cask Data, Inc.
+ *
+ * Licensed 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 io.cdap.plugin.marketo.common.api.entities;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * Represents marketo token response.
+ */
+public class MarketoToken {
+ @SerializedName("access_token")
+ private String accessToken;
+ private String scope;
+ @SerializedName("expires_in")
+ private String expiresIn;
+ @SerializedName("token_type")
+ private String tokenType;
+
+ public MarketoToken(String accessToken, String scope, String expiresIn, String tokenType) {
+ this.accessToken = accessToken;
+ this.scope = scope;
+ this.expiresIn = expiresIn;
+ this.tokenType = tokenType;
+ }
+
+ public String getAccessToken() {
+ return accessToken;
+ }
+
+ public String getScope() {
+ return scope;
+ }
+
+ public String getExpiresIn() {
+ return expiresIn;
+ }
+
+ public String getTokenType() {
+ return tokenType;
+ }
+}
diff --git a/src/main/java/io/cdap/plugin/marketo/common/api/entities/Warning.java b/src/main/java/io/cdap/plugin/marketo/common/api/entities/Warning.java
new file mode 100644
index 0000000..b1167fc
--- /dev/null
+++ b/src/main/java/io/cdap/plugin/marketo/common/api/entities/Warning.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright © 2019 Cask Data, Inc.
+ *
+ * Licensed 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 io.cdap.plugin.marketo.common.api.entities;
+
+/**
+ * Represents warning message.
+ */
+public class Warning {
+ private int code;
+ private String message;
+
+ public Warning(int code, String message) {
+ this.code = code;
+ this.message = message;
+ }
+
+ public Warning() {
+ }
+
+ public int getCode() {
+ return code;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("code: %d, message: %s", code, message);
+ }
+}
diff --git a/src/main/java/io/cdap/plugin/marketo/common/api/entities/WarningDeserializer.java b/src/main/java/io/cdap/plugin/marketo/common/api/entities/WarningDeserializer.java
new file mode 100644
index 0000000..b5b013d
--- /dev/null
+++ b/src/main/java/io/cdap/plugin/marketo/common/api/entities/WarningDeserializer.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright © 2019 Cask Data, Inc.
+ *
+ * Licensed 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 io.cdap.plugin.marketo.common.api.entities;
+
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+
+import java.lang.reflect.Type;
+
+/**
+ * Deserializer for warning messages that can handle simple string warning messages and code+message warnings.
+ */
+public class WarningDeserializer implements JsonDeserializer {
+ @Override
+ public Warning deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext)
+ throws JsonParseException {
+ if (jsonElement.isJsonPrimitive()) {
+ return new Warning(-1, jsonElement.getAsString());
+ } else if (jsonElement.isJsonObject()) {
+ JsonObject obj = jsonElement.getAsJsonObject();
+ int code = -1;
+ String message = "";
+
+ if (obj.has("code")) {
+ code = obj.get("code").getAsInt();
+ }
+
+ if (obj.has("message")) {
+ message = obj.get("message").getAsString();
+ }
+
+ return new Warning(code, message);
+ } else {
+ throw new RuntimeException("Failed to deserialize warning message: " + jsonElement.toString());
+ }
+ }
+}
diff --git a/src/main/java/io/cdap/plugin/marketo/common/api/entities/activities/ActivitiesExport.java b/src/main/java/io/cdap/plugin/marketo/common/api/entities/activities/ActivitiesExport.java
new file mode 100644
index 0000000..2cfc72e
--- /dev/null
+++ b/src/main/java/io/cdap/plugin/marketo/common/api/entities/activities/ActivitiesExport.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright © 2019 Cask Data, Inc.
+ *
+ * Licensed 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 io.cdap.plugin.marketo.common.api.entities.activities;
+
+/**
+ * Represents export response item.
+ */
+public class ActivitiesExport {
+ String createdAt;
+ String errorMsg;
+ String exportId;
+ int fileSize;
+ String fileChecksum;
+ String finishedAt;
+ String format;
+ int numberOfRecords;
+ String queuedAt;
+ String startedAt;
+ String status;
+
+ public String getCreatedAt() {
+ return createdAt;
+ }
+
+ public String getErrorMsg() {
+ return errorMsg;
+ }
+
+ public String getExportId() {
+ return exportId;
+ }
+
+ public int getFileSize() {
+ return fileSize;
+ }
+
+ public String getFileChecksum() {
+ return fileChecksum;
+ }
+
+ public String getFinishedAt() {
+ return finishedAt;
+ }
+
+ public String getFormat() {
+ return format;
+ }
+
+ public int getNumberOfRecords() {
+ return numberOfRecords;
+ }
+
+ public String getQueuedAt() {
+ return queuedAt;
+ }
+
+ public String getStartedAt() {
+ return startedAt;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+}
diff --git a/src/main/java/io/cdap/plugin/marketo/common/api/entities/activities/ActivitiesExportRequest.java b/src/main/java/io/cdap/plugin/marketo/common/api/entities/activities/ActivitiesExportRequest.java
new file mode 100644
index 0000000..31fc681
--- /dev/null
+++ b/src/main/java/io/cdap/plugin/marketo/common/api/entities/activities/ActivitiesExportRequest.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright © 2019 Cask Data, Inc.
+ *
+ * Licensed 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 io.cdap.plugin.marketo.common.api.entities.activities;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Represents activities bulk export request.
+ */
+public class ActivitiesExportRequest {
+
+ Map columnHeaderNames = null;
+ List fields = null;
+ ExportActivityFilter filter = null;
+ String format = "CSV";
+
+ public ActivitiesExportRequest(List fields, ExportActivityFilter filter) {
+ this.fields = fields;
+ this.filter = filter;
+ }
+}
diff --git a/src/main/java/io/cdap/plugin/marketo/common/api/entities/activities/ActivitiesExportResponse.java b/src/main/java/io/cdap/plugin/marketo/common/api/entities/activities/ActivitiesExportResponse.java
new file mode 100644
index 0000000..0b19058
--- /dev/null
+++ b/src/main/java/io/cdap/plugin/marketo/common/api/entities/activities/ActivitiesExportResponse.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright © 2019 Cask Data, Inc.
+ *
+ * Licensed 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 io.cdap.plugin.marketo.common.api.entities.activities;
+
+import io.cdap.plugin.marketo.common.api.entities.BaseResponse;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Represents activities bulk export response.
+ */
+public class ActivitiesExportResponse extends BaseResponse {
+
+ List result = Collections.emptyList();
+
+ public ActivitiesExport singleExport() {
+ if (result.size() != 1) {
+ throw new IllegalStateException(
+ String.format("Expected single export job result, but found '%s' results.", result.size()));
+ }
+ return result.get(0);
+ }
+
+ public List getResult() {
+ return result;
+ }
+
+}
diff --git a/src/main/java/io/cdap/plugin/marketo/common/api/entities/activities/ActivityTypeResponse.java b/src/main/java/io/cdap/plugin/marketo/common/api/entities/activities/ActivityTypeResponse.java
new file mode 100644
index 0000000..b853484
--- /dev/null
+++ b/src/main/java/io/cdap/plugin/marketo/common/api/entities/activities/ActivityTypeResponse.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright © 2019 Cask Data, Inc.
+ *
+ * Licensed 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 io.cdap.plugin.marketo.common.api.entities.activities;
+
+import io.cdap.plugin.marketo.common.api.entities.BaseResponse;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Activity type response.
+ */
+public class ActivityTypeResponse extends BaseResponse {
+ /**
+ * Activity type attribute.
+ */
+ public static class ActivityTypeAttribute {
+ private String apiName;
+ private String dataType;
+ private String name;
+
+ public String getApiName() {
+ return apiName;
+ }
+
+ public String getDataType() {
+ return dataType;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s(%s, %s)", getApiName(), getDataType(), getName());
+ }
+ }
+
+ /**
+ * Attribute type.
+ */
+ public static class ActivityType {
+ private String apiName;
+ private List attributes;
+ private String description;
+ private Integer id;
+ private String name;
+ private ActivityTypeAttribute primaryAttribute;
+
+ public String getApiName() {
+ return apiName;
+ }
+
+ public List getAttributes() {
+ return attributes;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public Integer getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public ActivityTypeAttribute getPrimaryAttribute() {
+ return primaryAttribute;
+ }
+ }
+
+ List result = Collections.emptyList();
+
+ public List getResult() {
+ return result;
+ }
+}
diff --git a/src/main/java/io/cdap/plugin/marketo/common/api/entities/activities/ExportActivityFilter.java b/src/main/java/io/cdap/plugin/marketo/common/api/entities/activities/ExportActivityFilter.java
new file mode 100644
index 0000000..5e225cf
--- /dev/null
+++ b/src/main/java/io/cdap/plugin/marketo/common/api/entities/activities/ExportActivityFilter.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright © 2019 Cask Data, Inc.
+ *
+ * Licensed 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 io.cdap.plugin.marketo.common.api.entities.activities;
+
+import io.cdap.plugin.marketo.common.api.entities.DateRange;
+
+import java.util.List;
+
+/**
+ * Represents request filter.
+ */
+public class ExportActivityFilter {
+ private List activityTypeIds;
+ private DateRange createdAt;
+
+ /**
+ * Builder.
+ */
+ public static class Builder {
+
+ private List activityTypeIds = null;
+ private DateRange createdAt = null;
+
+ public Builder() {
+ }
+
+ Builder(List activityTypeIds, DateRange createdAt) {
+ this.activityTypeIds = activityTypeIds;
+ this.createdAt = createdAt;
+ }
+
+ public Builder activityTypeIds(List activityTypeIds) {
+ this.activityTypeIds = activityTypeIds;
+ return Builder.this;
+ }
+
+ public Builder addActivityTypeIds(Integer activityTypeIds) {
+ this.activityTypeIds.add(activityTypeIds);
+ return Builder.this;
+ }
+
+ public Builder createdAt(DateRange createdAt) {
+ this.createdAt = createdAt;
+ return Builder.this;
+ }
+
+ public ExportActivityFilter build() {
+
+ return new ExportActivityFilter(this);
+ }
+ }
+
+ private ExportActivityFilter(Builder builder) {
+ this.activityTypeIds = builder.activityTypeIds;
+ this.createdAt = builder.createdAt;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+}
diff --git a/src/main/java/io/cdap/plugin/marketo/common/api/entities/asset/Email.java b/src/main/java/io/cdap/plugin/marketo/common/api/entities/asset/Email.java
new file mode 100644
index 0000000..d7da1d2
--- /dev/null
+++ b/src/main/java/io/cdap/plugin/marketo/common/api/entities/asset/Email.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright © 2019 Cask Data, Inc.
+ *
+ * Licensed 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 io.cdap.plugin.marketo.common.api.entities.asset;
+
+import io.cdap.plugin.marketo.common.api.entities.asset.gen.Entity;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Email entity.
+ */
+@Entity(topLevel = true)
+public class Email {
+ String createdAt;
+ String description;
+ FolderDescriptor folder;
+ EmailField fromEmail;
+ EmailField fromName;
+ Integer id;
+ String name;
+ Boolean operational;
+ Boolean publishToMSI;
+ EmailField replyEmail;
+ String status;
+ EmailField subject;
+ Integer template;
+ Boolean textOnly;
+ String updatedAt;
+ String url;
+ Integer version;
+ Boolean webView;
+ String workspace;
+ Boolean autoCopyToText;
+ List ccFields = Collections.emptyList();
+
+ public String getCreatedAt() {
+ return createdAt;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public FolderDescriptor getFolder() {
+ return folder;
+ }
+
+ public EmailField getFromEmail() {
+ return fromEmail;
+ }
+
+ public EmailField getFromName() {
+ return fromName;
+ }
+
+ public Integer getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Boolean getOperational() {
+ return operational;
+ }
+
+ public Boolean getPublishToMSI() {
+ return publishToMSI;
+ }
+
+ public EmailField getReplyEmail() {
+ return replyEmail;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ public EmailField getSubject() {
+ return subject;
+ }
+
+ public Integer getTemplate() {
+ return template;
+ }
+
+ public Boolean getTextOnly() {
+ return textOnly;
+ }
+
+ public String getUpdatedAt() {
+ return updatedAt;
+ }
+
+ public String getUrl() {
+ return url;
+ }
+
+ public Integer getVersion() {
+ return version;
+ }
+
+ public Boolean getWebView() {
+ return webView;
+ }
+
+ public String getWorkspace() {
+ return workspace;
+ }
+
+ public Boolean getAutoCopyToText() {
+ return autoCopyToText;
+ }
+
+ public List getCcFields() {
+ return ccFields;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Builder for Email entity.
+ */
+ public static class Builder {
+
+ private String createdAt;
+ private String description;
+ private FolderDescriptor folder;
+ private EmailField fromEmail;
+ private EmailField fromName;
+ private Integer id;
+ private String name;
+ private Boolean operational;
+ private Boolean publishToMSI;
+ private EmailField replyEmail;
+ private String status;
+ private EmailField subject;
+ private Integer template;
+ private Boolean textOnly;
+ private String updatedAt;
+ private String url;
+ private Integer version;
+ private Boolean webView;
+ private String workspace;
+ private Boolean autoCopyToText;
+ private List ccFields = new ArrayList<>();
+
+ public Builder() {
+ }
+
+ public Builder createdAt(String createdAt) {
+ this.createdAt = createdAt;
+ return Builder.this;
+ }
+
+ public Builder description(String description) {
+ this.description = description;
+ return Builder.this;
+ }
+
+ public Builder folder(FolderDescriptor folder) {
+ this.folder = folder;
+ return Builder.this;
+ }
+
+ public Builder fromEmail(EmailField fromEmail) {
+ this.fromEmail = fromEmail;
+ return Builder.this;
+ }
+
+ public Builder fromName(EmailField fromName) {
+ this.fromName = fromName;
+ return Builder.this;
+ }
+
+ public Builder id(Integer id) {
+ this.id = id;
+ return Builder.this;
+ }
+
+ public Builder name(String name) {
+ this.name = name;
+ return Builder.this;
+ }
+
+ public Builder operational(Boolean operational) {
+ this.operational = operational;
+ return Builder.this;
+ }
+
+ public Builder publishToMSI(Boolean publishToMSI) {
+ this.publishToMSI = publishToMSI;
+ return Builder.this;
+ }
+
+ public Builder replyEmail(EmailField replyEmail) {
+ this.replyEmail = replyEmail;
+ return Builder.this;
+ }
+
+ public Builder status(String status) {
+ this.status = status;
+ return Builder.this;
+ }
+
+ public Builder subject(EmailField subject) {
+ this.subject = subject;
+ return Builder.this;
+ }
+
+ public Builder template(Integer template) {
+ this.template = template;
+ return Builder.this;
+ }
+
+ public Builder textOnly(Boolean textOnly) {
+ this.textOnly = textOnly;
+ return Builder.this;
+ }
+
+ public Builder updatedAt(String updatedAt) {
+ this.updatedAt = updatedAt;
+ return Builder.this;
+ }
+
+ public Builder url(String url) {
+ this.url = url;
+ return Builder.this;
+ }
+
+ public Builder version(Integer version) {
+ this.version = version;
+ return Builder.this;
+ }
+
+ public Builder webView(Boolean webView) {
+ this.webView = webView;
+ return Builder.this;
+ }
+
+ public Builder workspace(String workspace) {
+ this.workspace = workspace;
+ return Builder.this;
+ }
+
+ public Builder autoCopyToText(Boolean autoCopyToText) {
+ this.autoCopyToText = autoCopyToText;
+ return Builder.this;
+ }
+
+ public Builder ccFields(List ccFields) {
+ this.ccFields = ccFields;
+ return Builder.this;
+ }
+
+ public Builder addCcFields(EmailCCField ccFields) {
+ this.ccFields.add(ccFields);
+ return Builder.this;
+ }
+
+ public Email build() {
+
+ return new Email(this);
+ }
+ }
+
+ private Email(Builder builder) {
+ this.createdAt = builder.createdAt;
+ this.description = builder.description;
+ this.folder = builder.folder;
+ this.fromEmail = builder.fromEmail;
+ this.fromName = builder.fromName;
+ this.id = builder.id;
+ this.name = builder.name;
+ this.operational = builder.operational;
+ this.publishToMSI = builder.publishToMSI;
+ this.replyEmail = builder.replyEmail;
+ this.status = builder.status;
+ this.subject = builder.subject;
+ this.template = builder.template;
+ this.textOnly = builder.textOnly;
+ this.updatedAt = builder.updatedAt;
+ this.url = builder.url;
+ this.version = builder.version;
+ this.webView = builder.webView;
+ this.workspace = builder.workspace;
+ this.autoCopyToText = builder.autoCopyToText;
+ this.ccFields = builder.ccFields;
+ }
+}
diff --git a/src/main/java/io/cdap/plugin/marketo/common/api/entities/asset/EmailCCField.java b/src/main/java/io/cdap/plugin/marketo/common/api/entities/asset/EmailCCField.java
new file mode 100644
index 0000000..d59a89c
--- /dev/null
+++ b/src/main/java/io/cdap/plugin/marketo/common/api/entities/asset/EmailCCField.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright © 2019 Cask Data, Inc.
+ *
+ * Licensed 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 io.cdap.plugin.marketo.common.api.entities.asset;
+
+import io.cdap.plugin.marketo.common.api.entities.asset.gen.Entity;
+
+/**
+ * Email CC field.
+ */
+@Entity
+public class EmailCCField {
+ String attributeId;
+ String objectName;
+ String displayName;
+ String apiName;
+
+ public EmailCCField() {
+ }
+
+ public EmailCCField(String attributeId, String objectName, String displayName, String apiName) {
+ this.attributeId = attributeId;
+ this.objectName = objectName;
+ this.displayName = displayName;
+ this.apiName = apiName;
+ }
+
+ public String getAttributeId() {
+ return attributeId;
+ }
+
+ public String getObjectName() {
+ return objectName;
+ }
+
+ public String getDisplayName() {
+ return displayName;
+ }
+
+ public String getApiName() {
+ return apiName;
+ }
+
+ @Override
+ public String toString() {
+ return "EmailCCField{" +
+ "attributeId='" + attributeId + '\'' +
+ ", objectName='" + objectName + '\'' +
+ ", displayName='" + displayName + '\'' +
+ ", apiName='" + apiName + '\'' +
+ '}';
+ }
+}
diff --git a/src/main/java/io/cdap/plugin/marketo/common/api/entities/asset/EmailField.java b/src/main/java/io/cdap/plugin/marketo/common/api/entities/asset/EmailField.java
new file mode 100644
index 0000000..5cf78b7
--- /dev/null
+++ b/src/main/java/io/cdap/plugin/marketo/common/api/entities/asset/EmailField.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright © 2019 Cask Data, Inc.
+ *
+ * Licensed 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 io.cdap.plugin.marketo.common.api.entities.asset;
+
+import io.cdap.plugin.marketo.common.api.entities.asset.gen.Entity;
+
+/**
+ * Email field.
+ */
+@Entity
+public class EmailField {
+ String type;
+ String value;
+
+ public String getType() {
+ return type;
+ }
+
+ public String getValue() {
+ return value;
+ }
+}
diff --git a/src/main/java/io/cdap/plugin/marketo/common/api/entities/asset/EmailResponse.java b/src/main/java/io/cdap/plugin/marketo/common/api/entities/asset/EmailResponse.java
new file mode 100644
index 0000000..ef8fa78
--- /dev/null
+++ b/src/main/java/io/cdap/plugin/marketo/common/api/entities/asset/EmailResponse.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright © 2019 Cask Data, Inc.
+ *
+ * Licensed 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 io.cdap.plugin.marketo.common.api.entities.asset;
+
+import io.cdap.plugin.marketo.common.api.entities.asset.gen.Response;
+
+/**
+ * GET /rest/asset/v1/emails.json
+ */
+@Response(fetchUrl = "/rest/asset/v1/emails.json")
+public class EmailResponse extends SimpleBaseResponse {
+
+}
diff --git a/src/main/java/io/cdap/plugin/marketo/common/api/entities/asset/EmailTemplate.java b/src/main/java/io/cdap/plugin/marketo/common/api/entities/asset/EmailTemplate.java
new file mode 100644
index 0000000..b54e3f7
--- /dev/null
+++ b/src/main/java/io/cdap/plugin/marketo/common/api/entities/asset/EmailTemplate.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright © 2019 Cask Data, Inc.
+ *
+ * Licensed 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 io.cdap.plugin.marketo.common.api.entities.asset;
+
+import io.cdap.plugin.marketo.common.api.entities.asset.gen.Entity;
+
+/**
+ * Email template entity.
+ */
+@Entity(topLevel = true)
+public class EmailTemplate {
+ String createdAt;
+ String description;
+ FolderDescriptor folder;
+ Integer id;
+ String name;
+ String status;
+ String updatedAt;
+ String url;
+ Integer version;
+ String workspace;
+
+ public String getCreatedAt() {
+ return createdAt;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public FolderDescriptor getFolder() {
+ return folder;
+ }
+
+ public Integer getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ public String getUpdatedAt() {
+ return updatedAt;
+ }
+
+ public String getUrl() {
+ return url;
+ }
+
+ public Integer getVersion() {
+ return version;
+ }
+
+ public String getWorkspace() {
+ return workspace;
+ }
+}
diff --git a/src/main/java/io/cdap/plugin/marketo/common/api/entities/asset/EmailTemplateResponse.java b/src/main/java/io/cdap/plugin/marketo/common/api/entities/asset/EmailTemplateResponse.java
new file mode 100644
index 0000000..0ba9987
--- /dev/null
+++ b/src/main/java/io/cdap/plugin/marketo/common/api/entities/asset/EmailTemplateResponse.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright © 2019 Cask Data, Inc.
+ *
+ * Licensed 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 io.cdap.plugin.marketo.common.api.entities.asset;
+
+import io.cdap.plugin.marketo.common.api.entities.asset.gen.Response;
+
+/**
+ * GET /rest/asset/v1/emailTemplates.json
+ */
+@Response(fetchUrl = "/rest/asset/v1/emailTemplates.json")
+public class EmailTemplateResponse extends SimpleBaseResponse {
+
+}
diff --git a/src/main/java/io/cdap/plugin/marketo/common/api/entities/asset/File.java b/src/main/java/io/cdap/plugin/marketo/common/api/entities/asset/File.java
new file mode 100644
index 0000000..3e54994
--- /dev/null
+++ b/src/main/java/io/cdap/plugin/marketo/common/api/entities/asset/File.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright © 2019 Cask Data, Inc.
+ *
+ * Licensed 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 io.cdap.plugin.marketo.common.api.entities.asset;
+
+import io.cdap.plugin.marketo.common.api.entities.asset.gen.Entity;
+
+/**
+ * File entity.
+ */
+@Entity(topLevel = true)
+public class File {
+ String createdAt;
+ String description;
+ FileFolder folder;
+ Integer id;
+ String mimeType;
+ String name;
+ Integer size;
+ String updatedAt;
+ String url;
+
+ public String getCreatedAt() {
+ return createdAt;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public FileFolder getFolder() {
+ return folder;
+ }
+
+ public Integer getId() {
+ return id;
+ }
+
+ public String getMimeType() {
+ return mimeType;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Integer getSize() {
+ return size;
+ }
+
+ public String getUpdatedAt() {
+ return updatedAt;
+ }
+
+ public String getUrl() {
+ return url;
+ }
+}
diff --git a/src/main/java/io/cdap/plugin/marketo/common/api/entities/asset/FileFolder.java b/src/main/java/io/cdap/plugin/marketo/common/api/entities/asset/FileFolder.java
new file mode 100644
index 0000000..9dec989
--- /dev/null
+++ b/src/main/java/io/cdap/plugin/marketo/common/api/entities/asset/FileFolder.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright © 2019 Cask Data, Inc.
+ *
+ * Licensed 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 io.cdap.plugin.marketo.common.api.entities.asset;
+
+import io.cdap.plugin.marketo.common.api.entities.asset.gen.Entity;
+
+/**
+ * File folder descriptor.
+ */
+@Entity
+public class FileFolder {
+ Integer id;
+ String name;
+ String type;
+
+ public Integer getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getType() {
+ return type;
+ }
+}
diff --git a/src/main/java/io/cdap/plugin/marketo/common/api/entities/asset/FileResponse.java b/src/main/java/io/cdap/plugin/marketo/common/api/entities/asset/FileResponse.java
new file mode 100644
index 0000000..f6eed56
--- /dev/null
+++ b/src/main/java/io/cdap/plugin/marketo/common/api/entities/asset/FileResponse.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright © 2019 Cask Data, Inc.
+ *
+ * Licensed 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 io.cdap.plugin.marketo.common.api.entities.asset;
+
+import io.cdap.plugin.marketo.common.api.entities.asset.gen.Response;
+
+/**
+ * GET /rest/asset/v1/files.json
+ */
+@Response(fetchUrl = "/rest/asset/v1/files.json")
+public class FileResponse extends SimpleBaseResponse {
+
+}
diff --git a/src/main/java/io/cdap/plugin/marketo/common/api/entities/asset/Folder.java b/src/main/java/io/cdap/plugin/marketo/common/api/entities/asset/Folder.java
new file mode 100644
index 0000000..84eb790
--- /dev/null
+++ b/src/main/java/io/cdap/plugin/marketo/common/api/entities/asset/Folder.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright © 2019 Cask Data, Inc.
+ *
+ * Licensed 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 io.cdap.plugin.marketo.common.api.entities.asset;
+
+import io.cdap.plugin.marketo.common.api.entities.asset.gen.Entity;
+
+/**
+ * Folder entity.
+ */
+@Entity(topLevel = true)
+public class Folder {
+ Integer accessZoneId;
+ String createdAt;
+ String description;
+ FolderDescriptor folderId;
+ String folderType;
+ Integer id;
+ Boolean isArchive;
+ Boolean isSystem;
+ String name;
+ FolderDescriptor parent;
+ String path;
+ String updatedAt;
+ String url;
+ String workspace;
+
+ public Integer getAccessZoneId() {
+ return accessZoneId;
+ }
+
+ public String getCreatedAt() {
+ return createdAt;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public FolderDescriptor getFolderId() {
+ return folderId;
+ }
+
+ public String getFolderType() {
+ return folderType;
+ }
+
+ public Integer getId() {
+ return id;
+ }
+
+ public Boolean getArchive() {
+ return isArchive;
+ }
+
+ public Boolean getSystem() {
+ return isSystem;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public FolderDescriptor getParent() {
+ return parent;
+ }
+
+ public String getPath() {
+ return path;
+ }
+
+ public String getUpdatedAt() {
+ return updatedAt;
+ }
+
+ public String getUrl() {
+ return url;
+ }
+
+ public String getWorkspace() {
+ return workspace;
+ }
+}
diff --git a/src/main/java/io/cdap/plugin/marketo/common/api/entities/asset/FolderDescriptor.java b/src/main/java/io/cdap/plugin/marketo/common/api/entities/asset/FolderDescriptor.java
new file mode 100644
index 0000000..6d8d5b7
--- /dev/null
+++ b/src/main/java/io/cdap/plugin/marketo/common/api/entities/asset/FolderDescriptor.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright © 2019 Cask Data, Inc.
+ *
+ * Licensed 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 io.cdap.plugin.marketo.common.api.entities.asset;
+
+import io.cdap.plugin.marketo.common.api.entities.asset.gen.Entity;
+
+/**
+ * Folder descriptor.
+ */
+@Entity
+public class FolderDescriptor {
+ String id;
+ String type;
+ String folderName;
+
+ public FolderDescriptor(String id, String type, String folderName) {
+ this.id = id;
+ this.type = type;
+ this.folderName = folderName;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public String getFolderName() {
+ return folderName;
+ }
+}
diff --git a/src/main/java/io/cdap/plugin/marketo/common/api/entities/asset/FolderResponse.java b/src/main/java/io/cdap/plugin/marketo/common/api/entities/asset/FolderResponse.java
new file mode 100644
index 0000000..efac548
--- /dev/null
+++ b/src/main/java/io/cdap/plugin/marketo/common/api/entities/asset/FolderResponse.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright © 2019 Cask Data, Inc.
+ *
+ * Licensed 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 io.cdap.plugin.marketo.common.api.entities.asset;
+
+import io.cdap.plugin.marketo.common.api.entities.asset.gen.Response;
+
+/**
+ * GET /rest/asset/v1/folders.json
+ */
+@Response(fetchUrl = "/rest/asset/v1/folders.json")
+public class FolderResponse extends SimpleBaseResponse {
+
+}
diff --git a/src/main/java/io/cdap/plugin/marketo/common/api/entities/asset/Form.java b/src/main/java/io/cdap/plugin/marketo/common/api/entities/asset/Form.java
new file mode 100644
index 0000000..3893896
--- /dev/null
+++ b/src/main/java/io/cdap/plugin/marketo/common/api/entities/asset/Form.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright © 2019 Cask Data, Inc.
+ *
+ * Licensed 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 io.cdap.plugin.marketo.common.api.entities.asset;
+
+import io.cdap.plugin.marketo.common.api.entities.asset.gen.Entity;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Form entity.
+ */
+@Entity(topLevel = true)
+public class Form {
+ String buttonLabel;
+ Integer buttonLocation;
+ String createdAt;
+ String description;
+ FolderDescriptor folder;
+ String fontFamily;
+ String fontSize;
+ Integer id;
+ FormKnownVisitorDTO knownVisitor;
+ String labelPosition;
+ String language;
+ String locale;
+ String name;
+ Boolean progressiveProfiling;
+ String status;
+ List thankYouList = Collections.emptyList();
+ String theme;
+ String updatedAt;
+ String url;
+ String waitingLabel;
+
+ public String getButtonLabel() {
+ return buttonLabel;
+ }
+
+ public Integer getButtonLocation() {
+ return buttonLocation;
+ }
+
+ public String getCreatedAt() {
+ return createdAt;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public FolderDescriptor getFolder() {
+ return folder;
+ }
+
+ public String getFontFamily() {
+ return fontFamily;
+ }
+
+ public String getFontSize() {
+ return fontSize;
+ }
+
+ public Integer getId() {
+ return id;
+ }
+
+ public FormKnownVisitorDTO getKnownVisitor() {
+ return knownVisitor;
+ }
+
+ public String getLabelPosition() {
+ return labelPosition;
+ }
+
+ public String getLanguage() {
+ return language;
+ }
+
+ public String getLocale() {
+ return locale;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Boolean getProgressiveProfiling() {
+ return progressiveProfiling;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ public List getThankYouList() {
+ return thankYouList;
+ }
+
+ public String getTheme() {
+ return theme;
+ }
+
+ public String getUpdatedAt() {
+ return updatedAt;
+ }
+
+ public String getUrl() {
+ return url;
+ }
+
+ public String getWaitingLabel() {
+ return waitingLabel;
+ }
+}
diff --git a/src/main/java/io/cdap/plugin/marketo/common/api/entities/asset/FormField.java b/src/main/java/io/cdap/plugin/marketo/common/api/entities/asset/FormField.java
new file mode 100644
index 0000000..a268fac
--- /dev/null
+++ b/src/main/java/io/cdap/plugin/marketo/common/api/entities/asset/FormField.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright © 2019 Cask Data, Inc.
+ *
+ * Licensed 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 io.cdap.plugin.marketo.common.api.entities.asset;
+
+import io.cdap.plugin.marketo.common.api.entities.asset.gen.Entity;
+
+/**
+ * Form filed entity.
+ */
+@Entity(topLevel = true)
+public class FormField {
+ String dataType;
+ String defaultValue;
+ String description;
+ String fieldMaskValues;
+ Integer fieldWidth;
+ String id;
+ Boolean initiallyChecked;
+ Boolean isLabelToRight;
+ Boolean isMultiselect;
+ Boolean isRequired;
+ Integer labelWidth;
+ Integer maxLength;
+ String maximumNumber;
+ String minimumNumber;
+ String picklistValues;
+ String placeholderText;
+ String validationMessage;
+ Integer visibleRows;
+
+ public String getDataType() {
+ return dataType;
+ }
+
+ public void setDataType(String dataType) {
+ this.dataType = dataType;
+ }
+
+ public String getDefaultValue() {
+ return defaultValue;
+ }
+
+ public void setDefaultValue(String defaultValue) {
+ this.defaultValue = defaultValue;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public String getFieldMaskValues() {
+ return fieldMaskValues;
+ }
+
+ public void setFieldMaskValues(String fieldMaskValues) {
+ this.fieldMaskValues = fieldMaskValues;
+ }
+
+ public Integer getFieldWidth() {
+ return fieldWidth;
+ }
+
+ public void setFieldWidth(Integer fieldWidth) {
+ this.fieldWidth = fieldWidth;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public Boolean getInitiallyChecked() {
+ return initiallyChecked;
+ }
+
+ public void setInitiallyChecked(Boolean initiallyChecked) {
+ this.initiallyChecked = initiallyChecked;
+ }
+
+ public Boolean getLabelToRight() {
+ return isLabelToRight;
+ }
+
+ public void setLabelToRight(Boolean labelToRight) {
+ isLabelToRight = labelToRight;
+ }
+
+ public Boolean getMultiselect() {
+ return isMultiselect;
+ }
+
+ public void setMultiselect(Boolean multiselect) {
+ isMultiselect = multiselect;
+ }
+
+ public Boolean getRequired() {
+ return isRequired;
+ }
+
+ public void setRequired(Boolean required) {
+ isRequired = required;
+ }
+
+ public Integer getLabelWidth() {
+ return labelWidth;
+ }
+
+ public void setLabelWidth(Integer labelWidth) {
+ this.labelWidth = labelWidth;
+ }
+
+ public Integer getMaxLength() {
+ return maxLength;
+ }
+
+ public void setMaxLength(Integer maxLength) {
+ this.maxLength = maxLength;
+ }
+
+ public String getMaximumNumber() {
+ return maximumNumber;
+ }
+
+ public void setMaximumNumber(String maximumNumber) {
+ this.maximumNumber = maximumNumber;
+ }
+
+ public String getMinimumNumber() {
+ return minimumNumber;
+ }
+
+ public void setMinimumNumber(String minimumNumber) {
+ this.minimumNumber = minimumNumber;
+ }
+
+ public String getPicklistValues() {
+ return picklistValues;
+ }
+
+ public void setPicklistValues(String picklistValues) {
+ this.picklistValues = picklistValues;
+ }
+
+ public String getPlaceholderText() {
+ return placeholderText;
+ }
+
+ public void setPlaceholderText(String placeholderText) {
+ this.placeholderText = placeholderText;
+ }
+
+ public String getValidationMessage() {
+ return validationMessage;
+ }
+
+ public void setValidationMessage(String validationMessage) {
+ this.validationMessage = validationMessage;
+ }
+
+ public Integer getVisibleRows() {
+ return visibleRows;
+ }
+
+ public void setVisibleRows(Integer visibleRows) {
+ this.visibleRows = visibleRows;
+ }
+}
diff --git a/src/main/java/io/cdap/plugin/marketo/common/api/entities/asset/FormFieldsResponse.java b/src/main/java/io/cdap/plugin/marketo/common/api/entities/asset/FormFieldsResponse.java
new file mode 100644
index 0000000..ff406bc
--- /dev/null
+++ b/src/main/java/io/cdap/plugin/marketo/common/api/entities/asset/FormFieldsResponse.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright © 2019 Cask Data, Inc.
+ *
+ * Licensed 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 io.cdap.plugin.marketo.common.api.entities.asset;
+
+import io.cdap.plugin.marketo.common.api.entities.asset.gen.Response;
+
+/**
+ * GET /rest/asset/v1/form/fields.json
+ */
+@Response(fetchUrl = "/rest/asset/v1/form/fields.json")
+public class FormFieldsResponse extends SimpleBaseResponse {
+
+}
diff --git a/src/main/java/io/cdap/plugin/marketo/common/api/entities/asset/FormKnownVisitorDTO.java b/src/main/java/io/cdap/plugin/marketo/common/api/entities/asset/FormKnownVisitorDTO.java
new file mode 100644
index 0000000..aba9e2e
--- /dev/null
+++ b/src/main/java/io/cdap/plugin/marketo/common/api/entities/asset/FormKnownVisitorDTO.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright © 2019 Cask Data, Inc.
+ *
+ * Licensed 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 io.cdap.plugin.marketo.common.api.entities.asset;
+
+import io.cdap.plugin.marketo.common.api.entities.asset.gen.Entity;
+
+/**
+ * Known visitor behavior for the form.
+ */
+@Entity
+public class FormKnownVisitorDTO {
+ String template;
+ String type;
+
+ public String getTemplate() {
+ return template;
+ }
+
+ public String getType() {
+ return type;
+ }
+}
diff --git a/src/main/java/io/cdap/plugin/marketo/common/api/entities/asset/FormResponse.java b/src/main/java/io/cdap/plugin/marketo/common/api/entities/asset/FormResponse.java
new file mode 100644
index 0000000..6351369
--- /dev/null
+++ b/src/main/java/io/cdap/plugin/marketo/common/api/entities/asset/FormResponse.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright © 2019 Cask Data, Inc.
+ *
+ * Licensed 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 io.cdap.plugin.marketo.common.api.entities.asset;
+
+import io.cdap.plugin.marketo.common.api.entities.asset.gen.Response;
+
+/**
+ * GET /rest/asset/v1/forms.json
+ */
+@Response(fetchUrl = "/rest/asset/v1/forms.json")
+public class FormResponse extends SimpleBaseResponse