From 4a8e4ed180fb42b0231ecdfbc8809d1cd0bb2995 Mon Sep 17 00:00:00 2001 From: ruanwenjun Date: Tue, 6 Jul 2021 10:57:12 +0800 Subject: [PATCH] [ISSUE #367]Enhance SPI plugins --- eventmesh-common/gradle.properties | 2 +- eventmesh-spi/build.gradle | 16 +++ eventmesh-spi/gradle.properties | 20 ++++ .../spi/EventMeshExtensionFactory.java | 37 ++++++ .../spi/EventMeshExtensionLoader.java | 110 ++++++++++++++++++ .../apache/eventmesh/spi/EventMeshSPI.java | 35 ++++++ .../eventmesh/spi/ExtensionException.java | 33 ++++++ .../spi/EventMeshExtensionFactoryTest.java | 29 +++++ .../org/apache/eventmesh/spi/ExtensionA.java | 26 +++++ .../apache/eventmesh/spi/TestExtension.java | 24 ++++ .../org.apache.eventmesh.spi.TestExtension | 17 +++ settings.gradle | 9 +- 12 files changed, 356 insertions(+), 2 deletions(-) create mode 100644 eventmesh-spi/build.gradle create mode 100644 eventmesh-spi/gradle.properties create mode 100644 eventmesh-spi/src/main/java/org/apache/eventmesh/spi/EventMeshExtensionFactory.java create mode 100644 eventmesh-spi/src/main/java/org/apache/eventmesh/spi/EventMeshExtensionLoader.java create mode 100644 eventmesh-spi/src/main/java/org/apache/eventmesh/spi/EventMeshSPI.java create mode 100644 eventmesh-spi/src/main/java/org/apache/eventmesh/spi/ExtensionException.java create mode 100644 eventmesh-spi/src/test/java/org/apache/eventmesh/spi/EventMeshExtensionFactoryTest.java create mode 100644 eventmesh-spi/src/test/java/org/apache/eventmesh/spi/ExtensionA.java create mode 100644 eventmesh-spi/src/test/java/org/apache/eventmesh/spi/TestExtension.java create mode 100644 eventmesh-spi/src/test/resources/META-INF/eventmesh/org.apache.eventmesh.spi.TestExtension diff --git a/eventmesh-common/gradle.properties b/eventmesh-common/gradle.properties index b4202ce825..4117dffc92 100644 --- a/eventmesh-common/gradle.properties +++ b/eventmesh-common/gradle.properties @@ -16,4 +16,4 @@ # group=org.apache.eventmesh version=1.2.0-SNAPSHOT -jdk=1.7 +jdk=1.8 diff --git a/eventmesh-spi/build.gradle b/eventmesh-spi/build.gradle new file mode 100644 index 0000000000..d973dcedae --- /dev/null +++ b/eventmesh-spi/build.gradle @@ -0,0 +1,16 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ \ No newline at end of file diff --git a/eventmesh-spi/gradle.properties b/eventmesh-spi/gradle.properties new file mode 100644 index 0000000000..d0503c3b7b --- /dev/null +++ b/eventmesh-spi/gradle.properties @@ -0,0 +1,20 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# +group=org.apache.eventmesh +version=1.2.0-SNAPSHOT +jdk=1.8 +snapshot=false diff --git a/eventmesh-spi/src/main/java/org/apache/eventmesh/spi/EventMeshExtensionFactory.java b/eventmesh-spi/src/main/java/org/apache/eventmesh/spi/EventMeshExtensionFactory.java new file mode 100644 index 0000000000..96e054bd0c --- /dev/null +++ b/eventmesh-spi/src/main/java/org/apache/eventmesh/spi/EventMeshExtensionFactory.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.eventmesh.spi; + +import org.apache.commons.lang3.StringUtils; + +public enum EventMeshExtensionFactory { + ; + + public static T getExtension(Class extensionType, String extensionName) { + if (extensionType == null) { + throw new ExtensionException("extensionType is null"); + } + if (StringUtils.isEmpty(extensionName)) { + throw new ExtensionException("extensionName is null"); + } + if (!extensionType.isInterface() || !extensionType.isAnnotationPresent(EventMeshSPI.class)) { + throw new ExtensionException(String.format("extensionType:%s is invalided", extensionType)); + } + return EventMeshExtensionLoader.getExtension(extensionType, extensionName); + } +} diff --git a/eventmesh-spi/src/main/java/org/apache/eventmesh/spi/EventMeshExtensionLoader.java b/eventmesh-spi/src/main/java/org/apache/eventmesh/spi/EventMeshExtensionLoader.java new file mode 100644 index 0000000000..740ecb3f9d --- /dev/null +++ b/eventmesh-spi/src/main/java/org/apache/eventmesh/spi/EventMeshExtensionLoader.java @@ -0,0 +1,110 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.eventmesh.spi; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Enumeration; +import java.util.Properties; +import java.util.concurrent.ConcurrentHashMap; + +public enum EventMeshExtensionLoader { + ; + + private static final ConcurrentHashMap, ConcurrentHashMap>> EXTENSION_CLASS_LOAD_CACHE = new ConcurrentHashMap<>(16); + + private static final ConcurrentHashMap EXTENSION_INSTANCE_CACHE = new ConcurrentHashMap<>(16); + + private static final String EVENTMESH_EXTENSION_DIR = "META-INF/eventmesh/"; + + @SuppressWarnings("unchecked") + public static T getExtension(Class extensionType, String extensionName) { + if (!hasLoadExtensionClass(extensionType)) { + loadExtensionClass(extensionType); + } + if (!hasInitializeExtension(extensionName)) { + initializeExtension(extensionType, extensionName); + } + return (T) EXTENSION_INSTANCE_CACHE.get(extensionName); + } + + private static void initializeExtension(Class extensionType, String extensionName) { + ConcurrentHashMap> extensionClassMap = EXTENSION_CLASS_LOAD_CACHE.get(extensionType); + if (extensionClassMap == null) { + throw new ExtensionException(String.format("Extension type:%s has not been loaded", extensionType)); + } + if (!extensionClassMap.containsKey(extensionName)) { + throw new ExtensionException(String.format("Extension name:%s has not been loaded", extensionName)); + } + Class aClass = extensionClassMap.get(extensionName); + try { + EXTENSION_INSTANCE_CACHE.put(extensionName, aClass.newInstance()); + } catch (InstantiationException | IllegalAccessException e) { + throw new ExtensionException("Extension initialize error", e); + } + } + + public static void loadExtensionClass(Class extensionType) { + String extensionFileName = EVENTMESH_EXTENSION_DIR + extensionType.getName(); + ClassLoader classLoader = EventMeshExtensionLoader.class.getClassLoader(); + try { + Enumeration extensionUrls = classLoader.getResources(extensionFileName); + if (extensionUrls != null) { + while (extensionUrls.hasMoreElements()) { + URL url = extensionUrls.nextElement(); + loadResources(url, extensionType); + } + } + } catch (IOException e) { + throw new ExtensionException("load extension class error", e); + } + + + } + + private static void loadResources(URL url, Class extensionType) throws IOException { + try (InputStream inputStream = url.openStream()) { + Properties properties = new Properties(); + properties.load(inputStream); + properties.forEach((extensionName, extensionClass) -> { + String extensionNameStr = (String) extensionName; + String extensionClassStr = (String) extensionClass; + try { + Class targetClass = Class.forName(extensionClassStr); + if (!extensionType.isAssignableFrom(targetClass)) { + throw new ExtensionException( + String.format("class: %s is not subClass of %s", targetClass, extensionType)); + } + EXTENSION_CLASS_LOAD_CACHE.computeIfAbsent(extensionType, k -> new ConcurrentHashMap<>()) + .put(extensionNameStr, targetClass); + } catch (ClassNotFoundException e) { + throw new ExtensionException("load extension class error", e); + } + }); + } + } + + private static boolean hasLoadExtensionClass(Class extensionType) { + return EXTENSION_CLASS_LOAD_CACHE.containsKey(extensionType); + } + + private static boolean hasInitializeExtension(String extensionName) { + return EXTENSION_INSTANCE_CACHE.containsKey(extensionName); + } +} diff --git a/eventmesh-spi/src/main/java/org/apache/eventmesh/spi/EventMeshSPI.java b/eventmesh-spi/src/main/java/org/apache/eventmesh/spi/EventMeshSPI.java new file mode 100644 index 0000000000..0ea72d431b --- /dev/null +++ b/eventmesh-spi/src/main/java/org/apache/eventmesh/spi/EventMeshSPI.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.eventmesh.spi; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Just as a marker for SPI + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +public @interface EventMeshSPI { + +} + diff --git a/eventmesh-spi/src/main/java/org/apache/eventmesh/spi/ExtensionException.java b/eventmesh-spi/src/main/java/org/apache/eventmesh/spi/ExtensionException.java new file mode 100644 index 0000000000..874f03da5d --- /dev/null +++ b/eventmesh-spi/src/main/java/org/apache/eventmesh/spi/ExtensionException.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.eventmesh.spi; + +public class ExtensionException extends RuntimeException { + + public ExtensionException(Exception e) { + super(e); + } + + public ExtensionException(String message) { + super(message); + } + + public ExtensionException(String message, Exception e) { + super(message, e); + } +} diff --git a/eventmesh-spi/src/test/java/org/apache/eventmesh/spi/EventMeshExtensionFactoryTest.java b/eventmesh-spi/src/test/java/org/apache/eventmesh/spi/EventMeshExtensionFactoryTest.java new file mode 100644 index 0000000000..649f4b18b2 --- /dev/null +++ b/eventmesh-spi/src/test/java/org/apache/eventmesh/spi/EventMeshExtensionFactoryTest.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.eventmesh.spi; + +import org.junit.Test; + +public class EventMeshExtensionFactoryTest { + + @Test + public void getExtension() { + TestExtension extensionA = EventMeshExtensionFactory.getExtension(TestExtension.class, "extensionA"); + extensionA.hello(); + } +} \ No newline at end of file diff --git a/eventmesh-spi/src/test/java/org/apache/eventmesh/spi/ExtensionA.java b/eventmesh-spi/src/test/java/org/apache/eventmesh/spi/ExtensionA.java new file mode 100644 index 0000000000..03513e6203 --- /dev/null +++ b/eventmesh-spi/src/test/java/org/apache/eventmesh/spi/ExtensionA.java @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.eventmesh.spi; + +public class ExtensionA implements TestExtension { + + @Override + public void hello() { + System.out.println("I am ExtensionA"); + } +} diff --git a/eventmesh-spi/src/test/java/org/apache/eventmesh/spi/TestExtension.java b/eventmesh-spi/src/test/java/org/apache/eventmesh/spi/TestExtension.java new file mode 100644 index 0000000000..c0c9888130 --- /dev/null +++ b/eventmesh-spi/src/test/java/org/apache/eventmesh/spi/TestExtension.java @@ -0,0 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.eventmesh.spi; + +@EventMeshSPI +public interface TestExtension { + + void hello(); +} diff --git a/eventmesh-spi/src/test/resources/META-INF/eventmesh/org.apache.eventmesh.spi.TestExtension b/eventmesh-spi/src/test/resources/META-INF/eventmesh/org.apache.eventmesh.spi.TestExtension new file mode 100644 index 0000000000..3862ccbb4f --- /dev/null +++ b/eventmesh-spi/src/test/resources/META-INF/eventmesh/org.apache.eventmesh.spi.TestExtension @@ -0,0 +1,17 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. + +extensionA=org.apache.eventmesh.spi.ExtensionA \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index ea31ffdc16..2b5e0af662 100644 --- a/settings.gradle +++ b/settings.gradle @@ -17,5 +17,12 @@ rootProject.name = 'EventMesh' String jdkVersion = "${jdk}" -include 'eventmesh-runtime','eventmesh-connector-rocketmq','eventmesh-sdk-java','eventmesh-common','eventmesh-connector-api','eventmesh-starter','eventmesh-test' +include 'eventmesh-runtime' +include 'eventmesh-connector-rocketmq' +include 'eventmesh-sdk-java' +include 'eventmesh-common' +include 'eventmesh-connector-api' +include 'eventmesh-starter' +include 'eventmesh-test' +include 'eventmesh-spi'