diff --git a/conf/serviceConfig/helloworld.xml b/conf/serviceConfig/helloworld.xml
new file mode 100644
index 00000000000..4ee40379d65
--- /dev/null
+++ b/conf/serviceConfig/helloworld.xml
@@ -0,0 +1,13 @@
+
+
+ helloworld
+ HelloWorldApiInterceptor
+
+
+ org.zstack.plugin.example.APIHelloWorldMsg
+
+
+
+ org.zstack.plugin.example.APICreateGreetingMsg
+
+
\ No newline at end of file
diff --git a/conf/springConfigXml/helloworld.xml b/conf/springConfigXml/helloworld.xml
new file mode 100644
index 00000000000..6f55b01789a
--- /dev/null
+++ b/conf/springConfigXml/helloworld.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/conf/zstack.xml b/conf/zstack.xml
index a4bd0a088b8..f099fe1d067 100755
--- a/conf/zstack.xml
+++ b/conf/zstack.xml
@@ -119,4 +119,5 @@
+
diff --git a/plugin/helloworld/pom.xml b/plugin/helloworld/pom.xml
new file mode 100755
index 00000000000..b476f9ac9b0
--- /dev/null
+++ b/plugin/helloworld/pom.xml
@@ -0,0 +1,77 @@
+
+
+
+ plugin
+ org.zstack
+ 4.10.0
+
+ 4.0.0
+
+ helloworld
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ ${project.compiler.version}
+
+ groovy-eclipse-compiler
+ ${project.java.version}
+ ${project.java.version}
+ lines,vars,source
+ true
+
+
+
+ org.codehaus.groovy
+ groovy-eclipse-compiler
+ ${groovy.eclipse.compiler}
+
+
+ org.codehaus.groovy
+ groovy-eclipse-batch
+ ${groovy.eclipse.batch}
+
+
+
+
+ org.codehaus.mojo
+ aspectj-maven-plugin
+ ${aspectj.plugin.version}
+
+
+
+ compile
+ test-compile
+
+
+
+
+ ${project.java.version}
+ ${project.java.version}
+ ${project.java.version}
+ true
+
+
+ org.springframework
+ spring-aspects
+
+
+ org.zstack
+ core
+
+
+ org.zstack
+ header
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/plugin/helloworld/src/main/java/org/zstack/plugin/example/APICreateGreetingEvent.java b/plugin/helloworld/src/main/java/org/zstack/plugin/example/APICreateGreetingEvent.java
new file mode 100755
index 00000000000..6016d8a9523
--- /dev/null
+++ b/plugin/helloworld/src/main/java/org/zstack/plugin/example/APICreateGreetingEvent.java
@@ -0,0 +1,24 @@
+package org.zstack.plugin.example;
+
+import org.zstack.header.message.APIEvent;
+import org.zstack.header.rest.RestResponse;
+
+@RestResponse(allTo = "inventory")
+public class APICreateGreetingEvent extends APIEvent {
+ private GreetingInventory inventory;
+
+ public APICreateGreetingEvent() {
+ }
+
+ public APICreateGreetingEvent(String apiId) {
+ super(apiId);
+ }
+
+ public GreetingInventory getInventory() {
+ return inventory;
+ }
+
+ public void setInventory(GreetingInventory inventory) {
+ this.inventory = inventory;
+ }
+}
diff --git a/plugin/helloworld/src/main/java/org/zstack/plugin/example/APICreateGreetingMsg.java b/plugin/helloworld/src/main/java/org/zstack/plugin/example/APICreateGreetingMsg.java
new file mode 100755
index 00000000000..f600ff7d579
--- /dev/null
+++ b/plugin/helloworld/src/main/java/org/zstack/plugin/example/APICreateGreetingMsg.java
@@ -0,0 +1,21 @@
+package org.zstack.plugin.example;
+
+import org.springframework.http.HttpMethod;
+import org.zstack.header.message.APICreateMessage;
+import org.zstack.header.message.APIParam;
+import org.zstack.header.rest.RestRequest;
+
+
+@RestRequest(path = "/helloworld/greetings", method = HttpMethod.POST, responseClass = APICreateGreetingEvent.class, parameterName = "params")
+public class APICreateGreetingMsg extends APICreateMessage {
+ @APIParam(emptyString = false)
+ private String greeting;
+
+ public String getGreeting() {
+ return greeting;
+ }
+
+ public void setGreeting(String greeting) {
+ this.greeting = greeting;
+ }
+}
diff --git a/plugin/helloworld/src/main/java/org/zstack/plugin/example/APIDeleteGreetingEvent.java b/plugin/helloworld/src/main/java/org/zstack/plugin/example/APIDeleteGreetingEvent.java
new file mode 100755
index 00000000000..9b97a4818e9
--- /dev/null
+++ b/plugin/helloworld/src/main/java/org/zstack/plugin/example/APIDeleteGreetingEvent.java
@@ -0,0 +1,14 @@
+package org.zstack.plugin.example;
+
+import org.zstack.header.message.APIEvent;
+import org.zstack.header.rest.RestResponse;
+
+@RestResponse
+public class APIDeleteGreetingEvent extends APIEvent {
+ public APIDeleteGreetingEvent() {
+ }
+
+ public APIDeleteGreetingEvent(String apiId) {
+ super(apiId);
+ }
+}
diff --git a/plugin/helloworld/src/main/java/org/zstack/plugin/example/APIDeleteGreetingMsg.java b/plugin/helloworld/src/main/java/org/zstack/plugin/example/APIDeleteGreetingMsg.java
new file mode 100755
index 00000000000..d2cb9466b14
--- /dev/null
+++ b/plugin/helloworld/src/main/java/org/zstack/plugin/example/APIDeleteGreetingMsg.java
@@ -0,0 +1,23 @@
+package org.zstack.plugin.example;
+
+import org.springframework.http.HttpMethod;
+import org.zstack.header.message.APIDeleteMessage;
+import org.zstack.header.rest.RestRequest;
+
+@RestRequest(path = "/helloworld/greetings/{uuid}", method = HttpMethod.DELETE, responseClass = APIDeleteGreetingEvent.class)
+public class APIDeleteGreetingMsg extends APIDeleteMessage implements GreetingMessage {
+ private String uuid;
+
+ public String getUuid() {
+ return uuid;
+ }
+
+ public void setUuid(String uuid) {
+ this.uuid = uuid;
+ }
+
+ @Override
+ public String getGreetingUuid() {
+ return uuid;
+ }
+}
diff --git a/plugin/helloworld/src/main/java/org/zstack/plugin/example/APIHelloWorldEvent.java b/plugin/helloworld/src/main/java/org/zstack/plugin/example/APIHelloWorldEvent.java
new file mode 100755
index 00000000000..1b8e526e2fa
--- /dev/null
+++ b/plugin/helloworld/src/main/java/org/zstack/plugin/example/APIHelloWorldEvent.java
@@ -0,0 +1,24 @@
+package org.zstack.plugin.example;
+
+import org.zstack.header.message.APIEvent;
+import org.zstack.header.rest.RestResponse;
+
+@RestResponse(allTo = "greeting")
+public class APIHelloWorldEvent extends APIEvent {
+ private String greeting;
+
+ public APIHelloWorldEvent() {
+ }
+
+ public APIHelloWorldEvent(String apiId) {
+ super(apiId);
+ }
+
+ public String getGreeting() {
+ return greeting;
+ }
+
+ public void setGreeting(String greeting) {
+ this.greeting = greeting;
+ }
+}
\ No newline at end of file
diff --git a/plugin/helloworld/src/main/java/org/zstack/plugin/example/APIHelloWorldMsg.java b/plugin/helloworld/src/main/java/org/zstack/plugin/example/APIHelloWorldMsg.java
new file mode 100755
index 00000000000..da082f429bc
--- /dev/null
+++ b/plugin/helloworld/src/main/java/org/zstack/plugin/example/APIHelloWorldMsg.java
@@ -0,0 +1,20 @@
+package org.zstack.plugin.example;
+
+import org.springframework.http.HttpMethod;
+import org.zstack.header.message.APIMessage;
+import org.zstack.header.message.APIParam;
+import org.zstack.header.rest.RestRequest;
+
+@RestRequest(path = "/helloworld/greetings", method = HttpMethod.POST, responseClass = APIHelloWorldEvent.class)
+public class APIHelloWorldMsg extends APIMessage {
+ @APIParam(maxLength = 255)
+ private String greeting;
+
+ public String getGreeting() {
+ return greeting;
+ }
+
+ public void setGreeting(String greeting) {
+ this.greeting = greeting;
+ }
+}
\ No newline at end of file
diff --git a/plugin/helloworld/src/main/java/org/zstack/plugin/example/Greeting.java b/plugin/helloworld/src/main/java/org/zstack/plugin/example/Greeting.java
new file mode 100755
index 00000000000..1b9eb8b72c9
--- /dev/null
+++ b/plugin/helloworld/src/main/java/org/zstack/plugin/example/Greeting.java
@@ -0,0 +1,7 @@
+package org.zstack.plugin.example;
+
+import org.zstack.header.message.Message;
+
+public interface Greeting {
+ void handleMessage(Message msg);
+}
diff --git a/plugin/helloworld/src/main/java/org/zstack/plugin/example/GreetingBase.java b/plugin/helloworld/src/main/java/org/zstack/plugin/example/GreetingBase.java
new file mode 100755
index 00000000000..6e793d7874a
--- /dev/null
+++ b/plugin/helloworld/src/main/java/org/zstack/plugin/example/GreetingBase.java
@@ -0,0 +1,50 @@
+package org.zstack.plugin.example;
+
+import org.springframework.beans.factory.annotation.Autowire;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Configurable;
+import org.zstack.core.cloudbus.CloudBus;
+import org.zstack.core.db.DatabaseFacade;
+import org.zstack.header.message.APIMessage;
+import org.zstack.header.message.Message;
+
+@Configurable(preConstruction = true, autowire = Autowire.BY_TYPE)
+public class GreetingBase implements Greeting {
+ protected GreetingVO self;
+
+ @Autowired
+ protected CloudBus bus;
+ @Autowired
+ protected DatabaseFacade dbf;
+
+ public GreetingBase(GreetingVO self) {
+ this.self = self;
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg instanceof APIMessage) {
+ handleApiMessage((APIMessage) msg);
+ } else {
+ handleLocalMessage(msg);
+ }
+ }
+
+ private void handleLocalMessage(Message msg) {
+ bus.dealWithUnknownMessage(msg);
+ }
+
+ private void handleApiMessage(APIMessage msg) {
+ if (msg instanceof APIDeleteGreetingMsg) {
+ handle((APIDeleteGreetingMsg) msg);
+ } else {
+ bus.dealWithUnknownMessage(msg);
+ }
+ }
+
+ private void handle(APIDeleteGreetingMsg msg) {
+ dbf.remove(self);
+
+ bus.publish(new APIDeleteGreetingEvent(msg.getId()));
+ }
+}
diff --git a/plugin/helloworld/src/main/java/org/zstack/plugin/example/GreetingInventory.java b/plugin/helloworld/src/main/java/org/zstack/plugin/example/GreetingInventory.java
new file mode 100755
index 00000000000..f310cfeb7e9
--- /dev/null
+++ b/plugin/helloworld/src/main/java/org/zstack/plugin/example/GreetingInventory.java
@@ -0,0 +1,61 @@
+package org.zstack.plugin.example;
+
+import org.zstack.header.search.Inventory;
+
+import java.sql.Timestamp;
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Inventory(mappingVOClass = GreetingVO.class)
+public class GreetingInventory {
+ private String uuid;
+ private String greeting;
+ private Timestamp lastOpDate;
+ private Timestamp createDate;
+
+ public static GreetingInventory valueOf(GreetingVO vo) {
+ GreetingInventory inv = new GreetingInventory();
+ inv.uuid = vo.getUuid();
+ inv.greeting = vo.getGreeting();
+ inv.lastOpDate = vo.getLastOpDate();
+ inv.createDate = vo.getCreateDate();
+ return inv;
+ }
+
+ public static List valueOf(Collection vos) {
+ return vos.stream().map(GreetingInventory::valueOf).collect(Collectors.toList());
+ }
+
+ public String getUuid() {
+ return uuid;
+ }
+
+ public void setUuid(String uuid) {
+ this.uuid = uuid;
+ }
+
+ public String getGreeting() {
+ return greeting;
+ }
+
+ public void setGreeting(String greeting) {
+ this.greeting = greeting;
+ }
+
+ public Timestamp getLastOpDate() {
+ return lastOpDate;
+ }
+
+ public void setLastOpDate(Timestamp lastOpDate) {
+ this.lastOpDate = lastOpDate;
+ }
+
+ public Timestamp getCreateDate() {
+ return createDate;
+ }
+
+ public void setCreateDate(Timestamp createDate) {
+ this.createDate = createDate;
+ }
+}
diff --git a/plugin/helloworld/src/main/java/org/zstack/plugin/example/GreetingMessage.java b/plugin/helloworld/src/main/java/org/zstack/plugin/example/GreetingMessage.java
new file mode 100755
index 00000000000..23f52211e39
--- /dev/null
+++ b/plugin/helloworld/src/main/java/org/zstack/plugin/example/GreetingMessage.java
@@ -0,0 +1,5 @@
+package org.zstack.plugin.example;
+
+public interface GreetingMessage {
+ String getGreetingUuid();
+}
diff --git a/plugin/helloworld/src/main/java/org/zstack/plugin/example/GreetingVO.java b/plugin/helloworld/src/main/java/org/zstack/plugin/example/GreetingVO.java
new file mode 100755
index 00000000000..615bc6954b4
--- /dev/null
+++ b/plugin/helloworld/src/main/java/org/zstack/plugin/example/GreetingVO.java
@@ -0,0 +1,51 @@
+package org.zstack.plugin.example;
+
+import org.zstack.header.vo.ResourceVO;
+import org.zstack.header.vo.ToInventory;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.PreUpdate;
+import javax.persistence.Table;
+import java.sql.Timestamp;
+
+@Entity
+@Table
+public class GreetingVO extends ResourceVO implements ToInventory {
+ @Column
+ private String greeting;
+
+ @Column
+ private Timestamp lastOpDate;
+ @Column
+ private Timestamp createDate;
+
+ @PreUpdate
+ void preUpdate() {
+ lastOpDate = null;
+ }
+
+ public String getGreeting() {
+ return greeting;
+ }
+
+ public void setGreeting(String greeting) {
+ this.greeting = greeting;
+ }
+
+ public Timestamp getLastOpDate() {
+ return lastOpDate;
+ }
+
+ public void setLastOpDate(Timestamp lastOpDate) {
+ this.lastOpDate = lastOpDate;
+ }
+
+ public Timestamp getCreateDate() {
+ return createDate;
+ }
+
+ public void setCreateDate(Timestamp createDate) {
+ this.createDate = createDate;
+ }
+}
diff --git a/plugin/helloworld/src/main/java/org/zstack/plugin/example/GreetingVO_.java b/plugin/helloworld/src/main/java/org/zstack/plugin/example/GreetingVO_.java
new file mode 100755
index 00000000000..8dee790974c
--- /dev/null
+++ b/plugin/helloworld/src/main/java/org/zstack/plugin/example/GreetingVO_.java
@@ -0,0 +1,14 @@
+package org.zstack.plugin.example;
+
+ import org.zstack.header.vo.ResourceVO_;
+
+ import javax.persistence.metamodel.SingularAttribute;
+ import javax.persistence.metamodel.StaticMetamodel;
+ import java.sql.Timestamp;
+
+@StaticMetamodel(GreetingVO.class)
+public class GreetingVO_ extends ResourceVO_ {
+ public static volatile SingularAttribute greeting;
+ public static volatile SingularAttribute lastOpDate;
+ public static volatile SingularAttribute createDate;
+}
diff --git a/plugin/helloworld/src/main/java/org/zstack/plugin/example/HelloWorldApiInterceptor.java b/plugin/helloworld/src/main/java/org/zstack/plugin/example/HelloWorldApiInterceptor.java
new file mode 100755
index 00000000000..4c93ae797da
--- /dev/null
+++ b/plugin/helloworld/src/main/java/org/zstack/plugin/example/HelloWorldApiInterceptor.java
@@ -0,0 +1,23 @@
+package org.zstack.plugin.example;
+
+import org.zstack.header.apimediator.ApiMessageInterceptionException;
+import org.zstack.header.apimediator.ApiMessageInterceptor;
+import org.zstack.header.message.APIMessage;
+import static org.zstack.core.Platform.argerr;
+
+public class HelloWorldApiInterceptor implements ApiMessageInterceptor {
+ @Override
+ public APIMessage intercept(APIMessage msg) throws ApiMessageInterceptionException {
+ if (msg instanceof APIHelloWorldMsg) {
+ validate((APIHelloWorldMsg) msg);
+ }
+
+ return msg;
+ }
+
+ private void validate(APIHelloWorldMsg msg) {
+ if (msg.getGreeting() == null || msg.getGreeting().isEmpty()) {
+ throw new ApiMessageInterceptionException(argerr("greeting cannot be null or empty"));
+ }
+ }
+}
diff --git a/plugin/helloworld/src/main/java/org/zstack/plugin/example/HelloWorldManager.java b/plugin/helloworld/src/main/java/org/zstack/plugin/example/HelloWorldManager.java
new file mode 100755
index 00000000000..6001568a3e5
--- /dev/null
+++ b/plugin/helloworld/src/main/java/org/zstack/plugin/example/HelloWorldManager.java
@@ -0,0 +1,5 @@
+package org.zstack.plugin.example;
+
+public interface HelloWorldManager {
+ String SERVICE_ID = "helloworld";
+}
diff --git a/plugin/helloworld/src/main/java/org/zstack/plugin/example/HelloWorldManagerImpl.java b/plugin/helloworld/src/main/java/org/zstack/plugin/example/HelloWorldManagerImpl.java
new file mode 100755
index 00000000000..9fc98db0628
--- /dev/null
+++ b/plugin/helloworld/src/main/java/org/zstack/plugin/example/HelloWorldManagerImpl.java
@@ -0,0 +1,93 @@
+package org.zstack.plugin.example;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.zstack.core.Platform;
+import org.zstack.core.cloudbus.CloudBus;
+import org.zstack.core.cloudbus.MessageSafe;
+import org.zstack.core.db.DatabaseFacade;
+import org.zstack.header.AbstractService;
+import org.zstack.header.Component;
+import org.zstack.header.errorcode.OperationFailureException;
+import org.zstack.header.message.APIMessage;
+import org.zstack.header.message.Message;
+import org.zstack.utils.Utils;
+import org.zstack.utils.logging.CLogger;
+import static org.zstack.core.Platform.operr;
+
+public class HelloWorldManagerImpl extends AbstractService implements HelloWorldManager, Component {
+ private static final CLogger logger = Utils.getLogger(HelloWorldManagerImpl.class);
+
+ @Autowired
+ private CloudBus bus;
+ @Autowired
+ private DatabaseFacade dbf;
+
+ @Override
+ @MessageSafe
+ public void handleMessage(Message msg) {
+ if (msg instanceof GreetingMessage) {
+ passThrough((GreetingMessage) msg);
+ } else if (msg instanceof APIMessage) {
+ handleAPIMessage((APIMessage) msg);
+ } else {
+ handleLocalMessage(msg);
+ }
+ }
+
+ private void passThrough(GreetingMessage msg) {
+ GreetingVO vo = dbf.findByUuid(msg.getGreetingUuid(), GreetingVO.class);
+ if (vo == null) {
+ throw new OperationFailureException(operr("cannot find GreetingVO[uuid:%s], it may have been deleted", msg.getGreetingUuid()));
+ }
+
+ new GreetingBase(vo).handleMessage((Message) msg);
+ }
+
+ private void handleLocalMessage(Message msg) {
+ bus.dealWithUnknownMessage(msg);
+ }
+
+ private void handleAPIMessage(APIMessage msg) {
+ if (msg instanceof APIHelloWorldMsg) {
+ handle((APIHelloWorldMsg) msg);
+ } else if (msg instanceof APICreateGreetingMsg) {
+ handle((APICreateGreetingMsg) msg);
+ } else {
+ bus.dealWithUnknownMessage(msg);
+ }
+ }
+
+ private void handle(APICreateGreetingMsg msg) {
+ GreetingVO vo = new GreetingVO();
+ vo.setUuid(msg.getResourceUuid() == null ? Platform.getUuid() : msg.getResourceUuid());
+ vo.setGreeting(msg.getGreeting());
+ vo = dbf.updateAndRefresh(vo);
+
+ APICreateGreetingEvent evt = new APICreateGreetingEvent(msg.getId());
+ evt.setInventory(vo.toInventory());
+ bus.publish(evt);
+ }
+
+ private void handle(APIHelloWorldMsg msg) {
+ logger.debug(String.format("say hello: %s", msg));
+
+ APIHelloWorldEvent evt = new APIHelloWorldEvent(msg.getId());
+ evt.setGreeting(msg.getGreeting());
+ bus.publish(evt);
+ }
+
+ @Override
+ public String getId() {
+ return bus.makeLocalServiceId(SERVICE_ID);
+ }
+
+ @Override
+ public boolean start() {
+ return true;
+ }
+
+ @Override
+ public boolean stop() {
+ return true;
+ }
+}