embulk-output-kintone 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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: []