Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

improvement: add mysql support for the inventory service #89

Merged
merged 98 commits into from
Mar 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
98 commits
Select commit Hold shift + click to select a range
d6ddccb
fix: remove incorrect delimiter in skaffold
Shabirmean Feb 24, 2022
df3c401
ci: add skaffold build step
Shabirmean Feb 24, 2022
5114d05
Merge branch 'main' into add-build-steps
Shabirmean Feb 24, 2022
f4d0d89
fix: namespace error in cloudbuild
Shabirmean Feb 24, 2022
e2b143b
Merge branch 'add-build-steps' of github.com:GoogleCloudPlatform/poin…
Shabirmean Feb 24, 2022
551b835
fix: the cloudbuild config for gcloud
Shabirmean Feb 24, 2022
353a163
fix: the pr number prefix
Shabirmean Feb 24, 2022
739862b
cleanup: use kubectl image
Shabirmean Feb 24, 2022
b32fc31
cleanup: format cloudbuild file
Shabirmean Feb 24, 2022
9543e0f
fix: pr prefix
Shabirmean Feb 24, 2022
729038c
fix: change underscore to hyphen
Shabirmean Feb 24, 2022
efbd309
cleanup: update kubectl image in skaffold builder
Shabirmean Feb 24, 2022
d9ebe30
fix: kubectl command
Shabirmean Feb 24, 2022
7541593
cleanup: add correct node version tag
Shabirmean Feb 24, 2022
71e5057
cleanup: update namepsace yaml creation
Shabirmean Feb 24, 2022
9653817
cleanup: update the skaffold image usage
Shabirmean Feb 24, 2022
ad43a34
cleanup: split the gcloud command
Shabirmean Feb 24, 2022
f21636a
cleanup: fix extra quotes
Shabirmean Feb 24, 2022
d8d7d62
fix: split the cloudbuild step commands
Shabirmean Feb 24, 2022
c7e4505
cleanup: move gcloud and skaffold into a file
Shabirmean Feb 24, 2022
40011a7
cleanup: split the exec command
Shabirmean Feb 24, 2022
bb48101
fix: add skaffold build and deploy steps
Shabirmean Feb 24, 2022
05f61d8
cleanup: add timeout to build
Shabirmean Feb 24, 2022
c9d06f8
cleanup: remove unused image
Shabirmean Feb 24, 2022
f3a30a1
Merge branch 'main' into add-build-steps
Shabirmean Feb 24, 2022
201cc8a
cleanup: remove unused file
Shabirmean Feb 25, 2022
7958f4b
Merge branch 'add-build-steps' of github.com:GoogleCloudPlatform/poin…
Shabirmean Feb 25, 2022
2bc89f2
process: add options related t custom sa
Shabirmean Feb 25, 2022
907777b
cleanup: write logs to bucket
Shabirmean Feb 25, 2022
1a6a988
test: trigger ci
Shabirmean Feb 25, 2022
ac24940
test: trigger ci
Shabirmean Feb 25, 2022
5fecb2c
cleanup: remove unnecessary skaffold steps
Shabirmean Feb 25, 2022
79ab8f9
cleanup:migrate to new project
Shabirmean Feb 25, 2022
66128b9
cleanup:migrate to new project
Shabirmean Feb 25, 2022
b44af50
test: check builds
Shabirmean Feb 25, 2022
567323a
test: check builds
Shabirmean Feb 25, 2022
902bf37
test: check builds
Shabirmean Feb 25, 2022
ccc7ae5
ci: add cleanup step
Shabirmean Feb 25, 2022
4a5d442
Merge branch 'main' into add-pr-merge-hook
Shabirmean Feb 25, 2022
29304b8
ci: fix merge commits
Shabirmean Feb 25, 2022
807c20d
Merge branch 'main' into add-pr-merge-hook
Shabirmean Feb 25, 2022
b6b6378
ci: add steps to deploy main to staging
Shabirmean Feb 25, 2022
ab18002
test: check builds
Shabirmean Feb 25, 2022
0f7d122
ci: split the deploy steps
Shabirmean Feb 25, 2022
9c922e4
test: check builds
Shabirmean Mar 2, 2022
8fce8ae
test: check builds
Shabirmean Mar 2, 2022
8899f80
test: check builds
Shabirmean Mar 2, 2022
4124c09
ci: remove the maven clean
Shabirmean Mar 2, 2022
f80d3e5
ci: fix repo name
Shabirmean Mar 2, 2022
b8b3cdf
ci: fix repo name
Shabirmean Mar 2, 2022
59fce64
ci: comment the artifact maven plugin
Shabirmean Mar 3, 2022
a11aec7
ci: add mvn clean step before skaffold
Shabirmean Mar 3, 2022
eae2c88
ci: run the maven wrapper
Shabirmean Mar 3, 2022
0d7b437
ci run the mvnw first
Shabirmean Mar 3, 2022
f219cac
ci: remove mvnw
Shabirmean Mar 3, 2022
4edaebe
ci: remove mvnw
Shabirmean Mar 3, 2022
78759ea
ci: make mvnw executable
Shabirmean Mar 3, 2022
a7e7a08
ci: add mvnw back
Shabirmean Mar 3, 2022
9e68a09
ci: add mvnw to the merge to pr step
Shabirmean Mar 3, 2022
9fb62ea
doc: minor fix (#87)
Shabirmean Mar 3, 2022
ecb6e5e
doc: test pr (#88)
Shabirmean Mar 3, 2022
068caab
test: check builds
Shabirmean Mar 3, 2022
262951d
Merge branch 'add-pr-merge-hook' of github.com:GoogleCloudPlatform/po…
Shabirmean Mar 3, 2022
dd32aad
fix: remove maven clean
Shabirmean Mar 4, 2022
d7026dd
fix: remove maven clean
Shabirmean Mar 4, 2022
a6bbf4f
ci: remove initial mvn install
Shabirmean Mar 4, 2022
58afab1
test: check builds
Shabirmean Mar 4, 2022
19b6cba
cleanup: change master to main
Shabirmean Mar 4, 2022
c37d52a
improvement: add db access layer to the inventory service
Shabirmean Mar 8, 2022
67bfa64
improvement: complete mysql to inventory service
Shabirmean Mar 8, 2022
4f87329
ci: add license header
Shabirmean Mar 8, 2022
999a83b
Merge branch 'main' into add-mysql
Shabirmean Mar 9, 2022
5af9a47
fix: skip datasource loading with payments and apiserver
Shabirmean Mar 9, 2022
bae5d2b
Merge branch 'add-mysql' of github.com:GoogleCloudPlatform/point-of-s…
Shabirmean Mar 9, 2022
2b7ccfe
fix: explude JDBC in tests
Shabirmean Mar 9, 2022
dccf626
cleanup: remove the exclusion rule in test
Shabirmean Mar 9, 2022
a1dddc0
fix: NPE
Shabirmean Mar 9, 2022
6583ebc
test: check builds
Shabirmean Mar 9, 2022
12a14ec
test: exclude hibernate
Shabirmean Mar 10, 2022
e89f54c
add: spring profiles and h2 db
Shabirmean Mar 10, 2022
845a406
improvement: add mysql manifest
Shabirmean Mar 10, 2022
f587595
improvement: split skaffold with profiles
Shabirmean Mar 10, 2022
3f882f1
doc: add readme
Shabirmean Mar 10, 2022
1ddf03f
fix: update the skaffold commands
Shabirmean Mar 10, 2022
35c32a3
lint: remove unnecessary extra line
Shabirmean Mar 10, 2022
18eb65a
cleanup: add region tags
Shabirmean Mar 14, 2022
4bed40b
Merge branch 'add-dev-manifests' into add-mysql
Shabirmean Mar 14, 2022
1600fc8
Merge branch 'main' into add-mysql
Shabirmean Mar 15, 2022
399d764
cleanup: add profiles for db
Shabirmean Mar 15, 2022
02716ca
config: fix skaffold ot point to correct repo for mysql
Shabirmean Mar 15, 2022
4d7f24d
config: move build to the dev profile
Shabirmean Mar 15, 2022
7eaa393
fix: skaffold error with 3p images
Shabirmean Mar 15, 2022
476a718
ci: add inmemory profile to the pr cloudbuild yaml
Shabirmean Mar 15, 2022
da6e5e8
ci: add deployment type suffix to tag
Shabirmean Mar 15, 2022
f50ad6d
cleanup: fix region tags
Shabirmean Mar 15, 2022
5b38076
ci: update cleanup and staging ci yamls
Shabirmean Mar 16, 2022
7a40dc0
ci: increase timeout
Shabirmean Mar 16, 2022
b7111a3
doc: add details for inmemory profile
Shabirmean Mar 17, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
14 changes: 11 additions & 3 deletions .github/cloudbuild/pos-pr-open-to-main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,17 @@ steps:
- -c
- |
cat <<EOF > /workspace/namespace.yaml -
# namespace to deploy the mysql db based deployment
apiVersion: v1
kind: Namespace
metadata:
name: pr-$_PR_NUMBER
name: pr-$_PR_NUMBER-db
---
# namespace to deploy the inmemory h2 db based deployment
apiVersion: v1
kind: Namespace
metadata:
name: pr-$_PR_NUMBER-inmemory
EOF
# indicates that the step need not wait for any other step
waitFor: [ '-' ]
Expand Down Expand Up @@ -79,7 +86,8 @@ steps:
gcloud container clusters get-credentials --zone $_DEV_CLUSTER_ZONE $_DEV_CLUSTER

./mvnw install
skaffold run -p dev -f=skaffold.yaml --default-repo=us-docker.pkg.dev/$PROJECT_ID/$_POS_IMAGE_REPO --namespace=pr-$_PR_NUMBER --tag=pr-$_PR_NUMBER
skaffold run -p dev -f=skaffold.yaml --default-repo=us-docker.pkg.dev/$PROJECT_ID/$_POS_IMAGE_REPO --namespace=pr-$_PR_NUMBER-db --tag=pr-$_PR_NUMBER-db
skaffold run -p dev,inmemory -f=skaffold.yaml --default-repo=us-docker.pkg.dev/$PROJECT_ID/$_POS_IMAGE_REPO --namespace=pr-$_PR_NUMBER-inmemory --tag=pr-$_PR_NUMBER-inmemory

EOF
# indicates that the step need not wait for any other step
Expand All @@ -97,7 +105,7 @@ steps:
]
# waitFor: commented out, so this step waits for all previous steps

timeout: 900s
timeout: 1800s
logsBucket: 'gs://pos-cloudbuild-logs'
options:
logging: GCS_ONLY
3 changes: 2 additions & 1 deletion .github/cloudbuild/pos-pr-webhook-event.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ steps:
echo "Event [$_PULL_REQUEST_EVENT_TYPE] for pr-$_PULL_REQUEST_ID"
if [ $_PULL_REQUEST_EVENT_TYPE == 'closed' ]; then
gcloud container clusters get-credentials --zone $_DEV_CLUSTER_ZONE $_DEV_CLUSTER
kubectl delete namespace pr-$_PULL_REQUEST_ID || true
kubectl delete namespace pr-$_PULL_REQUEST_ID-db
kubectl delete namespace pr-$_PULL_REQUEST_ID-db || true
else
echo "Nothing to do since it's not a 'closed' event"
fi
Expand Down
14 changes: 11 additions & 3 deletions .github/cloudbuild/pos-push-to-main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,17 @@ steps:
- -c
- |
cat <<EOF > /workspace/namespace.yaml -
# namespace to deploy the mysql db based deployment
apiVersion: v1
kind: Namespace
metadata:
name: $_STAGING_NS
name: $_STAGING_NS-db
---
# namespace to deploy the inmemory h2 db based deployment
apiVersion: v1
kind: Namespace
metadata:
name: $_STAGING_NS-inmemory
EOF

cat <<EOF > /workspace/deploy.sh -
Expand All @@ -41,7 +48,8 @@ steps:
kubectl apply -f /workspace/namespace.yaml

./mvnw install
skaffold run -p dev -f=skaffold.yaml --default-repo=us-docker.pkg.dev/$PROJECT_ID/$_POS_IMAGE_REPO --namespace=$_STAGING_NS --tag=$COMMIT_SHA
skaffold run -p dev -f=skaffold.yaml --default-repo=us-docker.pkg.dev/$PROJECT_ID/$_POS_IMAGE_REPO --namespace=$_STAGING_NS-db --tag=$COMMIT_SHA-db
skaffold run -p dev,inmemory -f=skaffold.yaml --default-repo=us-docker.pkg.dev/$PROJECT_ID/$_POS_IMAGE_REPO --namespace=$_STAGING_NS-db --tag=$COMMIT_SHA-inmemory
EOF
# indicates that the step need not wait for any other step
waitFor: [ '-' ]
Expand All @@ -58,7 +66,7 @@ steps:
]
# waitFor: commented out, so this step waits for all previous steps

timeout: 900s
timeout: 1800s
logsBucket: 'gs://pos-cloudbuild-logs'
options:
logging: GCS_ONLY
2 changes: 2 additions & 0 deletions point-of-sale-app/api-server/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
<description>The api-server proxy for the Edge Use case workload</description>
<properties>
<java.version>11</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
Expand Down Expand Up @@ -69,6 +70,7 @@
<!--The Resources Plugin handles the copying of project resources to the output directory.-->
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>copy Vue.js UI content</id>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
12 changes: 12 additions & 0 deletions point-of-sale-app/inventory/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,18 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,20 @@
package com.google.abmedge.inventory;

import javax.annotation.PreDestroy;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.hibernate5.LocalSessionFactoryBean;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;

/**
* The main entry point into the inventory server of the point-of-sale application stack. This class
Expand All @@ -30,6 +40,9 @@
* the items. The APIs of the inventory service are not exposed publicly.
*/
@SpringBootApplication
@EntityScan(basePackages={"com.google.abmedge"})
@ComponentScan(basePackages={"com.google.abmedge"})
@EnableJpaRepositories(basePackages={"com.google.abmedge"})
public class InventoryApplication {
private static final Logger LOGGER = LogManager.getLogger(InventoryApplication.class);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@

import com.google.abmedge.dto.Item;
import com.google.abmedge.dto.PurchaseItem;
import com.google.abmedge.inventory.dao.InMemoryStoreConnector;
import com.google.abmedge.inventory.dao.DatabaseConnector;
import com.google.abmedge.inventory.dao.InventoryStoreConnector;
import com.google.abmedge.inventory.dto.Inventory;
import com.google.abmedge.inventory.util.InventoryStoreConnectorException;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
Expand Down Expand Up @@ -57,21 +57,20 @@
public class InventoryController {

private static final Logger LOGGER = LogManager.getLogger(InventoryController.class);
private static final String CONNECTOR_TYPE_ENV_VAR = "CONNECTOR";
private static final String ACTIVE_TYPE_ENV_VAR = "ACTIVE_ITEM_TYPE";
private static final String INVENTORY_ITEMS_ENV_VAR = "ITEMS";
private static final String IN_MEMORY_CONNECTOR = "IN_MEMORY";
private static final String ALL_ITEMS = "ALL";
private static final Gson GSON = new Gson();
private static final Map<String, InventoryStoreConnector> inventoryMap =
new HashMap<>() {
{
put(IN_MEMORY_CONNECTOR, new InMemoryStoreConnector());
}
};

// the context of the inventory service (e.g. textile, food, electronics, etc)
private String activeItemsType;
private InventoryStoreConnector activeConnector;
private final DatabaseConnector databaseConnector;

// DatabaseConnector is autowired via Spring
public InventoryController(DatabaseConnector databaseConnector) {
this.databaseConnector = databaseConnector;
}

/**
* This method runs soon after the object for this class is created on startup of the
Expand Down Expand Up @@ -125,7 +124,8 @@ public ResponseEntity<String> items() {
@GetMapping(value = "/types", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<String> types() {
Set<String> itemTypes = activeConnector.getTypes();
String jsonString = GSON.toJson(itemTypes, new TypeToken<Set<String>>() {}.getType());
String jsonString = GSON.toJson(itemTypes, new TypeToken<Set<String>>() {
}.getType());
return new ResponseEntity<>(jsonString, HttpStatus.OK);
}

Expand All @@ -139,7 +139,8 @@ public ResponseEntity<String> items(@RequestBody List<String> idList) {
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
String jsonString = GSON.toJson(inventoryItems, new TypeToken<List<Item>>() {}.getType());
String jsonString = GSON.toJson(inventoryItems, new TypeToken<List<Item>>() {
}.getType());
return new ResponseEntity<>(jsonString, HttpStatus.OK);
}

Expand Down Expand Up @@ -171,7 +172,7 @@ public ResponseEntity<Void> switchType(@PathVariable String type) {
* specific to the implementation of {@link InventoryStoreConnector} that is used.
*
* @param purchaseList a list of {@link PurchaseItem} objects that needs to be updated in the
* underlying datastore
* underlying datastore
* @return an object of {@link ResponseEntity} that only has an HTTP code without any payload
*/
@PutMapping(value = "/update", consumes = MediaType.APPLICATION_JSON_VALUE)
Expand Down Expand Up @@ -220,32 +221,24 @@ private boolean updateItem(Item item, PurchaseItem purchaseItem)
/**
* This method initializes the connector that will be used to connect to the storage system that
* holds all the items' information. The connector should implement the interface {@link
* InventoryStoreConnector}. A running instance of the application shall can have multiple
* InventoryStoreConnector}. A running instance of the application shall have multiple
* implementations of {@link InventoryStoreConnector}. However, only one of them will be active at
* any single given time. Once, set to change the connector type a restart of the application is
* required.
*
* <p>On startup, the {@link #activeConnector} is identified by looking at the environment
* variable 'CONNECTOR'. If either this environment variable is unset/empty or an invalid value is
* set, then the {@link InMemoryStoreConnector} is set as the default {@link #activeConnector}.
* <p>Previously, we would look up an environment variable to decide the {@link #activeConnector}
* to be between an InMemoryConnector and a DatabaseConnector. However, with the latest changes we
* only have one connector {@link DatabaseConnector}.
*
* <p>The {@link InMemoryStoreConnector} is an implementation of the {@link
* InventoryStoreConnector} that maintains all information about the inventory in an in-memory
* data-structure. A connector type value that is set against the environment variable 'CONNECTOR'
* is deemed invalid if there exists no key entry matchign that type in the {@link
* #inventoryMap}.
* <p>The datasource this connector connects to is either an external MySQL DB or an Embedded H2
* DB. The choice for which one to connect to decided based on the 'SPRING_PROFILES_ACTIVE'
* environment variable. Depending on the value of this environment variable (empty or inmemory or
* database) a Spring profile is automatically configured and thus the corresponding
* `application-{profile}.properties` file is loaded. See the properties files under resources/
* for the configs for the two DB options: MySQL and Embedded.
*/
private void initConnectorType() {
String connectorType = System.getenv(CONNECTOR_TYPE_ENV_VAR);
if (StringUtils.isBlank(connectorType) || !inventoryMap.containsKey(connectorType)) {
LOGGER.warn(
String.format(
"'%s' environment variable is not set; " + "thus defaulting to: %s",
CONNECTOR_TYPE_ENV_VAR, IN_MEMORY_CONNECTOR));
connectorType = IN_MEMORY_CONNECTOR;
}
activeConnector = inventoryMap.get(connectorType);
LOGGER.info(String.format("Active connector type is: %s", connectorType));
activeConnector = databaseConnector;
}

/**
Expand Down Expand Up @@ -300,28 +293,39 @@ private void initItemsType() {
private void initInventoryItems() {
String inventoryList = System.getenv(INVENTORY_ITEMS_ENV_VAR);
if (StringUtils.isBlank(inventoryList)) {
LOGGER.warn(
String.format(
"No items found under inventory list env var '%s'", INVENTORY_ITEMS_ENV_VAR));
LOGGER.warn("No items found under inventory list env var '{}'", INVENTORY_ITEMS_ENV_VAR);
return;
}
inventoryList = inventoryList.replaceAll("\\\\n", "\n");
LOGGER.debug(inventoryList);
Map<String, Set<String>> itemTypeToNameMap = getItemTypeToNamesMap();
Yaml yaml = new Yaml(new Constructor(Inventory.class));
Inventory inventory = yaml.load(inventoryList);
inventory
.getItems()
.forEach(
i -> {
i.setId(UUID.randomUUID());
try {
activeConnector.insert(i);
} catch (InventoryStoreConnectorException e) {
String errMsg =
String.format(
"Failed to insert item '%s' of type '%s'", i.getName(), i.getType());
LOGGER.error(errMsg, e);
}
LOGGER.info(String.format("Inserting new item: %s", i));
});
inventory.getItems().forEach(i -> insertIfNotExists(i, itemTypeToNameMap));
}

private Map<String, Set<String>> getItemTypeToNamesMap() {
Map<String, Set<String>> itemTypeToNameMap = new HashMap<>();
List<Item> loadedItems = activeConnector.getAll();
loadedItems.forEach(i -> {
Set<String> itemNames = itemTypeToNameMap.computeIfAbsent(i.getType(), k -> new HashSet<>());
itemNames.add(i.getName());
});
return itemTypeToNameMap;
}

private void insertIfNotExists(Item i, Map<String, Set<String>> itemTypeToNameMap) {
if (itemTypeToNameMap.containsKey(i.getType()) &&
itemTypeToNameMap.get(i.getType()).contains(i.getName())) {
LOGGER.warn(
"Item ['type': {}, 'name': {}] already exists. Skipping..", i.getType(), i.getName());
return;
}
try {
activeConnector.insert(i);
} catch (InventoryStoreConnectorException e) {
LOGGER.error("Failed to insert item '{}' of type '{}'", i.getName(), i.getType(), e);
}
LOGGER.info(String.format("Inserting new item: %s", i));
}
}