embulk-output-kintone 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA256:
3
- metadata.gz: 256bcb9a1f785c4811ab072062b8bca8cf7d55cfc6dd01a883313ac341abf971
4
- data.tar.gz: bec48d54a62afbee336ef60c271e5795d6567d19b93a3029857403572a8418dd
2
+ SHA1:
3
+ metadata.gz: 12c249d873d4feb22cad46c5d7bd8858f21c6138
4
+ data.tar.gz: fe0ff0d7a8433caa4afe30a90c2acd7bdffb73ac
5
5
  SHA512:
6
- metadata.gz: 4fc949cbe0b4f631005b7de223902cee34c23099484d412e89bd5fad58ec099ca4e8ed536121348043cfa2b813630efc34f6274858d93ddb066b85c7b53b980a
7
- data.tar.gz: 2216fee31203a3f34dfa422b488d87d966848500c43db5045fb056780f082375d434d4b3ce82645ae6ee1b39973112714608f8dfa9eaf6bc2f6edc325fb7994e
6
+ metadata.gz: 6f6a9c530c8c9ea1b3324aace5dbac3a9cbb6eaf9a800a064942f1229565a86cfb0ad51bd95ce53cadfc014320981c4e126addf699a32e16b90e7667a2745004
7
+ data.tar.gz: 96f86db9027769370cf6af39f2ff157d7ca6b691d1712fa2ec112e04a464dc87e6db5b3d664ef45d69db73f213ce5e7007f0321c86809a5d5d17c11b1bb0479f
@@ -0,0 +1,13 @@
1
+ name: Java CI
2
+
3
+ on: [push, pull_request]
4
+
5
+ jobs:
6
+ build:
7
+ runs-on: ubuntu-latest
8
+ steps:
9
+ - uses: actions/checkout@v2
10
+ - uses: actions/setup-java@v1
11
+ with:
12
+ java-version: 1.8
13
+ - run: ./gradlew build
data/.gitignore CHANGED
@@ -10,3 +10,4 @@ build/
10
10
  /.metadata/
11
11
  .classpath
12
12
  .project
13
+ config.yml
data/README.md CHANGED
@@ -5,7 +5,6 @@
5
5
  ## Overview
6
6
 
7
7
  kintone output plugin for Embulk stores app records from kintone.
8
- embulk 0.9 is only supported due to the dependency of kintone-java-sdk 0.4.0, which requires java 8
9
8
 
10
9
  ## Configuration
11
10
 
@@ -23,23 +22,24 @@ embulk 0.9 is only supported due to the dependency of kintone-java-sdk 0.4.0, wh
23
22
  - **type**: field type (string, required)
24
23
  - **timezone**: timezone to convert into `date` (string, default is `UTC`)
25
24
  - **update_key**: update key (boolean, default is `false`)
25
+ - **val_sep**: Used to specify multiple checkbox values (string, default is `,`)
26
26
 
27
27
  ## Example
28
28
 
29
29
  ```yaml
30
30
  out:
31
31
  type: kintone
32
- domain: example.cybozu.com
33
- username: username
34
- password: password
35
- app_id: 1
36
- mode: insert
37
- column_options:
38
- id: {field_code: "id", type: "NUMBER"}
39
- name: {field_code: "name", type: "SINGLE_LINE_TEXT"}
40
- number: {field_code: "num", type: "NUMBER"}
41
- date: {field_code: "date", type: "DATE"}
42
- date_time: {field_code: "datetime", type: "DATETIME"}
32
+ domain: example.cybozu.com
33
+ username: username
34
+ password: password
35
+ app_id: 1
36
+ mode: insert
37
+ column_options:
38
+ id: {field_code: "id", type: "NUMBER"}
39
+ name: {field_code: "name", type: "SINGLE_LINE_TEXT"}
40
+ number: {field_code: "num", type: "NUMBER"}
41
+ date: {field_code: "date", type: "DATE"}
42
+ date_time: {field_code: "datetime", type: "DATETIME"}
43
43
  ```
44
44
 
45
45
 
@@ -1,6 +1,7 @@
1
1
  plugins {
2
2
  id "com.jfrog.bintray" version "1.1"
3
3
  id "com.github.jruby-gradle.base" version "1.5.0"
4
+ id "com.github.johnrengelman.shadow" version "4.0.3"
4
5
  id "java"
5
6
  id "checkstyle"
6
7
  }
@@ -13,23 +14,31 @@ configurations {
13
14
  provided
14
15
  }
15
16
 
16
- version = "0.1.1"
17
+ version = "0.2.0"
17
18
 
18
19
  sourceCompatibility = 1.8
19
20
  targetCompatibility = 1.8
20
21
 
22
+ shadowJar {
23
+ exclude "org/embulk/plugin/**"
24
+ relocate "com.fasterxml.jackson", "embulk.kintone.com.fasterxml.jackson"
25
+ }
26
+
21
27
  dependencies {
22
28
  compile "org.embulk:embulk-core:0.9.20"
23
29
  provided "org.embulk:embulk-core:0.9.20"
24
- compile group: 'com.cybozu.kintone', name: 'kintone-sdk', version: '0.4.0'
30
+ compile "com.kintone:kintone-java-client:1.0.2"
25
31
 
26
32
  // compile "YOUR_JAR_DEPENDENCY_GROUP:YOUR_JAR_DEPENDENCY_MODULE:YOUR_JAR_DEPENDENCY_VERSION"
27
33
  testCompile "junit:junit:4.+"
28
34
  }
29
35
 
30
- task classpath(type: Copy, dependsOn: ["jar"]) {
36
+ task classpath(type: Copy, dependsOn: ["jar", "shadowJar"]) {
31
37
  doFirst { file("classpath").deleteDir() }
32
- from (configurations.runtime - configurations.provided + files(jar.archivePath))
38
+ from (configurations.runtime - configurations.provided
39
+ + configurations.shadow
40
+ - files(shadowJar.getIncludedDependencies())
41
+ + files(shadowJar.archivePath))
33
42
  into "classpath"
34
43
  }
35
44
  clean { delete "classpath" }
@@ -1,6 +1,5 @@
1
- #Wed Dec 11 14:01:39 JST 2019
2
- distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
3
1
  distributionBase=GRADLE_USER_HOME
4
2
  distributionPath=wrapper/dists
5
- zipStorePath=wrapper/dists
3
+ distributionUrl=https\://services.gradle.org/distributions/gradle-6.6-bin.zip
6
4
  zipStoreBase=GRADLE_USER_HOME
5
+ zipStorePath=wrapper/dists
data/gradlew CHANGED
@@ -1,5 +1,21 @@
1
1
  #!/usr/bin/env sh
2
2
 
3
+ #
4
+ # Copyright 2015 the original author or authors.
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # https://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
3
19
  ##############################################################################
4
20
  ##
5
21
  ## Gradle start up script for UN*X
@@ -28,7 +44,7 @@ APP_NAME="Gradle"
28
44
  APP_BASE_NAME=`basename "$0"`
29
45
 
30
46
  # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31
- DEFAULT_JVM_OPTS=""
47
+ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
32
48
 
33
49
  # Use the maximum available, or set MAX_FD != -1 to use that value.
34
50
  MAX_FD="maximum"
@@ -66,6 +82,7 @@ esac
66
82
 
67
83
  CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68
84
 
85
+
69
86
  # Determine the Java command to use to start the JVM.
70
87
  if [ -n "$JAVA_HOME" ] ; then
71
88
  if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
@@ -109,10 +126,11 @@ if $darwin; then
109
126
  GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110
127
  fi
111
128
 
112
- # For Cygwin, switch paths to Windows format before running java
113
- if $cygwin ; then
129
+ # For Cygwin or MSYS, switch paths to Windows format before running java
130
+ if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
114
131
  APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115
132
  CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133
+
116
134
  JAVACMD=`cygpath --unix "$JAVACMD"`
117
135
 
118
136
  # We build the pattern for arguments to be converted via cygpath
@@ -138,19 +156,19 @@ if $cygwin ; then
138
156
  else
139
157
  eval `echo args$i`="\"$arg\""
140
158
  fi
141
- i=$((i+1))
159
+ i=`expr $i + 1`
142
160
  done
143
161
  case $i in
144
- (0) set -- ;;
145
- (1) set -- "$args0" ;;
146
- (2) set -- "$args0" "$args1" ;;
147
- (3) set -- "$args0" "$args1" "$args2" ;;
148
- (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149
- (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150
- (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151
- (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152
- (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153
- (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
162
+ 0) set -- ;;
163
+ 1) set -- "$args0" ;;
164
+ 2) set -- "$args0" "$args1" ;;
165
+ 3) set -- "$args0" "$args1" "$args2" ;;
166
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154
172
  esac
155
173
  fi
156
174
 
@@ -159,14 +177,9 @@ save () {
159
177
  for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160
178
  echo " "
161
179
  }
162
- APP_ARGS=$(save "$@")
180
+ APP_ARGS=`save "$@"`
163
181
 
164
182
  # Collect all arguments for the java command, following the shell quoting and substitution rules
165
183
  eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166
184
 
167
- # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168
- if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169
- cd "$(dirname "$0")"
170
- fi
171
-
172
185
  exec "$JAVACMD" "$@"
@@ -1,3 +1,19 @@
1
+ @rem
2
+ @rem Copyright 2015 the original author or authors.
3
+ @rem
4
+ @rem Licensed under the Apache License, Version 2.0 (the "License");
5
+ @rem you may not use this file except in compliance with the License.
6
+ @rem You may obtain a copy of the License at
7
+ @rem
8
+ @rem https://www.apache.org/licenses/LICENSE-2.0
9
+ @rem
10
+ @rem Unless required by applicable law or agreed to in writing, software
11
+ @rem distributed under the License is distributed on an "AS IS" BASIS,
12
+ @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ @rem See the License for the specific language governing permissions and
14
+ @rem limitations under the License.
15
+ @rem
16
+
1
17
  @if "%DEBUG%" == "" @echo off
2
18
  @rem ##########################################################################
3
19
  @rem
@@ -13,15 +29,18 @@ if "%DIRNAME%" == "" set DIRNAME=.
13
29
  set APP_BASE_NAME=%~n0
14
30
  set APP_HOME=%DIRNAME%
15
31
 
32
+ @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33
+ for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34
+
16
35
  @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17
- set DEFAULT_JVM_OPTS=
36
+ set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
18
37
 
19
38
  @rem Find java.exe
20
39
  if defined JAVA_HOME goto findJavaFromJavaHome
21
40
 
22
41
  set JAVA_EXE=java.exe
23
42
  %JAVA_EXE% -version >NUL 2>&1
24
- if "%ERRORLEVEL%" == "0" goto init
43
+ if "%ERRORLEVEL%" == "0" goto execute
25
44
 
26
45
  echo.
27
46
  echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@@ -35,7 +54,7 @@ goto fail
35
54
  set JAVA_HOME=%JAVA_HOME:"=%
36
55
  set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37
56
 
38
- if exist "%JAVA_EXE%" goto init
57
+ if exist "%JAVA_EXE%" goto execute
39
58
 
40
59
  echo.
41
60
  echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
@@ -45,28 +64,14 @@ echo location of your Java installation.
45
64
 
46
65
  goto fail
47
66
 
48
- :init
49
- @rem Get command-line arguments, handling Windows variants
50
-
51
- if not "%OS%" == "Windows_NT" goto win9xME_args
52
-
53
- :win9xME_args
54
- @rem Slurp the command line arguments.
55
- set CMD_LINE_ARGS=
56
- set _SKIP=2
57
-
58
- :win9xME_args_slurp
59
- if "x%~1" == "x" goto execute
60
-
61
- set CMD_LINE_ARGS=%*
62
-
63
67
  :execute
64
68
  @rem Setup the command line
65
69
 
66
70
  set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67
71
 
72
+
68
73
  @rem Execute Gradle
69
- "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
74
+ "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
70
75
 
71
76
  :end
72
77
  @rem End local scope for the variables with windows NT shell
@@ -22,4 +22,8 @@ public interface KintoneColumnOption
22
22
  @Config("update_key")
23
23
  @ConfigDefault("false")
24
24
  public boolean getUpdateKey();
25
+
26
+ @Config("val_sep")
27
+ @ConfigDefault("\",\"")
28
+ public String getValueSeparator();
25
29
  }
@@ -1,22 +1,34 @@
1
1
  package org.embulk.output.kintone;
2
2
 
3
- import com.cybozu.kintone.client.model.app.form.FieldType;
4
- import com.cybozu.kintone.client.model.record.field.FieldValue;
3
+ import com.kintone.client.model.record.CheckBoxFieldValue;
4
+ import com.kintone.client.model.record.DateFieldValue;
5
+ import com.kintone.client.model.record.DateTimeFieldValue;
6
+ import com.kintone.client.model.record.DropDownFieldValue;
7
+ import com.kintone.client.model.record.FieldType;
8
+ import com.kintone.client.model.record.FieldValue;
9
+ import com.kintone.client.model.record.LinkFieldValue;
10
+ import com.kintone.client.model.record.MultiLineTextFieldValue;
11
+ import com.kintone.client.model.record.NumberFieldValue;
12
+ import com.kintone.client.model.record.Record;
13
+ import com.kintone.client.model.record.SingleLineTextFieldValue;
14
+ import com.kintone.client.model.record.UpdateKey;
5
15
  import org.embulk.spi.Column;
6
16
  import org.embulk.spi.ColumnVisitor;
7
17
  import org.embulk.spi.PageReader;
8
18
  import org.embulk.spi.time.Timestamp;
9
- import org.embulk.spi.time.TimestampFormatter;
10
- import org.joda.time.DateTimeZone;
11
19
 
12
- import java.util.HashMap;
20
+ import java.math.BigDecimal;
21
+ import java.time.Instant;
22
+ import java.time.ZoneId;
23
+ import java.time.ZonedDateTime;
13
24
  import java.util.Map;
14
25
 
15
26
  public class KintoneColumnVisitor
16
27
  implements ColumnVisitor
17
28
  {
18
29
  private PageReader pageReader;
19
- private HashMap record;
30
+ private Record record;
31
+ private UpdateKey updateKey;
20
32
  private Map<String, KintoneColumnOption> columnOptions;
21
33
 
22
34
  public KintoneColumnVisitor(PageReader pageReader,
@@ -26,20 +38,71 @@ public class KintoneColumnVisitor
26
38
  this.columnOptions = columnOptions;
27
39
  }
28
40
 
29
- public void setRecord(HashMap record)
41
+ public void setRecord(Record record)
30
42
  {
31
43
  this.record = record;
32
44
  }
33
45
 
34
- private void setValue(String fieldCode, Object value, FieldType type)
46
+ public void setUpdateKey(UpdateKey updateKey)
47
+ {
48
+ this.updateKey = updateKey;
49
+ }
50
+
51
+ private void setValue(String fieldCode, Object value, FieldType type, boolean isUpdateKey)
35
52
  {
36
53
  if (value == null) {
37
54
  return;
38
55
  }
39
- FieldValue fieldValue = new FieldValue();
40
- fieldValue.setType(type);
41
- record.put(fieldCode, fieldValue);
42
- fieldValue.setValue(String.valueOf(value));
56
+
57
+ if (isUpdateKey && updateKey != null) {
58
+ updateKey
59
+ .setField(fieldCode)
60
+ .setValue(String.valueOf(value));
61
+ }
62
+ else {
63
+ String stringValue = String.valueOf(value);
64
+ FieldValue fieldValue = null;
65
+ switch (type) {
66
+ case NUMBER:
67
+ fieldValue = new NumberFieldValue(new BigDecimal(stringValue));
68
+ break;
69
+ case MULTI_LINE_TEXT:
70
+ fieldValue = new MultiLineTextFieldValue(stringValue);
71
+ break;
72
+ case DROP_DOWN:
73
+ fieldValue = new DropDownFieldValue(stringValue);
74
+ break;
75
+ case LINK:
76
+ fieldValue = new LinkFieldValue(stringValue);
77
+ break;
78
+ default:
79
+ fieldValue = new SingleLineTextFieldValue(stringValue);
80
+ }
81
+ record.putField(fieldCode, fieldValue);
82
+ }
83
+ }
84
+
85
+ private void setTimestampValue(String fieldCode, Instant instant, ZoneId zoneId, FieldType type)
86
+ {
87
+ FieldValue fieldValue = null;
88
+ ZonedDateTime datetime = instant.atZone(zoneId);
89
+ switch (type) {
90
+ case DATE:
91
+ fieldValue = new DateFieldValue(datetime.toLocalDate());
92
+ break;
93
+ case DATETIME:
94
+ fieldValue = new DateTimeFieldValue(datetime);
95
+ }
96
+ record.putField(fieldCode, fieldValue);
97
+ }
98
+
99
+ private void setCheckBoxValue(String fieldCode, Object value, String valueSeparator)
100
+ {
101
+ String str = String.valueOf(value);
102
+ record.putField(
103
+ fieldCode,
104
+ new CheckBoxFieldValue(str.split(valueSeparator, 0))
105
+ );
43
106
  }
44
107
 
45
108
  private FieldType getType(Column column, FieldType defaultType)
@@ -64,10 +127,28 @@ public class KintoneColumnVisitor
64
127
  }
65
128
  }
66
129
 
67
- private DateTimeZone getTimezone(Column column)
130
+ private ZoneId getZoneId(Column column)
131
+ {
132
+ KintoneColumnOption option = columnOptions.get(column.getName());
133
+ return ZoneId.of(option.getTimezone().get());
134
+ }
135
+
136
+ private boolean isUpdateKey(Column column)
137
+ {
138
+ KintoneColumnOption option = columnOptions.get(column.getName());
139
+ if (option == null) {
140
+ return false;
141
+ }
142
+ return option.getUpdateKey();
143
+ }
144
+
145
+ private String getValueSeparator(Column column)
68
146
  {
69
147
  KintoneColumnOption option = columnOptions.get(column.getName());
70
- return DateTimeZone.forID(option.getTimezone().get());
148
+ if (option == null) {
149
+ return ",";
150
+ }
151
+ return option.getValueSeparator();
71
152
  }
72
153
 
73
154
  @Override
@@ -75,7 +156,7 @@ public class KintoneColumnVisitor
75
156
  {
76
157
  String fieldCode = getFieldCode(column);
77
158
  FieldType type = getType(column, FieldType.NUMBER);
78
- setValue(fieldCode, pageReader.getBoolean(column), type);
159
+ setValue(fieldCode, pageReader.getBoolean(column), type, isUpdateKey(column));
79
160
  }
80
161
 
81
162
  @Override
@@ -83,7 +164,7 @@ public class KintoneColumnVisitor
83
164
  {
84
165
  String fieldCode = getFieldCode(column);
85
166
  FieldType type = getType(column, FieldType.NUMBER);
86
- setValue(fieldCode, pageReader.getLong(column), type);
167
+ setValue(fieldCode, pageReader.getLong(column), type, isUpdateKey(column));
87
168
  }
88
169
 
89
170
  @Override
@@ -91,7 +172,7 @@ public class KintoneColumnVisitor
91
172
  {
92
173
  String fieldCode = getFieldCode(column);
93
174
  FieldType type = getType(column, FieldType.NUMBER);
94
- setValue(fieldCode, pageReader.getDouble(column), type);
175
+ setValue(fieldCode, pageReader.getDouble(column), type, isUpdateKey(column));
95
176
  }
96
177
 
97
178
  @Override
@@ -99,39 +180,28 @@ public class KintoneColumnVisitor
99
180
  {
100
181
  String fieldCode = getFieldCode(column);
101
182
  FieldType type = getType(column, FieldType.MULTI_LINE_TEXT);
102
- setValue(fieldCode, pageReader.getString(column), type);
183
+ if (type == FieldType.CHECK_BOX) {
184
+ setCheckBoxValue(fieldCode, pageReader.getString(column), getValueSeparator(column));
185
+ return;
186
+ }
187
+ setValue(fieldCode, pageReader.getString(column), type, isUpdateKey(column));
103
188
  }
104
189
 
105
190
  @Override
106
191
  public void timestampColumn(Column column)
107
192
  {
108
- String fieldCode = getFieldCode(column);
109
- FieldType type = getType(column, FieldType.DATETIME);
110
193
  Timestamp value = pageReader.getTimestamp(column);
111
194
  if (value == null) {
112
195
  return;
113
196
  }
114
- switch (type) {
115
- case DATE: {
116
- String format = "%Y-%m-%d";
117
- DateTimeZone timezone = getTimezone(column);
118
- TimestampFormatter formatter = new TimestampFormatter(format, timezone);
119
- String date = formatter.format(value);
120
- setValue(fieldCode, date, type);
121
- break;
122
- }
123
- case DATETIME: {
124
- String format = "%Y-%m-%dT%H:%M:%S%z";
125
- DateTimeZone timezone = DateTimeZone.forID("UTC");
126
- TimestampFormatter formatter = new TimestampFormatter(format, timezone);
127
- String dateTime = formatter.format(value);
128
- setValue(fieldCode, dateTime, type);
129
- break;
130
- }
131
- default: {
132
- setValue(fieldCode, value, type);
133
- }
197
+
198
+ String fieldCode = getFieldCode(column);
199
+ FieldType type = getType(column, FieldType.DATETIME);
200
+ ZoneId zoneId = getZoneId(column);
201
+ if (type == FieldType.DATETIME) {
202
+ zoneId = ZoneId.of("UTC");
134
203
  }
204
+ setTimestampValue(fieldCode, value.getInstant(), zoneId, type);
135
205
  }
136
206
 
137
207
  @Override
@@ -139,6 +209,6 @@ public class KintoneColumnVisitor
139
209
  {
140
210
  String fieldCode = getFieldCode(column);
141
211
  FieldType type = getType(column, FieldType.MULTI_LINE_TEXT);
142
- setValue(fieldCode, pageReader.getJson(column), type);
212
+ setValue(fieldCode, pageReader.getJson(column), type, isUpdateKey(column));
143
213
  }
144
214
  }
@@ -1,36 +1,31 @@
1
1
  package org.embulk.output.kintone;
2
2
 
3
- import com.fasterxml.jackson.annotation.JsonCreator;
4
- import com.fasterxml.jackson.annotation.JsonValue;
5
3
  import org.embulk.config.ConfigException;
6
4
 
7
- import java.util.Locale;
8
-
9
5
  public enum KintoneMode
10
6
  {
11
- INSERT, UPDATE, UPSERT;
7
+ INSERT("insert"), UPDATE("update"), UPSERT("upsert");
8
+
9
+ private final String value;
12
10
 
13
- @JsonCreator
14
- public static KintoneMode fromString(String value)
11
+ KintoneMode(String value)
15
12
  {
16
- switch (value) {
17
- case "insert":
18
- return INSERT;
19
- case "update":
20
- return UPDATE;
21
- case "upsert":
22
- return UPSERT;
23
- default:
24
- throw new ConfigException(String.format(
25
- "Unknown mode '%s'. Supported modes are insert",
26
- value));
27
- }
13
+ this.value = value;
28
14
  }
29
15
 
30
- @JsonValue
31
16
  @Override
32
17
  public String toString()
33
18
  {
34
- return name().toLowerCase(Locale.ENGLISH);
19
+ return value;
20
+ }
21
+
22
+ public static KintoneMode getKintoneModeByValue(String value)
23
+ {
24
+ for (KintoneMode mode : values()) {
25
+ if (mode.toString().equals(value)) {
26
+ return mode;
27
+ }
28
+ }
29
+ throw new ConfigException(String.format("Unknown mode '%s'", value));
35
30
  }
36
31
  }
@@ -45,10 +45,11 @@ public class KintoneOutputPlugin
45
45
  public TransactionalPageOutput open(TaskSource taskSource, Schema schema, int taskIndex)
46
46
  {
47
47
  PluginTask task = taskSource.loadTask(PluginTask.class);
48
+ Collection<KintoneColumnOption> options = task.getColumnOptions().values();
48
49
 
49
- switch (task.getMode()) {
50
+ KintoneMode mode = KintoneMode.getKintoneModeByValue(task.getMode());
51
+ switch (mode) {
50
52
  case INSERT:
51
- Collection<KintoneColumnOption> options = task.getColumnOptions().values();
52
53
  for (KintoneColumnOption option : options) {
53
54
  if (option.getUpdateKey()) {
54
55
  throw new IllegalArgumentException(
@@ -57,12 +58,26 @@ public class KintoneOutputPlugin
57
58
  }
58
59
  break;
59
60
  case UPDATE:
60
- // TODO updatePage
61
61
  case UPSERT:
62
- // TODO upsertPage
62
+ boolean hasUpdateKey = false;
63
+ for (KintoneColumnOption option : options) {
64
+ if (option.getUpdateKey()) {
65
+ if (hasUpdateKey) {
66
+ throw new IllegalArgumentException(
67
+ "when mode is update and upsert, only one column can have an update_key.");
68
+ }
69
+ hasUpdateKey = true;
70
+ }
71
+ }
72
+ if (!hasUpdateKey) {
73
+ throw new IllegalArgumentException(
74
+ "when mode is update and upsert, require update_key.");
75
+ }
76
+ break;
63
77
  default:
64
- throw new IllegalArgumentException(
65
- "mode is insert only.");
78
+ throw new IllegalArgumentException(String.format(
79
+ "Unknown mode '%s'",
80
+ task.getMode()));
66
81
  }
67
82
  return new KintonePageOutput(task, schema);
68
83
  }
@@ -1,9 +1,11 @@
1
1
  package org.embulk.output.kintone;
2
2
 
3
- import com.cybozu.kintone.client.authentication.Auth;
4
- import com.cybozu.kintone.client.connection.Connection;
5
- import com.cybozu.kintone.client.model.record.field.FieldValue;
6
- import com.cybozu.kintone.client.module.record.Record;
3
+ import com.kintone.client.KintoneClient;
4
+ import com.kintone.client.KintoneClientBuilder;
5
+ import com.kintone.client.api.record.GetRecordsByCursorResponseBody;
6
+ import com.kintone.client.model.record.Record;
7
+ import com.kintone.client.model.record.RecordForUpdate;
8
+ import com.kintone.client.model.record.UpdateKey;
7
9
  import org.embulk.config.TaskReport;
8
10
  import org.embulk.spi.Column;
9
11
  import org.embulk.spi.Exec;
@@ -12,15 +14,20 @@ import org.embulk.spi.PageReader;
12
14
  import org.embulk.spi.Schema;
13
15
  import org.embulk.spi.TransactionalPageOutput;
14
16
 
17
+ import java.math.BigDecimal;
15
18
  import java.util.ArrayList;
19
+ import java.util.Arrays;
16
20
  import java.util.HashMap;
21
+ import java.util.List;
22
+ import java.util.Map;
23
+ import java.util.stream.Collectors;
17
24
 
18
25
  public class KintonePageOutput
19
26
  implements TransactionalPageOutput
20
27
  {
21
28
  private PageReader pageReader;
22
29
  private PluginTask task;
23
- private Connection conn;
30
+ private KintoneClient client;
24
31
 
25
32
  public KintonePageOutput(PluginTask task, Schema schema)
26
33
  {
@@ -31,17 +38,21 @@ public class KintonePageOutput
31
38
  @Override
32
39
  public void add(Page page)
33
40
  {
34
- switch (task.getMode()) {
41
+ KintoneMode mode = KintoneMode.getKintoneModeByValue(task.getMode());
42
+ switch (mode) {
35
43
  case INSERT:
36
44
  insertPage(page);
37
45
  break;
38
46
  case UPDATE:
39
- // TODO updatePage
47
+ updatePage(page);
48
+ break;
40
49
  case UPSERT:
41
- // TODO upsertPage
50
+ upsertPage(page);
51
+ break;
42
52
  default:
43
- throw new UnsupportedOperationException(
44
- "kintone output plugin does not support update, upsert");
53
+ throw new UnsupportedOperationException(String.format(
54
+ "Unknown mode '%s'",
55
+ task.getMode()));
45
56
  }
46
57
  }
47
58
 
@@ -54,7 +65,15 @@ public class KintonePageOutput
54
65
  @Override
55
66
  public void close()
56
67
  {
57
- // noop
68
+ if (this.client == null) {
69
+ return;
70
+ }
71
+ try {
72
+ this.client.close();
73
+ }
74
+ catch (Exception e) {
75
+ throw new RuntimeException("kintone throw exception", e);
76
+ }
58
77
  }
59
78
 
60
79
  @Override
@@ -76,56 +95,90 @@ public class KintonePageOutput
76
95
 
77
96
  public void connect(final PluginTask task)
78
97
  {
79
- Auth kintoneAuth = new Auth();
80
- if (task.getUsername().isPresent() && task.getPassword().isPresent()) {
81
- kintoneAuth.setPasswordAuth(task.getUsername().get(), task.getPassword().get());
82
- }
83
- else if (task.getToken().isPresent()) {
84
- kintoneAuth.setApiToken(task.getToken().get());
98
+ KintoneClientBuilder builder = KintoneClientBuilder.create("https://" + task.getDomain());
99
+ if (task.getGuestSpaceId().isPresent()) {
100
+ builder.setGuestSpaceId(task.getGuestSpaceId().or(-1));
85
101
  }
86
-
87
102
  if (task.getBasicAuthUsername().isPresent() && task.getBasicAuthPassword().isPresent()) {
88
- kintoneAuth.setBasicAuth(task.getBasicAuthUsername().get(),
103
+ builder.withBasicAuth(task.getBasicAuthUsername().get(),
89
104
  task.getBasicAuthPassword().get());
90
105
  }
91
106
 
92
- if (task.getGuestSpaceId().isPresent()) {
93
- this.conn = new Connection(task.getDomain(), kintoneAuth, task.getGuestSpaceId().or(-1));
107
+ if (task.getUsername().isPresent() && task.getPassword().isPresent()) {
108
+ this.client = builder
109
+ .authByPassword(task.getUsername().get(), task.getPassword().get())
110
+ .build();
94
111
  }
95
- else {
96
- this.conn = new Connection(task.getDomain(), kintoneAuth);
112
+ else if (task.getToken().isPresent()) {
113
+ this.client = builder
114
+ .authByApiToken(task.getToken().get())
115
+ .build();
97
116
  }
98
117
  }
99
118
 
100
- private void execute(Consumer<Connection> operation)
119
+ private void execute(Consumer<KintoneClient> operation)
101
120
  {
102
121
  connect(task);
103
- operation.accept(this.conn);
122
+ operation.accept(this.client);
104
123
  }
105
124
 
106
125
  private void insertPage(final Page page)
107
126
  {
108
- execute(conn -> {
127
+ execute(client -> {
109
128
  try {
110
- ArrayList<HashMap<String, FieldValue>> records = new ArrayList<>();
129
+ ArrayList<Record> records = new ArrayList<>();
111
130
  pageReader.setPage(page);
112
131
  KintoneColumnVisitor visitor = new KintoneColumnVisitor(pageReader,
113
132
  task.getColumnOptions());
114
- Record kintoneRecordManager = new Record(conn);
115
133
  while (pageReader.nextRecord()) {
116
- HashMap record = new HashMap();
134
+ Record record = new Record();
117
135
  visitor.setRecord(record);
118
136
  for (Column column : pageReader.getSchema().getColumns()) {
119
137
  column.visit(visitor);
120
138
  }
139
+
121
140
  records.add(record);
122
141
  if (records.size() == 100) {
123
- kintoneRecordManager.addRecords(task.getAppId(), records);
142
+ client.record().addRecords(task.getAppId(), records);
124
143
  records.clear();
125
144
  }
126
145
  }
127
146
  if (records.size() > 0) {
128
- kintoneRecordManager.addRecords(task.getAppId(), records);
147
+ client.record().addRecords(task.getAppId(), records);
148
+ }
149
+ }
150
+ catch (Exception e) {
151
+ throw new RuntimeException("kintone throw exception", e);
152
+ }
153
+ });
154
+ }
155
+
156
+ private void updatePage(final Page page)
157
+ {
158
+ execute(client -> {
159
+ try {
160
+ ArrayList<RecordForUpdate> updateRecords = new ArrayList<RecordForUpdate>();
161
+ pageReader.setPage(page);
162
+ KintoneColumnVisitor visitor = new KintoneColumnVisitor(pageReader,
163
+ task.getColumnOptions());
164
+ while (pageReader.nextRecord()) {
165
+ Record record = new Record();
166
+ UpdateKey updateKey = new UpdateKey();
167
+ visitor.setRecord(record);
168
+ visitor.setUpdateKey(updateKey);
169
+ for (Column column : pageReader.getSchema().getColumns()) {
170
+ column.visit(visitor);
171
+ }
172
+
173
+ record.removeField(updateKey.getField());
174
+ updateRecords.add(new RecordForUpdate(updateKey, record));
175
+ if (updateRecords.size() == 100) {
176
+ client.record().updateRecords(task.getAppId(), updateRecords);
177
+ updateRecords.clear();
178
+ }
179
+ }
180
+ if (updateRecords.size() > 0) {
181
+ client.record().updateRecords(task.getAppId(), updateRecords);
129
182
  }
130
183
  }
131
184
  catch (Exception e) {
@@ -133,4 +186,151 @@ public class KintonePageOutput
133
186
  }
134
187
  });
135
188
  }
189
+
190
+ private List<Record> getAllRecords(String fieldCode)
191
+ {
192
+ List<Record> allRecords = new ArrayList<Record>();
193
+ List<String> fields = Arrays.asList(fieldCode);
194
+ String cursorId = client.record().createCursor(task.getAppId(), fields, null);
195
+ while (true) {
196
+ GetRecordsByCursorResponseBody resp = client.record().getRecordsByCursor(cursorId);
197
+ List<Record> records = resp.getRecords();
198
+ allRecords.addAll(records);
199
+
200
+ if (!resp.hasNext()) {
201
+ break;
202
+ }
203
+ }
204
+ return allRecords;
205
+ }
206
+
207
+ abstract class UpsertPage<T>
208
+ {
209
+ public abstract List<T> getUpdateKeyValues();
210
+ public abstract boolean existsRecord(List<T> updateKeyValues, Record record);
211
+
212
+ public void run(final Page page)
213
+ {
214
+ execute(client -> {
215
+ try {
216
+ List<T> updateKeyValues = getUpdateKeyValues();
217
+
218
+ ArrayList<Record> insertRecords = new ArrayList<>();
219
+ ArrayList<RecordForUpdate> updateRecords = new ArrayList<RecordForUpdate>();
220
+ pageReader.setPage(page);
221
+ KintoneColumnVisitor visitor = new KintoneColumnVisitor(pageReader,
222
+ task.getColumnOptions());
223
+ while (pageReader.nextRecord()) {
224
+ Record record = new Record();
225
+ UpdateKey updateKey = new UpdateKey();
226
+ visitor.setRecord(record);
227
+ visitor.setUpdateKey(updateKey);
228
+ for (Column column : pageReader.getSchema().getColumns()) {
229
+ column.visit(visitor);
230
+ }
231
+
232
+ if (existsRecord(updateKeyValues, record)) {
233
+ record.removeField(updateKey.getField());
234
+ updateRecords.add(new RecordForUpdate(updateKey, record));
235
+ }
236
+ else {
237
+ insertRecords.add(record);
238
+ }
239
+
240
+ if (insertRecords.size() == 100) {
241
+ client.record().addRecords(task.getAppId(), insertRecords);
242
+ insertRecords.clear();
243
+ }
244
+ else if (updateRecords.size() == 100) {
245
+ client.record().updateRecords(task.getAppId(), updateRecords);
246
+ updateRecords.clear();
247
+ }
248
+ }
249
+
250
+ if (insertRecords.size() > 0) {
251
+ client.record().addRecords(task.getAppId(), insertRecords);
252
+ }
253
+ if (updateRecords.size() > 0) {
254
+ client.record().updateRecords(task.getAppId(), updateRecords);
255
+ }
256
+ }
257
+ catch (Exception e) {
258
+ throw new RuntimeException("kintone throw exception", e);
259
+ }
260
+ });
261
+ }
262
+ }
263
+
264
+ class UpsertPageByStringKey extends UpsertPage<String>
265
+ {
266
+ private String fieldCode;
267
+
268
+ public UpsertPageByStringKey(String fieldCode)
269
+ {
270
+ this.fieldCode = fieldCode;
271
+ }
272
+
273
+ public List<String> getUpdateKeyValues()
274
+ {
275
+ return getAllRecords(fieldCode)
276
+ .stream()
277
+ .map(r -> r.getSingleLineTextFieldValue(fieldCode))
278
+ .collect(Collectors.toList());
279
+ }
280
+
281
+ public boolean existsRecord(List<String> updateKeyValues, Record record)
282
+ {
283
+ return updateKeyValues.contains(record.getSingleLineTextFieldValue(fieldCode));
284
+ }
285
+ }
286
+
287
+ class UpsertPageByNumberKey extends UpsertPage<BigDecimal>
288
+ {
289
+ private String fieldCode;
290
+
291
+ public UpsertPageByNumberKey(String fieldCode)
292
+ {
293
+ this.fieldCode = fieldCode;
294
+ }
295
+
296
+ public List<BigDecimal> getUpdateKeyValues()
297
+ {
298
+ return getAllRecords(fieldCode)
299
+ .stream()
300
+ .map(r -> r.getNumberFieldValue(fieldCode))
301
+ .collect(Collectors.toList());
302
+ }
303
+
304
+ public boolean existsRecord(List<BigDecimal> updateKeyValues, Record record)
305
+ {
306
+ return updateKeyValues.contains(record.getNumberFieldValue(fieldCode));
307
+ }
308
+ }
309
+
310
+ private void upsertPage(final Page page)
311
+ {
312
+ KintoneColumnOption updateKeyColumn = null;
313
+ for (KintoneColumnOption v : task.getColumnOptions().values()) {
314
+ if (v.getUpdateKey()) {
315
+ updateKeyColumn = v;
316
+ break;
317
+ }
318
+ }
319
+ if (updateKeyColumn == null) {
320
+ throw new RuntimeException("when mode is upsert, require update_key");
321
+ }
322
+
323
+ UpsertPage runner;
324
+ switch(updateKeyColumn.getType()) {
325
+ case "SINGLE_LINE_TEXT":
326
+ runner = new UpsertPageByStringKey(updateKeyColumn.getFieldCode());
327
+ break;
328
+ case "NUMBER":
329
+ runner = new UpsertPageByNumberKey(updateKeyColumn.getFieldCode());
330
+ break;
331
+ default:
332
+ throw new RuntimeException("The update_key must be 'SINGLE_LINE_TEXT' or 'NUMBER'.");
333
+ }
334
+ runner.run(page);
335
+ }
136
336
  }
@@ -46,5 +46,5 @@ public interface PluginTask
46
46
 
47
47
  @Config("mode")
48
48
  @ConfigDefault("insert")
49
- public KintoneMode getMode();
49
+ public String getMode();
50
50
  }
metadata CHANGED
@@ -1,38 +1,38 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: embulk-output-kintone
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - takeshi fujita
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-12-17 00:00:00.000000000 Z
11
+ date: 2020-09-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: bundler
15
14
  requirement: !ruby/object:Gem::Requirement
16
15
  requirements:
17
16
  - - "~>"
18
17
  - !ruby/object:Gem::Version
19
18
  version: '1.0'
20
- type: :development
19
+ name: bundler
21
20
  prerelease: false
21
+ type: :development
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.0'
27
27
  - !ruby/object:Gem::Dependency
28
- name: rake
29
28
  requirement: !ruby/object:Gem::Requirement
30
29
  requirements:
31
30
  - - "~>"
32
31
  - !ruby/object:Gem::Version
33
32
  version: '12.0'
34
- type: :development
33
+ name: rake
35
34
  prerelease: false
35
+ type: :development
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
@@ -45,15 +45,14 @@ executables: []
45
45
  extensions: []
46
46
  extra_rdoc_files: []
47
47
  files:
48
+ - ".github/workflows/ci.yml"
48
49
  - ".gitignore"
49
50
  - Gemfile
50
51
  - LICENSE.txt
51
52
  - README.md
52
53
  - Rakefile
53
54
  - build.gradle
54
- - classpath/embulk-output-kintone-0.1.0.jar
55
- - classpath/gson-2.8.2.jar
56
- - classpath/kintone-sdk-0.4.0.jar
55
+ - classpath/embulk-output-kintone-0.2.0.jar
57
56
  - config/checkstyle/checkstyle.xml
58
57
  - config/checkstyle/default.xml
59
58
  - gradle/wrapper/gradle-wrapper.jar
@@ -72,7 +71,7 @@ homepage: https://github.com/trocco-io/embulk-output-kintone
72
71
  licenses:
73
72
  - MIT
74
73
  metadata: {}
75
- post_install_message:
74
+ post_install_message:
76
75
  rdoc_options: []
77
76
  require_paths:
78
77
  - lib
@@ -87,8 +86,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
87
86
  - !ruby/object:Gem::Version
88
87
  version: '0'
89
88
  requirements: []
90
- rubygems_version: 3.0.3
91
- signing_key:
89
+ rubyforge_project:
90
+ rubygems_version: 2.6.8
91
+ signing_key:
92
92
  specification_version: 4
93
93
  summary: kintone output plugin for Embulk
94
94
  test_files: []