diff --git a/client/pom.xml b/client/pom.xml
index d8fa433d5be3..0eb77389e9d5 100644
--- a/client/pom.xml
+++ b/client/pom.xml
@@ -28,6 +28,11 @@
4.23.0.0-SNAPSHOT
+
+ org.apache.cloudstack
+ cloud-plugin-hackerbook-feature
+ ${project.version}
+
javax.servlet
javax.servlet-api
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-42200to42300.sql b/engine/schema/src/main/resources/META-INF/db/schema-42200to42300.sql
index c1f1bb2c094d..48b7e9ce640a 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-42200to42300.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-42200to42300.sql
@@ -18,3 +18,17 @@
--;
-- Schema upgrade from 4.22.0.0 to 4.23.0.0
--;
+CREATE TABLE IF NOT EXISTS `cloud`.`coffee` (
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
+ `uuid` varchar(40) UNIQUE,
+ `name` varchar(255) NOT NULL,
+ `offering` varchar(40) NOT NULL,
+ `size` varchar(40) NOT NULL,
+ `state` varchar(40) NOT NULL,
+ `account_id` bigint unsigned NOT NULL,
+ `created` datetime NOT NULL COMMENT 'date of creation',
+ `removed` datetime COMMENT 'date of removal',
+ PRIMARY KEY (`id`),
+ KEY (`uuid`),
+ KEY `i_coffee` (`name`, `account_id`, `created`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
diff --git a/plugins/hackerbook/feature/pom.xml b/plugins/hackerbook/feature/pom.xml
new file mode 100644
index 000000000000..a88f5657c4aa
--- /dev/null
+++ b/plugins/hackerbook/feature/pom.xml
@@ -0,0 +1,48 @@
+
+
+ 4.0.0
+ cloud-plugin-hackerbook-feature
+ Apache CloudStack Plugin - HackerBook Coffee Feature
+
+ org.apache.cloudstack
+ cloudstack-plugins
+ 4.23.0.0-SNAPSHOT
+ ../../pom.xml
+
+
+
+ org.apache.cloudstack
+ cloud-api
+ ${project.version}
+
+
+ org.apache.cloudstack
+ cloud-utils
+ ${project.version}
+
+
+
+ 11
+ 11
+ UTF-8
+
+
diff --git a/plugins/hackerbook/feature/src/main/java/org/apache/cloudstack/api/Coffee.java b/plugins/hackerbook/feature/src/main/java/org/apache/cloudstack/api/Coffee.java
new file mode 100644
index 000000000000..7fb706f03a5d
--- /dev/null
+++ b/plugins/hackerbook/feature/src/main/java/org/apache/cloudstack/api/Coffee.java
@@ -0,0 +1,79 @@
+/*
+ * 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.cloudstack.api;
+
+/**
+ * Represents a coffee order in the system.
+ * @since 4.23.0.0
+ */
+public interface Coffee extends InternalIdentity, Identity {
+
+ /**
+ * Size options for coffee orders.
+ */
+ enum Size {
+ SMALL,
+ MEDIUM,
+ LARGE
+ }
+
+ /**
+ * Available coffee offerings/types.
+ */
+ enum Offering {
+ ESPRESSO,
+ CAPPUCCINO,
+ MOCHA,
+ LATTE
+ }
+
+ /**
+ * Lifecycle states for a coffee order.
+ */
+ enum State {
+ CREATED,
+ BREWING,
+ BREWED
+ }
+
+ /**
+ * Returns the name of the coffee order.
+ * @return the coffee name
+ */
+ String getName();
+
+ /**
+ * Returns the type of coffee ordered.
+ * @return the coffee offering type
+ */
+ Offering getOffering();
+
+ /**
+ * Returns the size of the coffee order.
+ * @return the coffee size
+ */
+ Size getSize();
+
+ /**
+ * Returns the current state of the coffee order.
+ * @return the coffee state
+ */
+ State getState();
+}
diff --git a/plugins/hackerbook/feature/src/main/java/org/apache/cloudstack/api/CoffeeManager.java b/plugins/hackerbook/feature/src/main/java/org/apache/cloudstack/api/CoffeeManager.java
new file mode 100644
index 000000000000..ee97063f3a1a
--- /dev/null
+++ b/plugins/hackerbook/feature/src/main/java/org/apache/cloudstack/api/CoffeeManager.java
@@ -0,0 +1,42 @@
+/*
+ * 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.cloudstack.api;
+
+import org.apache.cloudstack.api.command.CreateCoffeeCmd;
+import org.apache.cloudstack.api.command.ListCoffeeCmd;
+import org.apache.cloudstack.api.command.RemoveCoffeeCmd;
+import org.apache.cloudstack.api.command.UpdateCoffeeCmd;
+import org.apache.cloudstack.api.response.CoffeeResponse;
+
+import java.util.List;
+
+public interface CoffeeManager {
+ Coffee createCoffee(CreateCoffeeCmd cmd);
+
+ List listCoffees(ListCoffeeCmd cmd);
+
+ Coffee updateCoffee(UpdateCoffeeCmd cmd);
+
+ boolean removeCoffee(RemoveCoffeeCmd cmd);
+
+ CoffeeResponse createCoffeeResponse(Coffee coffee);
+
+ List createCoffeeResponses(List coffees);
+}
diff --git a/plugins/hackerbook/feature/src/main/java/org/apache/cloudstack/api/command/CreateCoffeeCmd.java b/plugins/hackerbook/feature/src/main/java/org/apache/cloudstack/api/command/CreateCoffeeCmd.java
new file mode 100644
index 000000000000..a98a9209cc51
--- /dev/null
+++ b/plugins/hackerbook/feature/src/main/java/org/apache/cloudstack/api/command/CreateCoffeeCmd.java
@@ -0,0 +1,124 @@
+/*
+ * 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.cloudstack.api.command;
+
+import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.BaseAsyncCreateCmd;
+import org.apache.cloudstack.api.Parameter;
+import org.apache.cloudstack.api.Coffee;
+import org.apache.cloudstack.api.CoffeeManager;
+import org.apache.cloudstack.api.response.CoffeeResponse;
+import org.apache.cloudstack.acl.RoleType;
+import org.apache.cloudstack.context.CallContext;
+
+import javax.inject.Inject;
+
+@APICommand(
+ name = CreateCoffeeCmd.APINAME,
+ description = "Creates a new coffee order",
+ responseObject = CoffeeResponse.class,
+ since = "4.23.0.0",
+ requestHasSensitiveInfo = false,
+ responseHasSensitiveInfo = false,
+ authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}
+)
+public class CreateCoffeeCmd extends BaseAsyncCreateCmd {
+ public static final String APINAME = "createCoffee";
+
+ @Inject
+ private CoffeeManager coffeeManager;
+
+ private Coffee coffee;
+
+ /////////////////////////////////////////////////////
+ //////////////// API parameters /////////////////////
+ /////////////////////////////////////////////////////
+
+ @Parameter(name = ApiConstants.NAME,
+ type = CommandType.STRING,
+ required = true,
+ description = "name of the coffee order")
+ private String name;
+
+ @Parameter(name = "offering",
+ type = CommandType.STRING,
+ required = true,
+ description = "type of coffee (ESPRESSO, CAPPUCCINO, MOCHA, LATTE)")
+ private String offering;
+
+ @Parameter(name = "size",
+ type = CommandType.STRING,
+ required = true,
+ description = "size of coffee (SMALL, MEDIUM, LARGE)")
+ private String size;
+
+ /////////////////////////////////////////////////////
+ /////////////////// Accessors ///////////////////////
+ /////////////////////////////////////////////////////
+
+ public String getName() {
+ return name;
+ }
+
+ public String getOffering() {
+ return offering;
+ }
+
+ public String getSize() {
+ return size;
+ }
+
+ /////////////////////////////////////////////////////
+ /////////////// API Implementation///////////////////
+ /////////////////////////////////////////////////////
+
+ @Override
+ public void create() {
+ coffee = coffeeManager.createCoffee(this);
+
+ if (coffee != null) {
+ setEntityId(coffee.getId());
+ setEntityUuid(coffee.getUuid());
+ }
+ }
+
+ @Override
+ public void execute() {
+ CoffeeResponse response = coffeeManager.createCoffeeResponse(coffee);
+ response.setResponseName(getCommandName());
+ setResponseObject(response);
+ }
+
+ @Override
+ public String getEventType() {
+ return "COFFEE.CREATE";
+ }
+
+ @Override
+ public String getEventDescription() {
+ return "Creating coffee: " + name;
+ }
+
+ @Override
+ public long getEntityOwnerId() {
+ return CallContext.current().getCallingAccountId();
+ }
+}
diff --git a/plugins/hackerbook/feature/src/main/java/org/apache/cloudstack/api/command/ListCoffeeCmd.java b/plugins/hackerbook/feature/src/main/java/org/apache/cloudstack/api/command/ListCoffeeCmd.java
new file mode 100644
index 000000000000..1099553e538e
--- /dev/null
+++ b/plugins/hackerbook/feature/src/main/java/org/apache/cloudstack/api/command/ListCoffeeCmd.java
@@ -0,0 +1,108 @@
+/*
+ * 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.cloudstack.api.command;
+
+import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.BaseListCmd;
+import org.apache.cloudstack.api.Parameter;
+import org.apache.cloudstack.api.Coffee;
+import org.apache.cloudstack.api.CoffeeManager;
+import org.apache.cloudstack.api.response.CoffeeResponse;
+import org.apache.cloudstack.api.response.ListResponse;
+import org.apache.cloudstack.acl.RoleType;
+import org.apache.cloudstack.context.CallContext;
+
+import javax.inject.Inject;
+import java.util.List;
+
+@APICommand(
+ name = ListCoffeeCmd.APINAME,
+ description = "Lists all coffees with optional filters",
+ responseObject = CoffeeResponse.class,
+ since = "4.23.0.0",
+ requestHasSensitiveInfo = false,
+ responseHasSensitiveInfo = false,
+ authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}
+)
+public class ListCoffeeCmd extends BaseListCmd {
+ public static final String APINAME = "listCoffees";
+
+ @Inject
+ private CoffeeManager coffeeManager;
+
+ /////////////////////////////////////////////////////
+ //////////////// API parameters /////////////////////
+ /////////////////////////////////////////////////////
+
+ @Parameter(name = ApiConstants.ID,
+ type = CommandType.LONG,
+ required = false,
+ description = "the ID of the coffee order")
+ private Long id;
+
+ @Parameter(name = "offering",
+ type = CommandType.STRING,
+ required = false,
+ description = "type of coffee (ESPRESSO, CAPPUCCINO, MOCHA, LATTE)")
+ private String offering;
+
+ @Parameter(name = "size",
+ type = CommandType.STRING,
+ required = false,
+ description = "size of coffee (SMALL, MEDIUM, LARGE)")
+ private String size;
+
+ /////////////////////////////////////////////////////
+ /////////////////// Accessors ///////////////////////
+ /////////////////////////////////////////////////////
+
+ public Long getId() {
+ return id;
+ }
+
+ public String getOffering() {
+ return offering;
+ }
+
+ public String getSize() {
+ return size;
+ }
+
+ /////////////////////////////////////////////////////
+ /////////////// API Implementation///////////////////
+ /////////////////////////////////////////////////////
+
+ @Override
+ public void execute() {
+ List coffees = coffeeManager.listCoffees(this);
+ List responseList = coffeeManager.createCoffeeResponses(coffees);
+
+ ListResponse listResponse = new ListResponse<>();
+ listResponse.setResponses(responseList, responseList.size());
+ listResponse.setResponseName(getCommandName());
+ setResponseObject(listResponse);
+ }
+
+ @Override
+ public long getEntityOwnerId() {
+ return CallContext.current().getCallingAccountId();
+ }
+}
diff --git a/plugins/hackerbook/feature/src/main/java/org/apache/cloudstack/api/command/RemoveCoffeeCmd.java b/plugins/hackerbook/feature/src/main/java/org/apache/cloudstack/api/command/RemoveCoffeeCmd.java
new file mode 100644
index 000000000000..05bbeca3e3d9
--- /dev/null
+++ b/plugins/hackerbook/feature/src/main/java/org/apache/cloudstack/api/command/RemoveCoffeeCmd.java
@@ -0,0 +1,125 @@
+/*
+ * 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.cloudstack.api.command;
+
+import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.BaseAsyncCmd;
+import org.apache.cloudstack.api.Parameter;
+import org.apache.cloudstack.api.CoffeeManager;
+import org.apache.cloudstack.api.response.SuccessResponse;
+import org.apache.cloudstack.acl.RoleType;
+import org.apache.cloudstack.context.CallContext;
+
+import javax.inject.Inject;
+import java.util.List;
+
+@APICommand(
+ name = RemoveCoffeeCmd.APINAME,
+ description = "Removes a coffee order or multiple coffee orders",
+ responseObject = SuccessResponse.class,
+ since = "4.23.0.0",
+ requestHasSensitiveInfo = false,
+ responseHasSensitiveInfo = false,
+ authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}
+)
+public class RemoveCoffeeCmd extends BaseAsyncCmd {
+ public static final String APINAME = "removeCoffee";
+
+ @Inject
+ private CoffeeManager coffeeManager;
+
+ /////////////////////////////////////////////////////
+ //////////////// API parameters /////////////////////
+ /////////////////////////////////////////////////////
+
+ @Parameter(name = ApiConstants.ID,
+ type = CommandType.LONG,
+ required = false,
+ description = "the ID of the coffee order to remove")
+ private Long id;
+
+ @Parameter(name = "ids",
+ type = CommandType.LIST,
+ collectionType = CommandType.LONG,
+ required = false,
+ description = "the IDs of coffee orders to remove")
+ private List ids;
+
+ /////////////////////////////////////////////////////
+ /////////////////// Accessors ///////////////////////
+ /////////////////////////////////////////////////////
+
+ public Long getId() {
+ return id;
+ }
+
+ public List getIds() {
+ return ids;
+ }
+
+ /////////////////////////////////////////////////////
+ /////////////// API Implementation///////////////////
+ /////////////////////////////////////////////////////
+
+ @Override
+ public void execute() {
+ boolean result = coffeeManager.removeCoffee(this);
+
+ SuccessResponse response = new SuccessResponse();
+
+ if (result) {
+ response.setSuccess(true);
+ if (id != null) {
+ response.setDisplayText("Successfully removed coffee order: " + id);
+ } else if (ids != null && !ids.isEmpty()) {
+ response.setDisplayText("Successfully removed " + ids.size() + " coffee orders");
+ } else {
+ response.setDisplayText("Coffee removal completed");
+ }
+ } else {
+ response.setSuccess(false);
+ response.setDisplayText("Failed to remove coffee order");
+ }
+
+ response.setResponseName(getCommandName());
+ setResponseObject(response);
+ }
+
+ @Override
+ public String getEventType() {
+ return "COFFEE.REMOVE";
+ }
+
+ @Override
+ public String getEventDescription() {
+ if (id != null) {
+ return "Removing coffee: " + id;
+ } else if (ids != null) {
+ return "Removing " + ids.size() + " coffees";
+ }
+ return "Removing coffee";
+ }
+
+ @Override
+ public long getEntityOwnerId() {
+ return CallContext.current().getCallingAccountId();
+ }
+}
diff --git a/plugins/hackerbook/feature/src/main/java/org/apache/cloudstack/api/command/UpdateCoffeeCmd.java b/plugins/hackerbook/feature/src/main/java/org/apache/cloudstack/api/command/UpdateCoffeeCmd.java
new file mode 100644
index 000000000000..1e450927b4d7
--- /dev/null
+++ b/plugins/hackerbook/feature/src/main/java/org/apache/cloudstack/api/command/UpdateCoffeeCmd.java
@@ -0,0 +1,103 @@
+/*
+ * 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.cloudstack.api.command;
+
+import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.BaseAsyncCmd;
+import org.apache.cloudstack.api.Parameter;
+import org.apache.cloudstack.api.Coffee;
+import org.apache.cloudstack.api.CoffeeManager;
+import org.apache.cloudstack.api.response.CoffeeResponse;
+import org.apache.cloudstack.acl.RoleType;
+import org.apache.cloudstack.context.CallContext;
+
+import javax.inject.Inject;
+
+@APICommand(
+ name = UpdateCoffeeCmd.APINAME,
+ description = "Updates an existing coffee order",
+ responseObject = CoffeeResponse.class,
+ since = "4.23.0.0",
+ requestHasSensitiveInfo = false,
+ responseHasSensitiveInfo = false,
+ authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}
+)
+public class UpdateCoffeeCmd extends BaseAsyncCmd {
+ public static final String APINAME = "updateCoffee";
+
+ @Inject
+ private CoffeeManager coffeeManager;
+
+ /////////////////////////////////////////////////////
+ //////////////// API parameters /////////////////////
+ /////////////////////////////////////////////////////
+
+ @Parameter(name = ApiConstants.ID,
+ type = CommandType.LONG,
+ required = true,
+ description = "the ID of the coffee order")
+ private Long id;
+
+ @Parameter(name = "size",
+ type = CommandType.STRING,
+ required = false,
+ description = "new size of coffee (SMALL, MEDIUM, LARGE)")
+ private String size;
+
+ /////////////////////////////////////////////////////
+ /////////////////// Accessors ///////////////////////
+ /////////////////////////////////////////////////////
+
+ public Long getId() {
+ return id;
+ }
+
+ public String getSize() {
+ return size;
+ }
+
+ /////////////////////////////////////////////////////
+ /////////////// API Implementation///////////////////
+ /////////////////////////////////////////////////////
+
+ @Override
+ public void execute() {
+ Coffee coffee = coffeeManager.updateCoffee(this);
+ CoffeeResponse response = coffeeManager.createCoffeeResponse(coffee);
+ response.setResponseName(getCommandName());
+ setResponseObject(response);
+ }
+
+ @Override
+ public String getEventType() {
+ return "COFFEE.UPDATE";
+ }
+
+ @Override
+ public String getEventDescription() {
+ return "Updating coffee: " + id;
+ }
+
+ @Override
+ public long getEntityOwnerId() {
+ return CallContext.current().getCallingAccountId();
+ }
+}
diff --git a/plugins/hackerbook/feature/src/main/java/org/apache/cloudstack/api/response/CoffeeResponse.java b/plugins/hackerbook/feature/src/main/java/org/apache/cloudstack/api/response/CoffeeResponse.java
new file mode 100644
index 000000000000..79ad10d9148c
--- /dev/null
+++ b/plugins/hackerbook/feature/src/main/java/org/apache/cloudstack/api/response/CoffeeResponse.java
@@ -0,0 +1,56 @@
+// 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.cloudstack.api.response;
+
+import com.google.gson.annotations.SerializedName;
+import org.apache.cloudstack.api.BaseResponse;
+import org.apache.cloudstack.api.EntityReference;
+import org.apache.cloudstack.api.Coffee;
+import org.apache.cloudstack.api.ApiConstants;
+import com.cloud.serializer.Param;
+
+@EntityReference(value = Coffee.class)
+public class CoffeeResponse extends BaseResponse {
+ public static final String OFFERING = "offering";
+ public static final String SIZE = "size";
+
+ @SerializedName(ApiConstants.ID)
+ @Param(description = "the coffee ID")
+ private String id;
+
+ @SerializedName(ApiConstants.NAME)
+ @Param(description = "the coffee name")
+ private String name;
+
+ @SerializedName(OFFERING)
+ @Param(description = "the type of coffee")
+ private String offering;
+
+ @SerializedName(SIZE)
+ @Param(description = "the size of coffee")
+ private String size;
+
+ @SerializedName(ApiConstants.STATE)
+ @Param(description = "current coffee state")
+ private String state;
+
+ public void setId(String id) { this.id = id; }
+ public void setName(String name) { this.name = name; }
+ public void setOffering(String offering) { this.offering = offering; }
+ public void setSize(String size) { this.size = size; }
+ public void setState(String state) { this.state = state; }
+}
diff --git a/plugins/hackerbook/feature/src/main/java/org/apache/cloudstack/feature/CoffeeManagerImpl.java b/plugins/hackerbook/feature/src/main/java/org/apache/cloudstack/feature/CoffeeManagerImpl.java
new file mode 100644
index 000000000000..8835678146d0
--- /dev/null
+++ b/plugins/hackerbook/feature/src/main/java/org/apache/cloudstack/feature/CoffeeManagerImpl.java
@@ -0,0 +1,264 @@
+/*
+ * 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.cloudstack.feature;
+
+import com.cloud.utils.component.ManagerBase;
+import com.cloud.utils.component.PluggableService;
+import com.cloud.utils.exception.CloudRuntimeException;
+import org.apache.cloudstack.managed.context.ManagedContextRunnable;
+import org.apache.cloudstack.poll.BackgroundPollManager;
+import org.apache.cloudstack.poll.BackgroundPollTask;
+import org.apache.cloudstack.api.Coffee;
+import org.apache.cloudstack.api.CoffeeManager;
+import org.apache.cloudstack.api.command.CreateCoffeeCmd;
+import org.apache.cloudstack.api.command.ListCoffeeCmd;
+import org.apache.cloudstack.api.command.RemoveCoffeeCmd;
+import org.apache.cloudstack.api.command.UpdateCoffeeCmd;
+import org.apache.cloudstack.api.response.CoffeeResponse;
+import org.apache.cloudstack.feature.dao.CoffeeDao;
+import org.apache.cloudstack.framework.config.Configurable;
+import org.apache.cloudstack.framework.config.ConfigKey;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.LogManager;
+
+import javax.inject.Inject;
+import javax.naming.ConfigurationException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+public class CoffeeManagerImpl extends ManagerBase implements CoffeeManager, Configurable, PluggableService {
+ protected Logger logger = LogManager.getLogger(getClass());
+
+ @Inject
+ private CoffeeDao _coffeeDao;
+
+ @Inject
+ private BackgroundPollManager _backgroundPollManager;
+
+ private static final ConfigKey CoffeeTTLInterval = new ConfigKey(
+ "Advanced",
+ Long.class,
+ "coffee.ttl.interval",
+ "600",
+ "The max time in seconds after which coffee becomes stale.",
+ true,
+ ConfigKey.Scope.Zone
+ );
+
+ private static final ConfigKey CoffeeGCInterval = new ConfigKey(
+ "Advanced",
+ Long.class,
+ "coffee.gc.interval",
+ "300",
+ "The interval in seconds at which the coffee garbage collection task runs.",
+ true,
+ ConfigKey.Scope.Zone
+ );
+
+ private static final class CoffeeGCTask extends ManagedContextRunnable implements BackgroundPollTask {
+ private final CoffeeManager coffeeManager;
+
+ private CoffeeGCTask(CoffeeManager coffeeManager) {
+ this.coffeeManager = coffeeManager;
+ }
+
+ @Override
+ protected void runInContext() {
+ try {
+ if (logger.isTraceEnabled()) {
+ logger.trace("Coffee GC task is running...");
+ }
+
+ final Long ttl = CoffeeTTLInterval.value();
+
+ logger.info("Coffee GC task executed. TTL: " + ttl + " seconds");
+
+ } catch (final Throwable t) {
+ logger.error("Error trying to run Coffee GC task", t);
+ }
+ }
+
+ @Override
+ public Long getDelay() {
+ return CoffeeGCInterval.value() * 1000L;
+ }
+ }
+
+ @Override
+ public boolean configure(String name, Map params) throws ConfigurationException {
+ super.configure(name, params);
+ logger.info("CoffeeManager is being configured");
+ _backgroundPollManager.submitTask(new CoffeeGCTask(this));
+ logger.info("Coffee GC background task has been scheduled");
+ return true;
+ }
+
+ @Override
+ public boolean start() {
+ logger.info("CoffeeManager is starting");
+ return true;
+ }
+
+ @Override
+ public boolean stop() {
+ logger.info("CoffeeManager is stopping");
+ return true;
+ }
+
+ @Override
+ public List> getCommands() {
+ final List> cmdList = new ArrayList<>();
+ cmdList.add(CreateCoffeeCmd.class);
+ cmdList.add(ListCoffeeCmd.class);
+ cmdList.add(UpdateCoffeeCmd.class);
+ cmdList.add(RemoveCoffeeCmd.class);
+ return cmdList;
+ }
+
+ @Override
+ public String getConfigComponentName() {
+ return CoffeeManager.class.getSimpleName();
+ }
+
+ @Override
+ public ConfigKey>[] getConfigKeys() {
+ return new ConfigKey[]{
+ CoffeeTTLInterval,
+ CoffeeGCInterval
+ };
+ }
+
+ @Override
+ public CoffeeResponse createCoffeeResponse(Coffee coffee) {
+ CoffeeResponse response = new CoffeeResponse();
+ response.setId(coffee.getUuid());
+ response.setName(coffee.getName());
+ response.setOffering(coffee.getOffering().name());
+ response.setSize(coffee.getSize().name());
+ response.setState(coffee.getState().name());
+ response.setObjectName("coffee");
+ return response;
+ }
+
+ @Override
+ public List createCoffeeResponses(List coffees) {
+ List responses = new ArrayList<>();
+ for (Coffee coffee : coffees) {
+ responses.add(createCoffeeResponse(coffee));
+ }
+ return responses;
+ }
+
+ @Override
+ public Coffee createCoffee(CreateCoffeeCmd cmd) {
+ logger.info("Creating coffee: " + cmd.getName());
+
+ Coffee.Offering offering = Coffee.Offering.valueOf(cmd.getOffering());
+ Coffee.Size size = Coffee.Size.valueOf(cmd.getSize());
+
+ CoffeeVO coffee = new CoffeeVO(cmd.getName(), offering, size, 1L);
+ coffee = _coffeeDao.persist(coffee);
+
+ logger.debug("Created coffee with ID: " + coffee.getId() + ", UUID: " + coffee.getUuid());
+ return coffee;
+ }
+
+ @Override
+ public List listCoffees(ListCoffeeCmd cmd) {
+ logger.info("Listing coffees");
+
+ List coffees = _coffeeDao.listAll();
+
+ Long id = cmd.getId();
+ String offering = cmd.getOffering();
+ String size = cmd.getSize();
+
+ List filteredCoffeeList = new ArrayList<>();
+
+ for (CoffeeVO coffee : coffees) {
+ boolean isCoffeeFound = true;
+
+ if (id != null && coffee.getId() != id) {
+ isCoffeeFound = false;
+ }
+
+ if (offering != null && !coffee.getOffering().name().equalsIgnoreCase(offering)) {
+ isCoffeeFound = false;
+ }
+
+ if (size != null && !coffee.getSize().name().equalsIgnoreCase(size)) {
+ isCoffeeFound = false;
+ }
+
+ if (isCoffeeFound) {
+ filteredCoffeeList.add(coffee);
+ }
+ }
+
+ logger.debug("Returning " + filteredCoffeeList.size() + " coffees");
+ return filteredCoffeeList;
+ }
+
+ @Override
+ public Coffee updateCoffee(UpdateCoffeeCmd cmd) {
+ logger.info("Updating coffee with ID: " + cmd.getId());
+
+ long id = cmd.getId();
+ CoffeeVO coffee = _coffeeDao.findById(id);
+
+ if (coffee == null) {
+ throw new CloudRuntimeException("Coffee with ID " + id + " not found");
+ }
+
+ if (cmd.getSize() != null) {
+ coffee.setSize(Coffee.Size.valueOf(cmd.getSize()));
+ }
+
+ _coffeeDao.update(id, coffee);
+
+ logger.debug("Updated coffee: " + coffee.getName());
+ return coffee;
+ }
+
+ @Override
+ public boolean removeCoffee(RemoveCoffeeCmd cmd) {
+ if (cmd.getId() != null) {
+ logger.info("Removing coffee with ID: " + cmd.getId());
+ long id = cmd.getId();
+
+ boolean result = _coffeeDao.remove(id);
+
+ if (!result) {
+ throw new CloudRuntimeException("Failed to remove coffee with ID " + id);
+ }
+
+ return true;
+ } else if (cmd.getIds() != null) {
+ logger.info("Removing " + cmd.getIds().size() + " coffees");
+ for (Long id : cmd.getIds()) {
+ _coffeeDao.remove(id);
+ }
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/plugins/hackerbook/feature/src/main/java/org/apache/cloudstack/feature/CoffeeVO.java b/plugins/hackerbook/feature/src/main/java/org/apache/cloudstack/feature/CoffeeVO.java
new file mode 100644
index 000000000000..850345bbbe3b
--- /dev/null
+++ b/plugins/hackerbook/feature/src/main/java/org/apache/cloudstack/feature/CoffeeVO.java
@@ -0,0 +1,173 @@
+// 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.cloudstack.feature;
+
+import org.apache.cloudstack.api.Coffee;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import java.util.Date;
+import java.util.UUID;
+
+@Entity
+@Table(name = "coffee")
+public class CoffeeVO implements Coffee {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "id")
+ private long id;
+
+ @Column(name = "uuid")
+ private String uuid;
+
+ @Column(name = "name")
+ private String name;
+
+ @Column(name = "offering")
+ @Enumerated(value = EnumType.STRING)
+ private Offering offering;
+
+ @Column(name = "size")
+ @Enumerated(value = EnumType.STRING)
+ private Size size;
+
+ @Column(name = "state")
+ @Enumerated(value = EnumType.STRING)
+ private State state = State.CREATED;
+
+ @Column(name = "account_id")
+ private long accountId;
+
+ @Column(name = "created")
+ private Date created;
+
+ @Column(name = "removed")
+ private Date removed;
+
+ public CoffeeVO() {
+ this.uuid = UUID.randomUUID().toString();
+ }
+
+ public CoffeeVO(String name, Offering offering, Size size, long accountId) {
+ this();
+ this.name = name;
+ this.offering = offering;
+ this.size = size;
+ this.accountId = accountId;
+ this.state = State.CREATED;
+ this.created = new Date();
+ }
+
+ @Override
+ public long getId() {
+ return id;
+ }
+
+ @Override
+ public String getUuid() {
+ return uuid;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public Offering getOffering() {
+ return offering;
+ }
+
+ @Override
+ public Size getSize() {
+ return size;
+ }
+
+ @Override
+ public State getState() {
+ return state;
+ }
+
+ public long getAccountId() {
+ return accountId;
+ }
+
+ public Date getCreated() {
+ return created;
+ }
+
+ public Date getRemoved() {
+ return removed;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ public void setUuid(String uuid) {
+ this.uuid = uuid;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public void setOffering(Offering offering) {
+ this.offering = offering;
+ }
+
+ public void setSize(Size size) {
+ this.size = size;
+ }
+
+ public void setState(State state) {
+ this.state = state;
+ }
+
+ public void setAccountId(long accountId) {
+ this.accountId = accountId;
+ }
+
+ public void setCreated(Date created) {
+ this.created = created;
+ }
+
+ public void setRemoved(Date removed) {
+ this.removed = removed;
+ }
+
+ @Override
+ public String toString() {
+ return "CoffeeVO{" +
+ "id=" + id +
+ ", uuid='" + uuid + '\'' +
+ ", name='" + name + '\'' +
+ ", offering=" + offering +
+ ", size=" + size +
+ ", state=" + state +
+ ", accountId=" + accountId +
+ '}';
+ }
+}
diff --git a/plugins/hackerbook/feature/src/main/java/org/apache/cloudstack/feature/dao/CoffeeDao.java b/plugins/hackerbook/feature/src/main/java/org/apache/cloudstack/feature/dao/CoffeeDao.java
new file mode 100644
index 000000000000..567392c6dcec
--- /dev/null
+++ b/plugins/hackerbook/feature/src/main/java/org/apache/cloudstack/feature/dao/CoffeeDao.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.cloudstack.feature.dao;
+
+import com.cloud.utils.db.GenericDao;
+import org.apache.cloudstack.feature.CoffeeVO;
+
+public interface CoffeeDao extends GenericDao {
+}
diff --git a/plugins/hackerbook/feature/src/main/java/org/apache/cloudstack/feature/dao/CoffeeDaoImpl.java b/plugins/hackerbook/feature/src/main/java/org/apache/cloudstack/feature/dao/CoffeeDaoImpl.java
new file mode 100644
index 000000000000..c584b0221a61
--- /dev/null
+++ b/plugins/hackerbook/feature/src/main/java/org/apache/cloudstack/feature/dao/CoffeeDaoImpl.java
@@ -0,0 +1,30 @@
+// 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.cloudstack.feature.dao;
+
+import com.cloud.utils.db.GenericDaoBase;
+import org.apache.cloudstack.feature.CoffeeVO;
+import org.springframework.stereotype.Component;
+
+@Component
+public class CoffeeDaoImpl extends GenericDaoBase implements CoffeeDao {
+
+ public CoffeeDaoImpl() {
+ super();
+ }
+}
diff --git a/plugins/hackerbook/feature/src/main/resources/META-INF/cloudstack/feature/module.properties b/plugins/hackerbook/feature/src/main/resources/META-INF/cloudstack/feature/module.properties
new file mode 100644
index 000000000000..69a1fd028abd
--- /dev/null
+++ b/plugins/hackerbook/feature/src/main/resources/META-INF/cloudstack/feature/module.properties
@@ -0,0 +1,18 @@
+# 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.
+name=feature
+parent=backend
diff --git a/plugins/hackerbook/feature/src/main/resources/META-INF/cloudstack/feature/spring-feature-context.xml b/plugins/hackerbook/feature/src/main/resources/META-INF/cloudstack/feature/spring-feature-context.xml
new file mode 100644
index 000000000000..75db0aa7c013
--- /dev/null
+++ b/plugins/hackerbook/feature/src/main/resources/META-INF/cloudstack/feature/spring-feature-context.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
diff --git a/plugins/pom.xml b/plugins/pom.xml
index e7d13871285e..c95e4651f269 100755
--- a/plugins/pom.xml
+++ b/plugins/pom.xml
@@ -140,6 +140,7 @@
storage/object/ceph
storage/object/cloudian
storage/object/simulator
+ hackerbook/feature
storage-allocators/random
diff --git a/tools/apidoc/gen_toc.py b/tools/apidoc/gen_toc.py
index e41a04ff2e1b..34970180e542 100644
--- a/tools/apidoc/gen_toc.py
+++ b/tools/apidoc/gen_toc.py
@@ -273,7 +273,8 @@
'Extensions' : 'Extension',
'CustomAction' : 'Extension',
'CustomActions' : 'Extension',
- 'ImportVmTask': 'Import VM Task'
+ 'ImportVmTask': 'Import VM Task',
+ 'Coffee': 'Coffee'
}