From 0010cb229451b121fb5d33794a643c00488019ed Mon Sep 17 00:00:00 2001 From: qianmoQ Date: Thu, 6 Oct 2022 11:48:51 +0800 Subject: [PATCH] plugin: Add kylin --- README.md | 5 ++ plugin/jdbc/kylin/pom.xml | 61 ++++++++++++++ .../plugin/jdbc/kylin/KylinAdapter.java | 13 +++ .../plugin/jdbc/kylin/KylinPlugin.java | 77 ++++++++++++++++++ .../plugin/jdbc/kylin/KylinPluginModule.java | 38 +++++++++ .../io.edurt.datacap.spi.PluginModule | 1 + .../jdbc/kylin/KylinPluginModuleTest.java | 30 +++++++ .../plugin/jdbc/kylin/KylinPluginTest.java | 44 ++++++++++ plugin/jdbc/pom.xml | 1 + server/pom.xml | 5 ++ .../service/impl/ExecuteServiceImpl.java | 1 + .../service/impl/SourceServiceImpl.java | 1 + shared/plugin/kylin.png | Bin 0 -> 14636 bytes 13 files changed, 277 insertions(+) create mode 100644 plugin/jdbc/kylin/pom.xml create mode 100644 plugin/jdbc/kylin/src/main/java/io/edurt/datacap/plugin/jdbc/kylin/KylinAdapter.java create mode 100644 plugin/jdbc/kylin/src/main/java/io/edurt/datacap/plugin/jdbc/kylin/KylinPlugin.java create mode 100644 plugin/jdbc/kylin/src/main/java/io/edurt/datacap/plugin/jdbc/kylin/KylinPluginModule.java create mode 100644 plugin/jdbc/kylin/src/main/resources/META-INF/services/io.edurt.datacap.spi.PluginModule create mode 100644 plugin/jdbc/kylin/src/test/java/io/edurt/datacap/plugin/jdbc/kylin/KylinPluginModuleTest.java create mode 100644 plugin/jdbc/kylin/src/test/java/io/edurt/datacap/plugin/jdbc/kylin/KylinPluginTest.java create mode 100644 shared/plugin/kylin.png diff --git a/README.md b/README.md index 6431e725..22ffaa95 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,11 @@ Here are some of the major database solutions that are supported: Hive
+ + + Kylin + +
diff --git a/plugin/jdbc/kylin/pom.xml b/plugin/jdbc/kylin/pom.xml new file mode 100644 index 00000000..43509f88 --- /dev/null +++ b/plugin/jdbc/kylin/pom.xml @@ -0,0 +1,61 @@ + + + + datacap-plugin-jdbc + io.edurt.datacap.plugin.jdbc + 1.0.0.20221015 + + 4.0.0 + + datacap-plugin-jdbc-kylin + DataCap plugin for jdbc (Kylin) + + + 2.6.3 + jdbc-kylin + + + + + io.edurt.datacap + datacap-spi + provided + + + commons-beanutils + commons-beanutils + + + org.apache.kylin + kylin-jdbc + ${kylin.version} + + + + + + + maven-assembly-plugin + ${assembly-plugin.version} + + ${plugin.name} + + ../../../configure/assembly/assembly-plugin.xml + + ../../../dist/plugins/${plugin.name} + + + + make-assembly + package + + single + + + + + + + \ No newline at end of file diff --git a/plugin/jdbc/kylin/src/main/java/io/edurt/datacap/plugin/jdbc/kylin/KylinAdapter.java b/plugin/jdbc/kylin/src/main/java/io/edurt/datacap/plugin/jdbc/kylin/KylinAdapter.java new file mode 100644 index 00000000..c8fd7aa1 --- /dev/null +++ b/plugin/jdbc/kylin/src/main/java/io/edurt/datacap/plugin/jdbc/kylin/KylinAdapter.java @@ -0,0 +1,13 @@ +package io.edurt.datacap.plugin.jdbc.kylin; + +import io.edurt.datacap.spi.adapter.JdbcAdapter; +import io.edurt.datacap.spi.connection.JdbcConnection; + +public class KylinAdapter + extends JdbcAdapter +{ + public KylinAdapter(JdbcConnection jdbcConnection) + { + super(jdbcConnection); + } +} diff --git a/plugin/jdbc/kylin/src/main/java/io/edurt/datacap/plugin/jdbc/kylin/KylinPlugin.java b/plugin/jdbc/kylin/src/main/java/io/edurt/datacap/plugin/jdbc/kylin/KylinPlugin.java new file mode 100644 index 00000000..075c955e --- /dev/null +++ b/plugin/jdbc/kylin/src/main/java/io/edurt/datacap/plugin/jdbc/kylin/KylinPlugin.java @@ -0,0 +1,77 @@ +package io.edurt.datacap.plugin.jdbc.kylin; + +import io.edurt.datacap.spi.Plugin; +import io.edurt.datacap.spi.PluginType; +import io.edurt.datacap.spi.adapter.JdbcAdapter; +import io.edurt.datacap.spi.connection.JdbcConfigure; +import io.edurt.datacap.spi.connection.JdbcConnection; +import io.edurt.datacap.spi.model.Configure; +import io.edurt.datacap.spi.model.Response; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.beanutils.BeanUtils; +import org.apache.commons.lang3.ObjectUtils; + +@Slf4j +public class KylinPlugin + implements Plugin +{ + private JdbcConfigure jdbcConfigure; + private JdbcConnection connection; + private Response response; + + @Override + public String name() + { + return "Kylin"; + } + + @Override + public String description() + { + return "Integrate Kylin data sources"; + } + + @Override + public PluginType type() + { + return PluginType.SOURCE; + } + + @Override + public void connect(Configure configure) + { + try { + this.response = new Response(); + this.jdbcConfigure = new JdbcConfigure(); + BeanUtils.copyProperties(this.jdbcConfigure, configure); + this.jdbcConfigure.setJdbcDriver("org.apache.kylin.jdbc.Driver"); + this.jdbcConfigure.setJdbcType("kylin"); + this.connection = new JdbcConnection(this.jdbcConfigure, this.response); + } + catch (Exception ex) { + this.response.setIsConnected(Boolean.FALSE); + this.response.setMessage(ex.getMessage()); + } + } + + @Override + public Response execute(String content) + { + if (ObjectUtils.isNotEmpty(this.connection)) { + log.info("Execute kylin plugin logic started"); + this.response = this.connection.getResponse(); + JdbcAdapter processor = new KylinAdapter(this.connection); + this.response = processor.handlerJDBCExecute(content); + log.info("Execute kylin plugin logic end"); + } + return this.response; + } + + @Override + public void destroy() + { + if (ObjectUtils.isNotEmpty(this.connection)) { + this.connection.destroy(); + } + } +} diff --git a/plugin/jdbc/kylin/src/main/java/io/edurt/datacap/plugin/jdbc/kylin/KylinPluginModule.java b/plugin/jdbc/kylin/src/main/java/io/edurt/datacap/plugin/jdbc/kylin/KylinPluginModule.java new file mode 100644 index 00000000..de3708a4 --- /dev/null +++ b/plugin/jdbc/kylin/src/main/java/io/edurt/datacap/plugin/jdbc/kylin/KylinPluginModule.java @@ -0,0 +1,38 @@ +package io.edurt.datacap.plugin.jdbc.kylin; + +import com.google.inject.multibindings.Multibinder; +import io.edurt.datacap.spi.AbstractPluginModule; +import io.edurt.datacap.spi.Plugin; +import io.edurt.datacap.spi.PluginModule; +import io.edurt.datacap.spi.PluginType; + +public class KylinPluginModule + extends AbstractPluginModule + implements PluginModule +{ + @Override + public String getName() + { + return "Kylin"; + } + + @Override + public PluginType getType() + { + return PluginType.SOURCE; + } + + @Override + public AbstractPluginModule get() + { + return this; + } + + protected void configure() + { + Multibinder module = Multibinder.newSetBinder(this.binder(), String.class); + module.addBinding().toInstance(this.getClass().getSimpleName()); + Multibinder plugin = Multibinder.newSetBinder(this.binder(), Plugin.class); + plugin.addBinding().to(KylinPlugin.class); + } +} diff --git a/plugin/jdbc/kylin/src/main/resources/META-INF/services/io.edurt.datacap.spi.PluginModule b/plugin/jdbc/kylin/src/main/resources/META-INF/services/io.edurt.datacap.spi.PluginModule new file mode 100644 index 00000000..4915d350 --- /dev/null +++ b/plugin/jdbc/kylin/src/main/resources/META-INF/services/io.edurt.datacap.spi.PluginModule @@ -0,0 +1 @@ +io.edurt.datacap.plugin.jdbc.kylin.KylinPluginModule diff --git a/plugin/jdbc/kylin/src/test/java/io/edurt/datacap/plugin/jdbc/kylin/KylinPluginModuleTest.java b/plugin/jdbc/kylin/src/test/java/io/edurt/datacap/plugin/jdbc/kylin/KylinPluginModuleTest.java new file mode 100644 index 00000000..161c3cb6 --- /dev/null +++ b/plugin/jdbc/kylin/src/test/java/io/edurt/datacap/plugin/jdbc/kylin/KylinPluginModuleTest.java @@ -0,0 +1,30 @@ +package io.edurt.datacap.plugin.jdbc.kylin; + +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.Key; +import com.google.inject.TypeLiteral; +import io.edurt.datacap.spi.Plugin; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.util.Set; + +public class KylinPluginModuleTest +{ + private Injector injector; + + @Before + public void before() + { + this.injector = Guice.createInjector(new KylinPluginModule()); + } + + @Test + public void test() + { + Set plugins = injector.getInstance(Key.get(new TypeLiteral>() {})); + Assert.assertTrue(plugins.size() > 0); + } +} diff --git a/plugin/jdbc/kylin/src/test/java/io/edurt/datacap/plugin/jdbc/kylin/KylinPluginTest.java b/plugin/jdbc/kylin/src/test/java/io/edurt/datacap/plugin/jdbc/kylin/KylinPluginTest.java new file mode 100644 index 00000000..d88a970c --- /dev/null +++ b/plugin/jdbc/kylin/src/test/java/io/edurt/datacap/plugin/jdbc/kylin/KylinPluginTest.java @@ -0,0 +1,44 @@ +package io.edurt.datacap.plugin.jdbc.kylin; + +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.Key; +import com.google.inject.TypeLiteral; +import io.edurt.datacap.spi.Plugin; +import io.edurt.datacap.spi.model.Configure; +import org.junit.Before; +import org.junit.Test; + +import java.util.Optional; +import java.util.Set; + +public class KylinPluginTest +{ + private Injector injector; + private Configure configure; + + @Before + public void before() + { + injector = Guice.createInjector(new KylinPluginModule()); + configure = new Configure(); + configure.setHost("127.0.0.1"); + configure.setPort(7070); + configure.setDatabase(Optional.of("default")); + } + + @Test + public void test() + { + Set plugins = injector.getInstance(Key.get(new TypeLiteral>() {})); + Optional pluginOptional = plugins.stream() + .filter(v -> v.name().equalsIgnoreCase("Kylin")) + .findFirst(); + if (pluginOptional.isPresent()) { + Plugin plugin = pluginOptional.get(); + plugin.connect(configure); + System.out.println(plugin.execute("SHOW DATABASES")); + plugin.destroy(); + } + } +} diff --git a/plugin/jdbc/pom.xml b/plugin/jdbc/pom.xml index 24c99a68..77bdc568 100644 --- a/plugin/jdbc/pom.xml +++ b/plugin/jdbc/pom.xml @@ -24,5 +24,6 @@ druid kyuubi hive + kylin \ No newline at end of file diff --git a/server/pom.xml b/server/pom.xml index 125a9978..67ed7ff1 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -128,6 +128,11 @@ datacap-plugin-jdbc-hive ${project.version} + + io.edurt.datacap.plugin.jdbc + datacap-plugin-jdbc-kylin + ${project.version} + diff --git a/server/src/main/java/io/edurt/datacap/server/service/impl/ExecuteServiceImpl.java b/server/src/main/java/io/edurt/datacap/server/service/impl/ExecuteServiceImpl.java index 2000a20d..d3f0ae18 100644 --- a/server/src/main/java/io/edurt/datacap/server/service/impl/ExecuteServiceImpl.java +++ b/server/src/main/java/io/edurt/datacap/server/service/impl/ExecuteServiceImpl.java @@ -54,6 +54,7 @@ public class ExecuteServiceImpl _configure.setPort(entity.getPort()); _configure.setUsername(Optional.ofNullable(entity.getUsername())); _configure.setPassword(Optional.ofNullable(entity.getPassword())); + _configure.setDatabase(Optional.ofNullable(entity.getDatabase())); _configure.setEnv(Optional.ofNullable(configure.getEnv())); _configure.setFormat(configure.getFormat()); plugin.connect(_configure); diff --git a/server/src/main/java/io/edurt/datacap/server/service/impl/SourceServiceImpl.java b/server/src/main/java/io/edurt/datacap/server/service/impl/SourceServiceImpl.java index d944e648..650efc5d 100644 --- a/server/src/main/java/io/edurt/datacap/server/service/impl/SourceServiceImpl.java +++ b/server/src/main/java/io/edurt/datacap/server/service/impl/SourceServiceImpl.java @@ -73,6 +73,7 @@ public class SourceServiceImpl _configure.setPort(configure.getPort()); _configure.setUsername(Optional.ofNullable(configure.getUsername())); _configure.setPassword(Optional.ofNullable(configure.getPassword())); + _configure.setDatabase(Optional.ofNullable(configure.getDatabase())); _configure.setEnv(Optional.empty()); _configure.setFormat(FormatType.JSON); plugin.connect(_configure); diff --git a/shared/plugin/kylin.png b/shared/plugin/kylin.png new file mode 100644 index 0000000000000000000000000000000000000000..361847c8bb5d63916aac93d5a4cdfecd3f2e88cb GIT binary patch literal 14636 zcmbWe1yEeUvoE}h+v0A4g%Akt?n{v1kN_db0*kvlB)AjY-9qr-!3pjJ0s(>r53a%A z$=~YUukQC=y|-0cd(NDmo}NC_J=5L4jZjyWhhR}+fj}ULq5@141OjUSUw#ZUpv92r z8!qsJ>8POR0s>(Z{r!SL8Cm2Y5GtvawyvwLin55QgB=Il%)tb~;c4dxP=i2XlAexm zQyYXUoe9Fi%3d6L^raI@XJsZ1)!|p+QgM_;SXwD~J0rBbRkcmMZA^vDppp`FVxA%Z z13QE(oX*qE*4{+eV zU{Cj#BizKn%~c!tG&B7Vouiwx?LP)L zGv!3sBJ2?Mt}XyA_kU;|Egf7PTr3^_e^~$L@qZftP_2r}f5!M9WwEpS&k!!Ia_+!1 z{-u!rk=jMu%Mrn;iEwdnb2dfDxdUP{{GE-Xh^#XL?&{#I?ciYh?-!;1Z;|O_W$Bnz z;HFmgf2Y9mUpgRQa94yl6nJYw>|Da^+=ANNTq0b2BK$n8z`Nt(`X{N1gPE1N*Z)Y$ zFTySS-$;QaV+MDH{~w9XOhwEcobBMigstr076?v9dkZMtf7B=<>tO5P3=9lN$NOK~ z6=h}BogK`rY=H+Zn({JqigL07+`<9^>^vOY{}5M2MMTlw#T9OEico}!LjmP*SXr5g z2=Z|ugn8iH?4~??ChU9yrabH>CS2z12w^_B0IvWqzo3A?zx%@+Ox^x2fq(Zm`@ih3 z=4=I+Al&xhb01F`oCEh|0Hv9Fn9HUJ0qkm0B!vr*BvJyJm+77{pUP5|G!iG$Fu)p z-2WyAJjUP4|F|mP=0DC3VGnpXXTX&mReKbJK)h{=FllYitb=Uvghhqv(3NYGO1D8_ zX&2Q={%4kh`Azh(a-Uf&V%%T{s4xR#IpN<}c0H8L--CE}h%jhsAL5_so}%1DEAf4r z0)^+^Ajhkr$l4N?DSh}FRRMf*{qO#OL*e8lRFB&Wpip=k{TbOD8mMdK;Y4kYiAy{j zR)}r;@WMg_(g28ZVfcv>gHoGav=uH-MRJVuA z-k1R4{dqh^AEXAs%gy{84BzjC4)2)!1}5VKj$$qhacfkbvQLPBzxaEXm~Mki-Y>vb z5Ka1qak><3%$WRlO*l_`{?deBZ!fQDDAzw^HH1Mi>Dot1WUmuh^vU~C4QGfDqteh+ zB5mpvm%IU9KDf56i?p2`XsMyLv_d?Tf`_eu;a#wbRAgqJ{82=X0;6B1ahgUiRK$32 z2y^rZH>6*PHw*Fd66(U(WqMkAn!|ppcv3|$u733~$EgXYe34Fv#bvj~M)Hgc=L_(McLv&I#rNR3C$K+1x>}3{F*g_3SO-BVLw! zpX)ohdQle7`Kl<2zH?Dlwi_4WsB=7&6 z^fo~LEJ{A?E(E`xEH9|8Iv}Sva_yH-J`F@lC4A>c$}1*=WrIfdiO#o`{&9s3m&?yOu-MWG8*aY8mm*J~dZV*Ji6?#dS1TsW|;z$kAmxRJK! z30JE}*5=yyVk7!uyt*o+bhWr@k~8N9g$~81?9E9x5rW`AfiVHO|JC+4LE5<=JdSq3 zk^Kg!9nz6j^NVE>y-+0_ZJ6LHh4(k|Y_q+flGbVqg(KIAWh+5ne2ry~4u%amv6e>D zjs3S&>V1Hof7`Yo9b*UL#S@v>rAwQ8-BCm^K82g3q4^Yuug;f|v|YT2vV4Q&`?XJ0 z{6fHbqvn~2qviUqpuh5cZvd<4S&X=5vkQX!(T*{Hwtb@lJptcw5-KYEK#Cryc~<^T zfsx)5(yr)FVLcijx+8j?8SD$Hq1v-BCcK0V^pcvd7skcYsyui~2_Bj6RJ^5{B#F%C z0JXilllbC@M`TB~RyER_mJWVEJ_M3=4*4&Go!1WT* z8DbeLkWWc4UJi3q*$Y05-BP#4rG|YMY%h&iztqWY+})8$=8{DgHK}X50vy^g28HBIhk(Z1gw;npt*ro%!@v%n~R zDMD4BHzS-$SKRDOK`2!au6NLqqe=0(bB4g^*V^1EZ~}f%!~$AZDq5I>Yb8M`DyUNi z>ueq$pv3g^KypT^9l7#AUiDB~FxvSX-GUZXCSBHbEUIlpyzSN)&3~0H?QLgDG@V(C zr?Kyo2etwe-V=(XhH0*lAEC0x!p{ zQB2))3({Iz?-vbAm_i{n#V6IkuK0|2!6K52!7eK_%? zv(7tncs`Fk_>MazGpf2K{7rDEb?5q~t*vo$XBBU!M4hGM=r*^P5#QI9f?S=OoHot; zF|I8NQjL*#9m-YB`)7PhoBfWr5`?V+Qi72>3w*x|2iXIUs99(6qRJSU4-sv#j14On zJ!dg;y}#`HnMswt%l|anMnd3c)%Ckm5urTkh2nfzMNSNmijW6uRqSBL2luXyv0 zDUk^hwZkXWBG5(PGl!NYqlow|GMpx@DssS=&ZnmfEA1X`0A~o*i#;5qwX-8US4v9` z1{4^%@s7UqLaiwxh1*pqnj91p`k@HqKk&yo+MJ}?=!=GO4;5<8 zuOg)T>4lC39AQXyXS0Cu`UYaOk~GDcOp)0A+!^^8MW1vcwRgph?u90wJuE#^ZEwdVWB9n#L*!xzH

`HP>wjys}%l6&IX@`p3K#nrHWXkbKHAKw- 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