From 4374852ba55a8cf96ef78bc37763d2472f194d21 Mon Sep 17 00:00:00 2001
From: Jeffrey Phillips Freeman <jeffrey.freeman@syncleus.com>
Date: Sun, 18 Mar 2018 22:47:59 -0400
Subject: [PATCH] Moved merchant data to graph DB.

---
 .../RandomRoomDescriptionCommand.java         |   2 +-
 .../commands/admin/LoadMerchantCommand.java   |  40 +++++-
 .../aethermud/configuration/ConfigureNpc.java |   9 +-
 .../syncleus/aethermud/core/GameManager.java  |   9 --
 .../aethermud/merchant/MerchantMetadata.java  | 101 ---------------
 .../aethermud/storage/AetherMudStorage.java   |  15 ++-
 .../aethermud/storage/MerchantStorage.java    |  99 ---------------
 .../graphdb/GraphDbAetherMudStorage.java      |  46 ++++++-
 .../storage/graphdb/model/MerchantData.java   | 120 ++++++++++++++++++
 .../model/MerchantItemForSaleData.java        |  51 ++++++++
 10 files changed, 267 insertions(+), 225 deletions(-)
 delete mode 100644 src/main/java/com/syncleus/aethermud/merchant/MerchantMetadata.java
 delete mode 100644 src/main/java/com/syncleus/aethermud/storage/MerchantStorage.java
 create mode 100644 src/main/java/com/syncleus/aethermud/storage/graphdb/model/MerchantData.java
 create mode 100644 src/main/java/com/syncleus/aethermud/storage/graphdb/model/MerchantItemForSaleData.java

diff --git a/src/main/java/com/syncleus/aethermud/bot/command/commands/RandomRoomDescriptionCommand.java b/src/main/java/com/syncleus/aethermud/bot/command/commands/RandomRoomDescriptionCommand.java
index 0cfa359d..8a8efbae 100644
--- a/src/main/java/com/syncleus/aethermud/bot/command/commands/RandomRoomDescriptionCommand.java
+++ b/src/main/java/com/syncleus/aethermud/bot/command/commands/RandomRoomDescriptionCommand.java
@@ -44,7 +44,7 @@ public class RandomRoomDescriptionCommand extends BotCommand {
         output.add(randomRoom.getRoomTitle());
         output.add(" ");
         output.add(randomRoom.getRoomDescription());
-        /*String mapString = botCommandManager.getGameManager().getMapsManager().drawMap(randomRoom.getRoomId(), new Coords(5, 5));
+        /*String mapString = botCommandManager.getGameManager().getMapsManager().drawMap(randomRoom.getRoomIds(), new Coords(5, 5));
         String[] split = mapString.split("\\r?\\n");
         for (String s: split) {
             output.add(s);
diff --git a/src/main/java/com/syncleus/aethermud/command/commands/admin/LoadMerchantCommand.java b/src/main/java/com/syncleus/aethermud/command/commands/admin/LoadMerchantCommand.java
index b5bd8e66..609c4b44 100644
--- a/src/main/java/com/syncleus/aethermud/command/commands/admin/LoadMerchantCommand.java
+++ b/src/main/java/com/syncleus/aethermud/command/commands/admin/LoadMerchantCommand.java
@@ -17,9 +17,14 @@ package com.syncleus.aethermud.command.commands.admin;
 
 import com.syncleus.aethermud.command.commands.Command;
 import com.syncleus.aethermud.core.GameManager;
-import com.syncleus.aethermud.merchant.MerchantMetadata;
+import com.syncleus.aethermud.merchant.Merchant;
+import com.syncleus.aethermud.merchant.MerchantItemForSale;
+import com.syncleus.aethermud.storage.AetherMudStorage;
+import com.syncleus.aethermud.storage.graphdb.GraphStorageFactory;
+import com.syncleus.aethermud.storage.graphdb.model.MerchantData;
 import com.syncleus.aethermud.player.PlayerRole;
 import com.google.common.collect.Sets;
+import com.syncleus.aethermud.storage.graphdb.model.MerchantItemForSaleData;
 import org.apache.http.HttpEntity;
 import org.apache.http.HttpResponse;
 import org.apache.http.client.HttpClient;
@@ -79,17 +84,32 @@ public class LoadMerchantCommand extends Command {
 
             String npcJson = EntityUtils.toString(entity);
 
-            MerchantMetadata merchantMetadata = null;
+            Merchant merchant = null;
             try {
-                merchantMetadata = gameManager.getGson().fromJson(npcJson, MerchantMetadata.class);
+                merchant = gameManager.getGson().fromJson(npcJson, Merchant.class);
             } catch (Exception ex) {
                 write("Retrieved JSON file is malformed. " + ex.getLocalizedMessage() + "\r\n");
                 return;
             }
             httpGet.reset();
 
-            gameManager.getMerchantStorage().saveMerchantMetadata(merchantMetadata);
-            write("Merchant Saved. - " + merchantMetadata.getInternalName() + "\r\n");
+            try( GraphStorageFactory.AetherMudTx tx = this.gameManager.getGraphStorageFactory().beginTransaction() ) {
+                AetherMudStorage storage = tx.getStorage();
+                MerchantData merchantData = storage.newMerchantData();
+                merchantData.setInternalName(merchant.internalName);
+                merchantData.setName(merchant.name);
+                merchantData.setColorName(merchant.colorName);
+                merchantData.setValidTriggers(merchant.validTriggers);
+                for(MerchantItemForSale item : merchant.merchantItemForSales) {
+                    MerchantItemForSaleData itemData = merchantData.createMerchantItemForSaleData();
+                    MerchantItemForSaleData.copyMerchantItemForSale(itemData, item);
+                }
+                merchantData.setWelcomeMessage(merchant.welcomeMessage);
+                merchantData.setMerchantType(merchant.merchantType);
+                merchantData.setRoomIds(merchant.roomIds);
+                tx.success();
+                write("Merchant Saved. - " + merchant.name + "\r\n");
+            }
 
         });
     }
@@ -109,5 +129,15 @@ public class LoadMerchantCommand extends Command {
         return true;
     }
 
+    private static class Merchant {
+        public String internalName;
+        public String name;
+        public String colorName;
+        public Set<String> validTriggers;
+        public List<MerchantItemForSale> merchantItemForSales;
+        public String welcomeMessage;
+        public com.syncleus.aethermud.merchant.Merchant.MerchantType merchantType;
+        public Set<Integer> roomIds;
+    }
 }
 
diff --git a/src/main/java/com/syncleus/aethermud/configuration/ConfigureNpc.java b/src/main/java/com/syncleus/aethermud/configuration/ConfigureNpc.java
index 19ee41dc..16e249b3 100644
--- a/src/main/java/com/syncleus/aethermud/configuration/ConfigureNpc.java
+++ b/src/main/java/com/syncleus/aethermud/configuration/ConfigureNpc.java
@@ -28,9 +28,11 @@ import com.syncleus.aethermud.spawner.NpcSpawner;
 import com.syncleus.aethermud.spawner.SpawnRule;
 import com.syncleus.aethermud.storage.graphdb.GraphStorageFactory;
 import com.syncleus.aethermud.storage.graphdb.model.ItemData;
+import com.syncleus.aethermud.storage.graphdb.model.MerchantData;
 
 import java.io.IOException;
 import java.util.List;
+import java.util.Optional;
 import java.util.Set;
 
 public class ConfigureNpc {
@@ -75,11 +77,14 @@ public class ConfigureNpc {
             }
         }
 
-        List<Merchant> allMerchantMetadatas = gameManager.getMerchantStorage().getAllMerchants();
+        List<Merchant> allMerchantMetadatas;
+        try( GraphStorageFactory.AetherMudTx tx = gameManager.getGraphStorageFactory().beginTransaction() ) {
+            allMerchantMetadatas = tx.getStorage().getAllMerchants(gameManager);
+        }
+
         for (Merchant merchant : allMerchantMetadatas) {
             Main.startUpMessage("Adding merchant: " + merchant.getInternalName());
             gameManager.getRoomManager().addMerchant(merchant);
         }
-
     }
 }
diff --git a/src/main/java/com/syncleus/aethermud/core/GameManager.java b/src/main/java/com/syncleus/aethermud/core/GameManager.java
index 32eca58e..eb3c2e66 100644
--- a/src/main/java/com/syncleus/aethermud/core/GameManager.java
+++ b/src/main/java/com/syncleus/aethermud/core/GameManager.java
@@ -104,15 +104,8 @@ public class GameManager {
     private final Room detainmentRoom;
     private final HttpClient httpclient;
     private final Gson gson;
-    private final FilebasedJsonStorage filebasedJsonStorage;
     private final GraphStorageFactory graphStorageFactory;
 
-    public MerchantStorage getMerchantStorage() {
-        return merchantStorage;
-    }
-
-    private final MerchantStorage merchantStorage;
-
 
     public GameManager(GraphStorageFactory graphStorageFactory, AetherMudConfiguration aetherMudConfiguration, RoomManager roomManager, PlayerManager playerManager, EntityManager entityManager, MapsManager mapsManager, ChannelCommunicationUtils channelUtils, HttpClient httpClient) {
         this.graphStorageFactory = graphStorageFactory;
@@ -143,8 +136,6 @@ public class GameManager {
         this.eventProcessor.startAsync();
         this.detainmentRoom = buildDetainmentRoom();
         this.gson = new GsonBuilder().setPrettyPrinting().create();
-        this.filebasedJsonStorage = new FilebasedJsonStorage(gson);
-        this.merchantStorage = new MerchantStorage(this, filebasedJsonStorage);
         this.httpclient = httpClient;
     }
 
diff --git a/src/main/java/com/syncleus/aethermud/merchant/MerchantMetadata.java b/src/main/java/com/syncleus/aethermud/merchant/MerchantMetadata.java
deleted file mode 100644
index f0c0d786..00000000
--- a/src/main/java/com/syncleus/aethermud/merchant/MerchantMetadata.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/**
- * Copyright 2017 Syncleus, Inc.
- *
- * Licensed 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 com.syncleus.aethermud.merchant;
-
-import java.util.List;
-import java.util.Set;
-
-public class MerchantMetadata {
-
-    private String internalName;
-    private Set<Integer> roomIds;
-    private String name;
-    private String colorName;
-    private Set<String> validTriggers;
-    private List<MerchantItemForSale> merchantItemForSales;
-    private String welcomeMessage;
-    private Merchant.MerchantType merchantType;
-
-    public void setMerchantType(Merchant.MerchantType merchantType) {
-        this.merchantType = merchantType;
-    }
-
-    public Set<Integer> getRoomIds() {
-
-        return roomIds;
-    }
-
-    public Merchant.MerchantType getMerchantType() {
-        return merchantType;
-    }
-
-    public String getInternalName() {
-        return internalName;
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public void setName(String name) {
-        this.name = name;
-    }
-
-    public String getColorName() {
-        return colorName;
-    }
-
-    public void setColorName(String colorName) {
-        this.colorName = colorName;
-    }
-
-    public Set<String> getValidTriggers() {
-        return validTriggers;
-    }
-
-    public void setValidTriggers(Set<String> validTriggers) {
-        this.validTriggers = validTriggers;
-    }
-
-    public List<MerchantItemForSale> getMerchantItemForSales() {
-        return merchantItemForSales;
-    }
-
-    public void setInternalName(String internalName) {
-        this.internalName = internalName;
-    }
-
-    public Set<Integer> getRoomId() {
-        return roomIds;
-    }
-
-    public void setRoomIds(Set<Integer> roomIds) {
-        this.roomIds = roomIds;
-    }
-
-    public void setMerchantItemForSales(List<MerchantItemForSale> merchantItemForSales) {
-        this.merchantItemForSales = merchantItemForSales;
-
-    }
-
-    public String getWelcomeMessage() {
-        return welcomeMessage;
-    }
-
-    public void setWelcomeMessage(String welcomeMessage) {
-        this.welcomeMessage = welcomeMessage;
-    }
-}
diff --git a/src/main/java/com/syncleus/aethermud/storage/AetherMudStorage.java b/src/main/java/com/syncleus/aethermud/storage/AetherMudStorage.java
index f19d20a0..4aca914d 100644
--- a/src/main/java/com/syncleus/aethermud/storage/AetherMudStorage.java
+++ b/src/main/java/com/syncleus/aethermud/storage/AetherMudStorage.java
@@ -19,11 +19,9 @@ package com.syncleus.aethermud.storage;
 import com.syncleus.aethermud.core.GameManager;
 import com.syncleus.aethermud.items.Item;
 import com.syncleus.aethermud.items.ItemInstance;
+import com.syncleus.aethermud.merchant.Merchant;
 import com.syncleus.aethermud.npc.NpcSpawn;
-import com.syncleus.aethermud.storage.graphdb.model.ItemData;
-import com.syncleus.aethermud.storage.graphdb.model.ItemInstanceData;
-import com.syncleus.aethermud.storage.graphdb.model.NpcData;
-import com.syncleus.aethermud.storage.graphdb.model.PlayerData;
+import com.syncleus.aethermud.storage.graphdb.model.*;
 
 import java.util.List;
 import java.util.Map;
@@ -59,4 +57,13 @@ public interface AetherMudStorage {
 
     GraphInfo getGraphInfo();
 
+    List<? extends MerchantData> getMerchantDatas();
+
+    List<Merchant> getAllMerchants(GameManager gameManager);
+
+    Merchant createMerchant(GameManager gameManager, MerchantData merchantData);
+
+    Optional<MerchantData> getMerchantData(String internalName);
+
+    MerchantData newMerchantData();
 }
diff --git a/src/main/java/com/syncleus/aethermud/storage/MerchantStorage.java b/src/main/java/com/syncleus/aethermud/storage/MerchantStorage.java
deleted file mode 100644
index 899d747d..00000000
--- a/src/main/java/com/syncleus/aethermud/storage/MerchantStorage.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/**
- * Copyright 2017 Syncleus, Inc.
- *
- * Licensed 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 com.syncleus.aethermud.storage;
-
-import com.syncleus.aethermud.common.ColorizedTextTemplate;
-import com.syncleus.aethermud.core.GameManager;
-import com.syncleus.aethermud.merchant.Merchant;
-import com.syncleus.aethermud.merchant.MerchantMetadata;
-import org.apache.log4j.Logger;
-
-import java.io.IOException;
-import java.util.List;
-import java.util.Optional;
-import java.util.stream.Collectors;
-
-public class MerchantStorage {
-
-    public final static String LOCAL_MERCHANT_DIRECTORY = "world/merchants/";
-
-    private static final Logger log = Logger.getLogger(MerchantStorage.class);
-    private final FilebasedJsonStorage filebasedJsonStorage;
-    private final GameManager gameManager;
-    private final List<MerchantMetadata> merchantMetadatas;
-
-    public MerchantStorage(GameManager gameManager, FilebasedJsonStorage filebasedJsonStorage) {
-        this.gameManager = gameManager;
-        this.filebasedJsonStorage = filebasedJsonStorage;
-        this.merchantMetadatas = getAllMerchantMetadata();
-    }
-
-    private List<MerchantMetadata> getAllMerchantMetadata() {
-        return filebasedJsonStorage.readAllMetadatas(LOCAL_MERCHANT_DIRECTORY, true, new MerchantMetadata()).stream()
-                .map(merchantMetadata -> {
-                    merchantMetadata.setColorName(ColorizedTextTemplate.renderFromTemplateLanguage(merchantMetadata.getColorName()));
-                    merchantMetadata.setWelcomeMessage(ColorizedTextTemplate.renderFromTemplateLanguage(merchantMetadata.getWelcomeMessage()));
-                    return merchantMetadata;
-                }).collect(Collectors.toList());
-    }
-
-    public List<MerchantMetadata> getMerchantMetadatas() {
-        return merchantMetadatas;
-    }
-
-    public List<Merchant> getAllMerchants() {
-        return merchantMetadatas.stream()
-                .map(this::create)
-                .collect(Collectors.toList());
-    }
-
-    public Merchant create(MerchantMetadata merchantMetadata) {
-
-        if (merchantMetadata.getMerchantType() != null) {
-            return new Merchant(gameManager,
-                    merchantMetadata.getInternalName(),
-                    merchantMetadata.getName(),
-                    merchantMetadata.getColorName(),
-                    merchantMetadata.getValidTriggers(),
-                    merchantMetadata.getMerchantItemForSales(),
-                    merchantMetadata.getWelcomeMessage(),
-                    merchantMetadata.getRoomId(),
-                    merchantMetadata.getMerchantType());
-        }
-
-        return new Merchant(gameManager,
-                merchantMetadata.getInternalName(),
-                merchantMetadata.getName(),
-                merchantMetadata.getColorName(),
-                merchantMetadata.getValidTriggers(),
-                merchantMetadata.getMerchantItemForSales(),
-                merchantMetadata.getWelcomeMessage(),
-                merchantMetadata.getRoomId());
-    }
-
-    public void saveMerchantMetadata(MerchantMetadata merchantMetadata) throws IOException {
-        merchantMetadata.setColorName(ColorizedTextTemplate.renderToTemplateLanguage(merchantMetadata.getColorName()));
-        merchantMetadata.setWelcomeMessage(ColorizedTextTemplate.renderToTemplateLanguage(merchantMetadata.getWelcomeMessage()));
-        filebasedJsonStorage.saveMetadata(merchantMetadata.getInternalName(), LOCAL_MERCHANT_DIRECTORY, merchantMetadata);
-    }
-
-    public Optional<MerchantMetadata> get(String internalName) {
-        return merchantMetadatas.stream()
-                .filter(merchantMetadata -> merchantMetadata.getInternalName().equals(internalName))
-                .findFirst();
-    }
-
-}
diff --git a/src/main/java/com/syncleus/aethermud/storage/graphdb/GraphDbAetherMudStorage.java b/src/main/java/com/syncleus/aethermud/storage/graphdb/GraphDbAetherMudStorage.java
index 2ca865aa..40159416 100644
--- a/src/main/java/com/syncleus/aethermud/storage/graphdb/GraphDbAetherMudStorage.java
+++ b/src/main/java/com/syncleus/aethermud/storage/graphdb/GraphDbAetherMudStorage.java
@@ -20,14 +20,12 @@ import com.google.common.collect.Interners;
 import com.syncleus.aethermud.core.GameManager;
 import com.syncleus.aethermud.items.Item;
 import com.syncleus.aethermud.items.ItemInstance;
+import com.syncleus.aethermud.merchant.Merchant;
 import com.syncleus.aethermud.npc.NpcBuilder;
 import com.syncleus.aethermud.npc.NpcSpawn;
 import com.syncleus.aethermud.storage.AetherMudStorage;
 import com.syncleus.aethermud.storage.GraphInfo;
-import com.syncleus.aethermud.storage.graphdb.model.ItemData;
-import com.syncleus.aethermud.storage.graphdb.model.ItemInstanceData;
-import com.syncleus.aethermud.storage.graphdb.model.NpcData;
-import com.syncleus.aethermud.storage.graphdb.model.PlayerData;
+import com.syncleus.aethermud.storage.graphdb.model.*;
 import com.syncleus.ferma.VertexFrame;
 import com.syncleus.ferma.WrappedFramedGraph;
 import org.apache.log4j.Logger;
@@ -156,4 +154,44 @@ public class GraphDbAetherMudStorage implements AetherMudStorage {
     public NpcData newNpcData() {
         return framedGraph.addFramedVertex(NpcData.class);
     }
+
+    public List<? extends MerchantData> getMerchantDatas() {
+        return framedGraph.traverse((g) -> framedGraph.getTypeResolver().hasType(g.V(), MerchantData.class)).toList(MerchantData.class);
+    }
+
+    public List<Merchant> getAllMerchants(GameManager gameManager) {
+        return this.getMerchantDatas().stream().map(m -> this.createMerchant(gameManager, m)).collect(Collectors.toList());
+    }
+
+    public Merchant createMerchant(GameManager gameManager, MerchantData merchantData) {
+        if (merchantData.getMerchantType() != null) {
+            return new Merchant(gameManager,
+                merchantData.getInternalName(),
+                merchantData.getName(),
+                merchantData.getColorName(),
+                merchantData.getValidTriggers(),
+                merchantData.getMerchantItemForSaleDatas().stream().map(m -> MerchantItemForSaleData.copyMerchantItemForSale(m)).collect(Collectors.toList()),
+                merchantData.getWelcomeMessage(),
+                merchantData.getRoomIds(),
+                merchantData.getMerchantType());
+        }
+
+        return new Merchant(gameManager,
+            merchantData.getInternalName(),
+            merchantData.getName(),
+            merchantData.getColorName(),
+            merchantData.getValidTriggers(),
+            merchantData.getMerchantItemForSaleDatas().stream().map(m -> MerchantItemForSaleData.copyMerchantItemForSale(m)).collect(Collectors.toList()),
+            merchantData.getWelcomeMessage(),
+            merchantData.getRoomIds());
+    }
+
+    public Optional<MerchantData> getMerchantData(String internalName) {
+        MerchantData data = framedGraph.traverse((g) -> framedGraph.getTypeResolver().hasType(g.V(), MerchantData.class).property("internalName", internalName)).nextOrDefault(MerchantData.class,null);
+        return Optional.ofNullable(data);
+    }
+
+    public MerchantData newMerchantData() {
+        return framedGraph.addFramedVertex(MerchantData.class);
+    }
 }
diff --git a/src/main/java/com/syncleus/aethermud/storage/graphdb/model/MerchantData.java b/src/main/java/com/syncleus/aethermud/storage/graphdb/model/MerchantData.java
new file mode 100644
index 00000000..fe721c1b
--- /dev/null
+++ b/src/main/java/com/syncleus/aethermud/storage/graphdb/model/MerchantData.java
@@ -0,0 +1,120 @@
+/**
+ * Copyright 2017 Syncleus, Inc.
+ *
+ * Licensed 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 com.syncleus.aethermud.storage.graphdb.model;
+
+import com.google.common.collect.Lists;
+import com.syncleus.aethermud.common.AetherMudMessage;
+import com.syncleus.aethermud.common.ColorizedTextTemplate;
+import com.syncleus.aethermud.merchant.Merchant;
+import com.syncleus.aethermud.merchant.MerchantItemForSale;
+import com.syncleus.aethermud.npc.Npc;
+import com.syncleus.aethermud.spawner.SpawnRule;
+import com.syncleus.aethermud.storage.graphdb.DataUtils;
+import com.syncleus.ferma.annotations.Adjacency;
+import com.syncleus.ferma.annotations.GraphElement;
+import com.syncleus.ferma.annotations.Property;
+import com.syncleus.ferma.ext.AbstractInterceptingVertexFrame;
+import org.apache.commons.beanutils.PropertyUtils;
+import org.apache.tinkerpop.gremlin.structure.Direction;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+@GraphElement
+public abstract class MerchantData extends AbstractInterceptingVertexFrame {
+
+    @Property("merchantType")
+    public abstract void setMerchantType(Merchant.MerchantType merchantType);
+
+    @Property("merchantType")
+    public abstract Merchant.MerchantType getMerchantType();
+
+    @Property("internalName")
+    public abstract String getInternalName();
+
+    @Property("internalName")
+    public abstract void setInternalName(String internalName);
+
+    @Property("name")
+    public abstract String getName();
+
+    @Property("name")
+    public abstract void setName(String name);
+    
+    public String getColorName() {
+        return ColorizedTextTemplate.renderFromTemplateLanguage(this.getProperty("colorName"));
+    }
+
+    public void setColorName(String colorName) {
+        this.setProperty("colorName", ColorizedTextTemplate.renderToTemplateLanguage(colorName));
+    }
+
+    @Property("validTriggers")
+    public abstract Set<String> getValidTriggers();
+
+    @Property("validTriggers")
+    public abstract void setValidTriggers(Set<String> validTriggers);
+
+    @Property("roomIds")
+    public abstract Set<Integer> getRoomIds();
+
+    @Property("roomIds")
+    public abstract void setRoomIds(Set<Integer> roomIds);
+
+    @Property("welcomeMessage")
+    public abstract String getWelcomeMessage();
+
+    @Property("welcomeMessage")
+    public abstract void setWelcomeMessage(String welcomeMessage);
+
+    @Adjacency(label = "merchantItemForSales", direction = Direction.OUT)
+    public abstract <N extends MerchantItemForSaleData> Iterator<? extends N> getMerchantItemForSaleDataIterator(Class<? extends N> type);
+
+    public List<MerchantItemForSaleData> getMerchantItemForSaleDatas() {
+        return Lists.newArrayList(this.getMerchantItemForSaleDataIterator(MerchantItemForSaleData.class));
+    }
+
+    @Adjacency(label = "merchantItemForSales", direction = Direction.OUT)
+    public abstract void addMerchantItemForSaleData(MerchantItemForSaleData item);
+
+    @Adjacency(label = "merchantItemForSales", direction = Direction.OUT)
+    public abstract void removeMerchantItemForSaleData(MerchantItemForSaleData item);
+
+    public void setMerchantItemForSaleDatas(List<MerchantItemForSaleData> items) {
+        DataUtils.setAllElements(items, () -> this.getMerchantItemForSaleDataIterator(MerchantItemForSaleData.class), item -> this.addMerchantItemForSaleData(item), () -> this.createMerchantItemForSaleData() );
+    }
+
+    public MerchantItemForSaleData createMerchantItemForSaleData() {
+        MerchantItemForSaleData item = this.getGraph().addFramedVertex(MerchantItemForSaleData.class);
+        this.addMerchantItemForSaleData(item);
+        return item;
+    }
+
+    public static void copyMerchant(MerchantData dest, Merchant src) {
+        try {
+            PropertyUtils.copyProperties(dest, src);
+
+            for(MerchantItemForSaleData data : dest.getMerchantItemForSaleDatas())
+                data.remove();
+            for(MerchantItemForSale item : src.getMerchantItemForSales())
+                MerchantItemForSaleData.copyMerchantItemForSale(dest.createMerchantItemForSaleData(), item);
+        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
+            throw new IllegalStateException("Could not copy properties", e);
+        }
+    }
+}
diff --git a/src/main/java/com/syncleus/aethermud/storage/graphdb/model/MerchantItemForSaleData.java b/src/main/java/com/syncleus/aethermud/storage/graphdb/model/MerchantItemForSaleData.java
new file mode 100644
index 00000000..bb2f5a4c
--- /dev/null
+++ b/src/main/java/com/syncleus/aethermud/storage/graphdb/model/MerchantItemForSaleData.java
@@ -0,0 +1,51 @@
+/**
+ * Copyright 2017 Syncleus, Inc.
+ *
+ * Licensed 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 com.syncleus.aethermud.storage.graphdb.model;
+
+import com.syncleus.aethermud.merchant.MerchantItemForSale;
+import com.syncleus.ferma.annotations.GraphElement;
+import com.syncleus.ferma.annotations.Property;
+import com.syncleus.ferma.ext.AbstractInterceptingVertexFrame;
+import org.apache.commons.beanutils.PropertyUtils;
+
+import java.lang.reflect.InvocationTargetException;
+
+@GraphElement
+public abstract class MerchantItemForSaleData extends AbstractInterceptingVertexFrame {
+    @Property("internalName")
+    public abstract String getInternalItemName();
+
+    @Property("internalName")
+    public abstract void setInternalItemName(String internalName);
+
+    @Property("cost")
+    public abstract int getCost();
+
+    @Property("cost")
+    public abstract void setCost(int cost);
+
+    public static void copyMerchantItemForSale(MerchantItemForSaleData dest, MerchantItemForSale src) {
+        try {
+            PropertyUtils.copyProperties(dest, src);
+        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
+            throw new IllegalStateException("Could not copy properties", e);
+        }
+    }
+
+    public static MerchantItemForSale copyMerchantItemForSale(MerchantItemForSaleData src) {
+        return new MerchantItemForSale(src.getInternalItemName(), src.getCost());
+    }
+}
-- 
GitLab