Initial import of CFM and SOAM api

Change-Id: Icf5cc2d5fb34b75460e80e8cced0d70265bcd33b
diff --git a/apps/cfm/src/main/java/org/onosproject/soam/web/DelayMeasurementStatCodec.java b/apps/cfm/src/main/java/org/onosproject/soam/web/DelayMeasurementStatCodec.java
new file mode 100644
index 0000000..7bd0d7a
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/soam/web/DelayMeasurementStatCodec.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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 org.onosproject.soam.web;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.time.Duration;
+import java.util.Map;
+
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.incubator.net.l2monitoring.soam.delay.DelayMeasurementStat;
+
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+/**
+ * Encode and decode to/from JSON to DelayMeasurementStat object.
+ */
+public class DelayMeasurementStatCodec extends JsonCodec<DelayMeasurementStat> {
+
+    private static final String LOWER_LIMIT = "lowerLimit";
+    private static final String COUNT = "count";
+    private static final String BINS = "bins";
+    private static final String SOAM_PDUS_SENT = "soamPdusSent";
+    private static final String SOAM_PDUS_RECEIVED = "soamPdusReceived";
+
+    @Override
+    public ObjectNode encode(DelayMeasurementStat dmStat, CodecContext context) {
+        checkNotNull(dmStat, "DM stat cannot be null");
+        ObjectNode result = context.mapper().createObjectNode()
+                .put("elapsedTime", dmStat.elapsedTime().toString())
+                .put("suspectStatus", String.valueOf(dmStat.suspectStatus()));
+
+        if (dmStat.frameDelayTwoWayMin() != null) {
+            result = result.put("frameDelayTwoWayMin",
+                    dmStat.frameDelayTwoWayMin().toString());
+        }
+        if (dmStat.frameDelayTwoWayMax() != null) {
+            result = result.put("frameDelayTwoWayMax",
+                    dmStat.frameDelayTwoWayMax().toString());
+        }
+        if (dmStat.frameDelayTwoWayAvg() != null) {
+            result = result.put("frameDelayTwoWayAvg",
+                    dmStat.frameDelayTwoWayAvg().toString());
+        }
+        if (dmStat.frameDelayTwoWayBins() != null) {
+            result = (ObjectNode) result.set("frameDelayTwoWayBins",
+                    encode(dmStat.frameDelayTwoWayBins(), context));
+        }
+        if (dmStat.frameDelayForwardMin() != null) {
+            result = result.put("frameDelayForwardMin",
+                    dmStat.frameDelayForwardMin().toString());
+        }
+        if (dmStat.frameDelayForwardMax() != null) {
+            result = result.put("frameDelayForwardMax",
+                    dmStat.frameDelayForwardMax().toString());
+        }
+        if (dmStat.frameDelayForwardAvg() != null) {
+            result = result.put("frameDelayForwardAvg",
+                    dmStat.frameDelayForwardAvg().toString());
+        }
+        if (dmStat.frameDelayForwardBins() != null) {
+            result = (ObjectNode) result.set("frameDelayForwardBins",
+                    encode(dmStat.frameDelayForwardBins(), context));
+        }
+        if (dmStat.frameDelayBackwardMin() != null) {
+            result = result.put("frameDelayBackwardMin",
+                    dmStat.frameDelayBackwardMin().toString());
+        }
+        if (dmStat.frameDelayBackwardMax() != null) {
+            result = result.put("frameDelayBackwardMax",
+                    dmStat.frameDelayBackwardMax().toString());
+        }
+        if (dmStat.frameDelayBackwardAvg() != null) {
+            result = result.put("frameDelayBackwardAvg",
+                    dmStat.frameDelayBackwardAvg().toString());
+        }
+        if (dmStat.frameDelayBackwardBins() != null) {
+            result = (ObjectNode) result.set("frameDelayBackwardBins",
+                    encode(dmStat.frameDelayBackwardBins(), context));
+        }
+        if (dmStat.interFrameDelayVariationTwoWayMin() != null) {
+            result = result.put("interFrameDelayVariationTwoWayMin",
+                    dmStat.interFrameDelayVariationTwoWayMin().toString());
+        }
+        if (dmStat.interFrameDelayVariationTwoWayMax() != null) {
+            result.put("interFrameDelayVariationTwoWayMax",
+                    dmStat.interFrameDelayVariationTwoWayMax().toString());
+        }
+        if (dmStat.interFrameDelayVariationTwoWayAvg() != null) {
+            result.put("interFrameDelayVariationTwoWayAvg",
+                    dmStat.interFrameDelayVariationTwoWayAvg().toString());
+        }
+        if (dmStat.interFrameDelayVariationTwoWayBins() != null) {
+            result = (ObjectNode) result.set("interFrameDelayVariationTwoWayBins",
+                    encode(dmStat.interFrameDelayVariationTwoWayBins(), context));
+        }
+        if (dmStat.interFrameDelayVariationForwardMin() != null) {
+            result = result.put("interFrameDelayVariationForwardMin",
+                    dmStat.interFrameDelayVariationForwardMin().toString());
+        }
+        if (dmStat.interFrameDelayVariationForwardMax() != null) {
+            result = result.put("interFrameDelayVariationForwardMax",
+                    dmStat.interFrameDelayVariationForwardMax().toString());
+        }
+        if (dmStat.interFrameDelayVariationForwardAvg() != null) {
+            result = result.put("interFrameDelayVariationForwardAvg",
+                    dmStat.interFrameDelayVariationForwardAvg().toString());
+        }
+        if (dmStat.interFrameDelayVariationForwardBins() != null) {
+            result = (ObjectNode) result.set("interFrameDelayVariationForwardBins",
+                    encode(dmStat.interFrameDelayVariationForwardBins(), context));
+        }
+        if (dmStat.interFrameDelayVariationBackwardMin() != null) {
+            result = result.put("interFrameDelayVariationBackwardMin",
+                    dmStat.interFrameDelayVariationBackwardMin().toString());
+        }
+        if (dmStat.interFrameDelayVariationBackwardMax() != null) {
+            result = result.put("interFrameDelayVariationBackwardMax",
+                    dmStat.interFrameDelayVariationBackwardMax().toString());
+        }
+        if (dmStat.interFrameDelayVariationBackwardAvg() != null) {
+            result = result.put("interFrameDelayVariationBackwardAvg",
+                    dmStat.interFrameDelayVariationBackwardAvg().toString());
+        }
+        if (dmStat.interFrameDelayVariationBackwardBins() != null) {
+            result = (ObjectNode) result.set("interFrameDelayVariationBackwardBins",
+                    encode(dmStat.interFrameDelayVariationBackwardBins(), context));
+        }
+        if (dmStat.frameDelayRangeTwoWayMax() != null) {
+            result = result.put("frameDelayRangeTwoWayMax",
+                    dmStat.frameDelayRangeTwoWayMax().toString());
+        }
+        if (dmStat.frameDelayRangeTwoWayAvg() != null) {
+            result = result.put("frameDelayRangeTwoWayAvg",
+                    dmStat.frameDelayRangeTwoWayAvg().toString());
+        }
+        if (dmStat.frameDelayRangeTwoWayBins() != null) {
+            result = (ObjectNode) result.set("frameDelayRangeTwoWayBins",
+                    encode(dmStat.frameDelayRangeTwoWayBins(), context));
+        }
+        if (dmStat.frameDelayRangeForwardMax() != null) {
+            result = result.put("frameDelayRangeForwardMax",
+                    dmStat.frameDelayRangeForwardMax().toString());
+        }
+        if (dmStat.frameDelayRangeForwardAvg() != null) {
+            result = result.put("frameDelayRangeForwardAvg",
+                    dmStat.frameDelayRangeForwardAvg().toString());
+        }
+        if (dmStat.frameDelayRangeForwardBins() != null) {
+            result = (ObjectNode) result.set("frameDelayRangeForwardBins",
+                    encode(dmStat.frameDelayRangeForwardBins(), context));
+        }
+        if (dmStat.frameDelayRangeBackwardMax() != null) {
+            result = result.put("frameDelayRangeBackwardMax",
+                    dmStat.frameDelayRangeBackwardMax().toString());
+        }
+        if (dmStat.frameDelayRangeBackwardAvg() != null) {
+            result = result.put("frameDelayRangeBackwardAvg",
+                    dmStat.frameDelayRangeBackwardAvg().toString());
+        }
+        if (dmStat.frameDelayRangeBackwardBins() != null) {
+            result = (ObjectNode) result.set("frameDelayRangeBackwardBins",
+                    encode(dmStat.frameDelayRangeBackwardBins(), context));
+        }
+
+        if (dmStat.soamPdusReceived() != null) {
+            result = result.put(SOAM_PDUS_RECEIVED, dmStat.soamPdusReceived().toString());
+        }
+
+        if (dmStat.soamPdusSent() != null) {
+            result = result.put(SOAM_PDUS_SENT, dmStat.soamPdusSent().toString());
+        }
+
+        return result;
+    }
+
+    private ObjectNode encode(Map<Duration, Integer> bins, CodecContext context) {
+        checkNotNull(bins, "Bins cannot be null");
+        ArrayNode binsResult = context.mapper().createArrayNode();
+        bins.keySet().forEach(lwrLimit -> binsResult.add(encode(lwrLimit, bins.get(lwrLimit), context)));
+
+        return (ObjectNode) context.mapper().createObjectNode().set(BINS, binsResult);
+    }
+
+    private ObjectNode encode(Duration duration, Integer count, CodecContext context) {
+        return context.mapper().createObjectNode()
+            .put(LOWER_LIMIT, duration.toString())
+            .put(COUNT, count);
+    }
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/soam/web/DelayMeasurementStatCurrentCodec.java b/apps/cfm/src/main/java/org/onosproject/soam/web/DelayMeasurementStatCurrentCodec.java
new file mode 100644
index 0000000..a79cd90
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/soam/web/DelayMeasurementStatCurrentCodec.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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 org.onosproject.soam.web;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.incubator.net.l2monitoring.soam.delay.DelayMeasurementStatCurrent;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+/**
+ * Encode and decode to/from JSON to DelayMeasurementStatCurrent object.
+ */
+public class DelayMeasurementStatCurrentCodec extends JsonCodec<DelayMeasurementStatCurrent> {
+
+    @Override
+    public ObjectNode encode(DelayMeasurementStatCurrent dmCurrent, CodecContext context) {
+        checkNotNull(dmCurrent, "DM current cannot be null");
+        ObjectNode result = context.mapper().createObjectNode()
+                .put("startTime", dmCurrent.startTime().toString())
+                .put("elapsedTime", dmCurrent.elapsedTime().toString());
+
+        ObjectNode resultAbstract = new DelayMeasurementStatCodec().encode(dmCurrent, context);
+        result.setAll(resultAbstract);
+
+        return result;
+    }
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/soam/web/DelayMeasurementStatHistoryCodec.java b/apps/cfm/src/main/java/org/onosproject/soam/web/DelayMeasurementStatHistoryCodec.java
new file mode 100644
index 0000000..8901958
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/soam/web/DelayMeasurementStatHistoryCodec.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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 org.onosproject.soam.web;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.incubator.net.l2monitoring.soam.delay.DelayMeasurementStatHistory;
+
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+/**
+ * Encode and decode to/from JSON to DelayMeasurementStatHistory object.
+ */
+public class DelayMeasurementStatHistoryCodec extends JsonCodec<DelayMeasurementStatHistory> {
+
+    @Override
+    public ObjectNode encode(DelayMeasurementStatHistory dmHistory, CodecContext context) {
+        checkNotNull(dmHistory, "DM history cannot be null");
+        ObjectNode result = context.mapper().createObjectNode()
+                .put("historyId", String.valueOf(dmHistory.historyStatsId()))
+                .put("endTime", dmHistory.endTime().toString());
+        ObjectNode resultAbstract = new DelayMeasurementStatCodec().encode(dmHistory, context);
+        result.setAll(resultAbstract);
+        return result;
+    }
+
+    @Override
+    public ArrayNode encode(Iterable<DelayMeasurementStatHistory> historyEntities, CodecContext context) {
+        ArrayNode an = context.mapper().createArrayNode();
+        historyEntities.forEach(history -> an.add(encode(history, context)));
+        return an;
+    }
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/soam/web/DmCreateCodec.java b/apps/cfm/src/main/java/org/onosproject/soam/web/DmCreateCodec.java
new file mode 100644
index 0000000..8743eb4
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/soam/web/DmCreateCodec.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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 org.onosproject.soam.web;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onlab.util.Tools.nullIsIllegal;
+
+import java.time.Duration;
+
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.incubator.net.l2monitoring.cfm.Mep.Priority;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MepId;
+import org.onosproject.incubator.net.l2monitoring.soam.SoamConfigException;
+import org.onosproject.incubator.net.l2monitoring.soam.StartTime;
+import org.onosproject.incubator.net.l2monitoring.soam.StopTime;
+import org.onosproject.incubator.net.l2monitoring.soam.delay.DefaultDelayMeasurementCreate;
+import org.onosproject.incubator.net.l2monitoring.soam.delay.DelayMeasurementCreate;
+import org.onosproject.incubator.net.l2monitoring.soam.delay.DelayMeasurementCreate.DmCreateBuilder;
+import org.onosproject.incubator.net.l2monitoring.soam.delay.DelayMeasurementCreate.DmType;
+import org.onosproject.incubator.net.l2monitoring.soam.delay.DelayMeasurementCreate.MeasurementOption;
+import org.onosproject.incubator.net.l2monitoring.soam.delay.DelayMeasurementCreate.Version;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+/**
+ * Encode and decode to/from JSON to DelayMeasurementCreate object.
+ */
+public class DmCreateCodec extends JsonCodec<DelayMeasurementCreate> {
+
+    private static final String VERSION = "version";
+    private static final String DM = "dm";
+    private static final String DM_CFG_TYPE = "dmCfgType";
+    private static final String DMDMM = "DMDMM";
+    private static final String REMOTE_MEP_ID = "remoteMepId";
+    private static final String PRIORITY = "priority";
+    private static final String MEASUREMENTS_ENABLED = "measurementsEnabled";
+    private static final String BINS_PER_FD_INTERVAL = "binsPerFdInterval";
+    private static final String BINS_PER_IFDV_INTERVAL = "binsPerIfdvInterval";
+    private static final String IFDV_SELECTION_OFFSET = "ifdvSelectionOffset";
+    private static final String BINS_PER_FDR_INTERVAL = "binsPerFdrInterval";
+    private static final String FRAME_SIZE = "frameSize";
+    private static final String MESSAGE_PERIOD_MS = "messagePeriodMs";
+    private static final String MEASUREMENT_INTERVAL_MINS = "measurementIntervalMins";
+    private static final String ALIGN_MEASUREMENT_INTERVALS = "alignMeasurementIntervals";
+    private static final String ALIGN_MEASUREMENT_OFFSET_MINS = "alignMeasurementOffsetMins";
+    private static final String START_TIME = "startTime";
+    private static final String STOP_TIME = "stopTime";
+
+    @Override
+    public DelayMeasurementCreate decode(ObjectNode json,
+            CodecContext context) {
+
+        if (json == null || !json.isObject()) {
+            return null;
+        }
+
+        JsonNode dmNode = json.get(DM);
+        Version version = Version.Y17312011;
+        if (dmNode.get(VERSION) != null) {
+            version = Version.valueOf(dmNode.get(VERSION).asText());
+        }
+        DmType dmCfgType = DmType.DMDMM;
+        if (dmNode.get(DM_CFG_TYPE) != null) {
+            dmCfgType = DmType.valueOf(dmNode.get(DM_CFG_TYPE).asText(DMDMM));
+        }
+        MepId remoteMepId = MepId.valueOf(
+                nullIsIllegal(dmNode.get(REMOTE_MEP_ID), REMOTE_MEP_ID + " is required")
+                .shortValue());
+        Priority prio = Priority.valueOf(nullIsIllegal(dmNode.get(PRIORITY),
+                PRIORITY + " is required in the format 'PRIOn'").asText());
+
+        try {
+            DmCreateBuilder builder = DefaultDelayMeasurementCreate
+                    .builder(dmCfgType, version, remoteMepId, prio);
+
+            if (dmNode.get(MEASUREMENTS_ENABLED) != null) {
+                context.codec(MeasurementOption.class)
+                    .decode((ArrayNode) (dmNode.get(MEASUREMENTS_ENABLED)), context)
+                    .forEach(builder::addToMeasurementsEnabled);
+            }
+
+            if (dmNode.get(BINS_PER_FD_INTERVAL) != null) {
+                builder = builder.binsPerFdInterval(
+                        (short) dmNode.get(BINS_PER_FD_INTERVAL).asInt());
+            }
+            if (dmNode.get(BINS_PER_IFDV_INTERVAL) != null) {
+                builder = builder.binsPerIfdvInterval(
+                        (short) dmNode.get(BINS_PER_IFDV_INTERVAL).asInt());
+            }
+            if (dmNode.get(IFDV_SELECTION_OFFSET) != null) {
+                builder = builder.ifdvSelectionOffset(
+                        (short) dmNode.get(IFDV_SELECTION_OFFSET).asInt());
+            }
+            if (dmNode.get(BINS_PER_FDR_INTERVAL) != null) {
+                builder = builder.binsPerFdrInterval(
+                        (short) dmNode.get(BINS_PER_FDR_INTERVAL).asInt());
+            }
+            if (dmNode.get(FRAME_SIZE) != null) {
+                builder = (DmCreateBuilder) builder.frameSize(
+                        (short) dmNode.get(FRAME_SIZE).asInt());
+            }
+            if (dmNode.get(MESSAGE_PERIOD_MS) != null) {
+                builder = (DmCreateBuilder) builder.messagePeriod(Duration.ofMillis(
+                        dmNode.get(MESSAGE_PERIOD_MS).asInt()));
+            }
+            if (dmNode.get(MEASUREMENT_INTERVAL_MINS) != null) {
+                builder = (DmCreateBuilder) builder.measurementInterval(
+                        Duration.ofMinutes(
+                        dmNode.get(MEASUREMENT_INTERVAL_MINS).asInt()));
+            }
+            if (dmNode.get(ALIGN_MEASUREMENT_INTERVALS) != null) {
+                builder = (DmCreateBuilder) builder.alignMeasurementIntervals(
+                        dmNode.get(ALIGN_MEASUREMENT_INTERVALS).asBoolean());
+            }
+            if (dmNode.get(ALIGN_MEASUREMENT_OFFSET_MINS) != null) {
+                builder = (DmCreateBuilder) builder.alignMeasurementOffset(Duration.ofMinutes(
+                        dmNode.get(ALIGN_MEASUREMENT_OFFSET_MINS).asInt()));
+            }
+            if (dmNode.get(START_TIME) != null) {
+                builder = (DmCreateBuilder) builder.startTime(context.codec(StartTime.class)
+                .decode((ObjectNode) dmNode.get(START_TIME), context));
+            }
+            if (dmNode.get(STOP_TIME) != null) {
+                builder = (DmCreateBuilder) builder.stopTime(context.codec(StopTime.class)
+                .decode((ObjectNode) dmNode.get(STOP_TIME), context));
+            }
+
+            return builder.build();
+        } catch (SoamConfigException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    @Override
+    public ObjectNode encode(DelayMeasurementCreate dm, CodecContext context) {
+        checkNotNull(dm, "DM cannot be null");
+        ObjectNode result = context.mapper().createObjectNode()
+                .put(DM_CFG_TYPE, dm.dmCfgType().name())
+                .put(VERSION, dm.version().name())
+                .put(REMOTE_MEP_ID, dm.remoteMepId().id())
+                .put(PRIORITY, dm.priority().name());
+
+        if (dm.measurementsEnabled() != null) {
+            result.set(MEASUREMENTS_ENABLED, new DmMeasurementOptionCodec()
+                    .encode(dm.measurementsEnabled(), context));
+        }
+
+        if (dm.messagePeriod() != null) {
+            result.put(MESSAGE_PERIOD_MS, dm.messagePeriod().toMillis());
+        }
+        if (dm.frameSize() != null) {
+            result.put(FRAME_SIZE, dm.frameSize());
+        }
+
+
+        return result;
+    }
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/soam/web/DmEntryCodec.java b/apps/cfm/src/main/java/org/onosproject/soam/web/DmEntryCodec.java
new file mode 100644
index 0000000..ce851d6
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/soam/web/DmEntryCodec.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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 org.onosproject.soam.web;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Iterator;
+import java.util.Map.Entry;
+
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.incubator.net.l2monitoring.soam.delay.DelayMeasurementEntry;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+/**
+ * Encode and decode to/from JSON to DelayMeasurementEntry object.
+ */
+public class DmEntryCodec extends JsonCodec<DelayMeasurementEntry> {
+
+    private static final String DM_ID = "dmId";
+    private static final String SESSION_STATUS = "sessionStatus";
+    private static final String FRAME_DELAY_TWO_WAY = "frameDelayTwoWay";
+    private static final String FRAME_DELAY_FORWARD = "frameDelayForward";
+    private static final String FRAME_DELAY_BACKWARD = "frameDelayBackward";
+    private static final String INTER_FRAME_DELAY_VARIATION_TWO_WAY = "interFrameDelayVariationTwoWay";
+    private static final String INTER_FRAME_DELAY_VARIATION_FORWARD = "interFrameDelayVariationForward";
+    private static final String INTER_FRAME_DELAY_VARIATION_BACKWARD = "interFrameDelayVariationBackward";
+    private static final String CURRENT = "current";
+    private static final String HISTORIC = "historic";
+
+    @Override
+    public ObjectNode encode(DelayMeasurementEntry dm, CodecContext context) {
+        checkNotNull(dm, "DM cannot be null");
+        ObjectNode result = context.mapper().createObjectNode()
+                .put(DM_ID, dm.dmId().toString());
+
+        if (dm.sessionStatus() != null) {
+            result.put(SESSION_STATUS, dm.sessionStatus().name());
+        }
+        if (dm.frameDelayTwoWay() != null) {
+            result.put(FRAME_DELAY_TWO_WAY, dm.frameDelayTwoWay().toString());
+        }
+        if (dm.frameDelayForward() != null) {
+            result.put(FRAME_DELAY_FORWARD, dm.frameDelayForward().toString());
+        }
+        if (dm.frameDelayBackward() != null) {
+            result.put(FRAME_DELAY_BACKWARD, dm.frameDelayBackward().toString());
+        }
+        if (dm.interFrameDelayVariationTwoWay() != null) {
+            result.put(INTER_FRAME_DELAY_VARIATION_TWO_WAY,
+                    dm.interFrameDelayVariationTwoWay().toString());
+        }
+        if (dm.interFrameDelayVariationForward() != null) {
+            result.put(INTER_FRAME_DELAY_VARIATION_FORWARD,
+                    dm.interFrameDelayVariationForward().toString());
+        }
+        if (dm.interFrameDelayVariationBackward() != null) {
+            result.put(INTER_FRAME_DELAY_VARIATION_BACKWARD,
+                    dm.interFrameDelayVariationBackward().toString());
+        }
+
+        ObjectNode dmAttrs = new DmCreateCodec().encode(dm, context);
+        Iterator<Entry<String, JsonNode>> elements = dmAttrs.fields();
+        while (elements.hasNext()) {
+            Entry<String, JsonNode> element = elements.next();
+            result.set(element.getKey(), element.getValue());
+        }
+
+        if (dm.currentResult() != null) {
+            result.set(CURRENT, new DelayMeasurementStatCurrentCodec()
+                    .encode(dm.currentResult(), context));
+        }
+
+        if (dm.historicalResults() != null) {
+            result.set(HISTORIC, new DelayMeasurementStatHistoryCodec()
+                    .encode(dm.historicalResults(), context));
+        }
+
+        return result;
+    }
+
+    @Override
+    public ArrayNode encode(Iterable<DelayMeasurementEntry> dmEntities, CodecContext context) {
+        ArrayNode an = context.mapper().createArrayNode();
+        dmEntities.forEach(dm -> an.add(encode(dm, context)));
+        return an;
+    }
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/soam/web/DmMeasurementOptionCodec.java b/apps/cfm/src/main/java/org/onosproject/soam/web/DmMeasurementOptionCodec.java
new file mode 100644
index 0000000..8c8a5a0
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/soam/web/DmMeasurementOptionCodec.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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 org.onosproject.soam.web;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.incubator.net.l2monitoring.soam.delay.DelayMeasurementCreate.MeasurementOption;
+
+import com.fasterxml.jackson.databind.node.ArrayNode;
+
+/**
+ * Encode and decode to/from JSON to MeasurementOption object.
+ */
+public class DmMeasurementOptionCodec extends JsonCodec<MeasurementOption> {
+
+    @Override
+    public ArrayNode encode(Iterable<MeasurementOption> entities,
+            CodecContext context) {
+        ArrayNode an = context.mapper().createArrayNode();
+        entities.forEach(node -> an.add(node.name()));
+
+        return an;
+    }
+
+    @Override
+    public List<MeasurementOption> decode(ArrayNode json,
+            CodecContext context) {
+        if (json == null) {
+            return null;
+        }
+        List<MeasurementOption> moList = new ArrayList<>();
+        json.forEach(node -> moList.add(MeasurementOption.valueOf(node.asText())));
+        return moList;
+    }
+
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/soam/web/LmCounterOptionCodec.java b/apps/cfm/src/main/java/org/onosproject/soam/web/LmCounterOptionCodec.java
new file mode 100644
index 0000000..9e3e40e
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/soam/web/LmCounterOptionCodec.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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 org.onosproject.soam.web;
+
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.incubator.net.l2monitoring.soam.loss.LossMeasurementCreate.CounterOption;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Encode and decode to/from JSON to CounterOption object.
+ */
+public class LmCounterOptionCodec extends JsonCodec<CounterOption> {
+
+    @Override
+    public ArrayNode encode(Iterable<CounterOption> entities,
+            CodecContext context) {
+        ArrayNode an = context.mapper().createArrayNode();
+        entities.forEach(node -> an.add(node.name()));
+
+        return an;
+    }
+
+    @Override
+    public List<CounterOption> decode(ArrayNode json,
+            CodecContext context) {
+        if (json == null) {
+            return null;
+        }
+        List<CounterOption> moList = new ArrayList<>();
+        json.forEach(node -> moList.add(CounterOption.valueOf(node.asText())));
+        return moList;
+    }
+
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/soam/web/LmCreateCodec.java b/apps/cfm/src/main/java/org/onosproject/soam/web/LmCreateCodec.java
new file mode 100644
index 0000000..e88fb2f
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/soam/web/LmCreateCodec.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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 org.onosproject.soam.web;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.incubator.net.l2monitoring.cfm.Mep.Priority;
+import org.onosproject.incubator.net.l2monitoring.cfm.identifier.MepId;
+import org.onosproject.incubator.net.l2monitoring.soam.MilliPct;
+import org.onosproject.incubator.net.l2monitoring.soam.SoamConfigException;
+import org.onosproject.incubator.net.l2monitoring.soam.StartTime;
+import org.onosproject.incubator.net.l2monitoring.soam.StopTime;
+import org.onosproject.incubator.net.l2monitoring.soam.delay.DelayMeasurementCreate.Version;
+import org.onosproject.incubator.net.l2monitoring.soam.loss.DefaultLmCreate;
+import org.onosproject.incubator.net.l2monitoring.soam.loss.LossMeasurementCreate;
+import org.onosproject.incubator.net.l2monitoring.soam.loss.LossMeasurementThreshold;
+
+import java.time.Duration;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onlab.util.Tools.nullIsIllegal;
+import static org.onosproject.incubator.net.l2monitoring.soam.loss.LossMeasurementCreate.CounterOption;
+import static org.onosproject.incubator.net.l2monitoring.soam.loss.LossMeasurementCreate.LmType;
+import static org.onosproject.incubator.net.l2monitoring.soam.loss.LossMeasurementCreate.LmCreateBuilder;
+
+/**
+ * Encode and decode to/from JSON to LossMeasurementCreate object.
+ */
+public class LmCreateCodec extends JsonCodec<LossMeasurementCreate> {
+
+    public static final String LM = "lm";
+    public static final String VERSION = "version";
+    public static final String LM_CFG_TYPE = "lmCfgType";
+    public static final String LMLMM = "LMLMM";
+    public static final String REMOTE_MEP_ID = "remoteMepId";
+    public static final String PRIORITY = "priority";
+    public static final String COUNTERS_ENABLED = "countersEnabled";
+    public static final String THRESHOLDS = "thresholds";
+    public static final String AVAILABILITY_MEASUREMENT_INTERVAL_MINS =
+                                "availabilityMeasurementIntervalMins";
+    public static final String AVAILABILITY_NUMBER_CONSECUTIVE_FLR_MEASUREMENTS =
+                                "availabilityNumberConsecutiveFlrMeasurements";
+    public static final String AVAILABILITY_FLR_THRESHOLD_PCT =
+                                "availabilityFlrThresholdPct";
+    public static final String AVAILABILITY_NUMBER_CONSECUTIVE_INTERVALS =
+                                "availabilityNumberConsecutiveIntervals";
+    public static final String AVAILABILITY_NUMBER_CONSECUTIVE_HIGH_FLR =
+                                "availabilityNumberConsecutiveHighFlr";
+    public static final String FRAME_SIZE = "frameSize";
+    public static final String MESSAGE_PERIOD_MS = "messagePeriodMs";
+    public static final String MEASUREMENT_INTERVAL_MINS =
+                                "measurementIntervalMins";
+    public static final String ALIGN_MEASUREMENT_INTERVALS =
+                                "alignMeasurementIntervals";
+    public static final String ALIGN_MEASUREMENT_OFFSET_MINS =
+                                "alignMeasurementOffsetMins";
+    public static final String START_TIME = "startTime";
+    public static final String STOP_TIME = "stopTime";
+
+    @Override
+    public LossMeasurementCreate decode(ObjectNode json,
+            CodecContext context) {
+
+        if (json == null || !json.isObject()) {
+            return null;
+        }
+
+        JsonNode lmNode = json.get(LM);
+        Version version = Version.Y17312011;
+        if (lmNode.get(VERSION) != null) {
+            version = Version.valueOf(lmNode.get(VERSION).asText());
+        }
+        LmType lmCfgType = LmType.LMLMM;
+        if (lmNode.get(LM_CFG_TYPE) != null) {
+            lmCfgType = LmType.valueOf(lmNode.get(LM_CFG_TYPE).asText(LMLMM));
+        }
+        MepId remoteMepId = MepId.valueOf(
+                nullIsIllegal(lmNode.get(REMOTE_MEP_ID), REMOTE_MEP_ID + " is required")
+                .shortValue());
+        Priority prio = Priority.valueOf(nullIsIllegal(lmNode.get(PRIORITY),
+                PRIORITY + " is required in the format 'PRIOn'").asText());
+
+        try {
+            LmCreateBuilder builder = DefaultLmCreate
+                    .builder(version, remoteMepId, prio, lmCfgType);
+
+            if (lmNode.get(COUNTERS_ENABLED) != null) {
+                context.codec(CounterOption.class)
+                    .decode((ArrayNode) (lmNode.get(COUNTERS_ENABLED)), context)
+                    .forEach(builder::addToCountersEnabled);
+            }
+
+            if (lmNode.get(THRESHOLDS) != null) {
+                context.codec(LossMeasurementThreshold.class)
+                        .decode((ArrayNode) (lmNode.get(THRESHOLDS)), context)
+                        .forEach(builder::addToLossMeasurementThreshold);
+            }
+
+            if (lmNode.get(AVAILABILITY_MEASUREMENT_INTERVAL_MINS) != null) {
+                builder = builder.availabilityMeasurementInterval(
+                        Duration.ofMinutes(lmNode.get(AVAILABILITY_MEASUREMENT_INTERVAL_MINS).asInt()));
+            }
+            if (lmNode.get(AVAILABILITY_NUMBER_CONSECUTIVE_FLR_MEASUREMENTS) != null) {
+                builder = builder.availabilityNumberConsecutiveFlrMeasurements(
+                        lmNode.get(AVAILABILITY_NUMBER_CONSECUTIVE_FLR_MEASUREMENTS).asInt());
+            }
+            if (lmNode.get(AVAILABILITY_FLR_THRESHOLD_PCT) != null) {
+                builder = builder.availabilityFlrThreshold(
+                        MilliPct.ofPercent((float) lmNode.get(AVAILABILITY_FLR_THRESHOLD_PCT).asDouble()));
+            }
+            if (lmNode.get(AVAILABILITY_NUMBER_CONSECUTIVE_INTERVALS) != null) {
+                builder = builder.availabilityNumberConsecutiveIntervals(
+                        (short) lmNode.get(AVAILABILITY_NUMBER_CONSECUTIVE_INTERVALS).asInt());
+            }
+            if (lmNode.get(AVAILABILITY_NUMBER_CONSECUTIVE_HIGH_FLR) != null) {
+                builder = builder.availabilityNumberConsecutiveHighFlr(
+                        (short) lmNode.get(AVAILABILITY_NUMBER_CONSECUTIVE_HIGH_FLR).asInt());
+            }
+            if (lmNode.get(FRAME_SIZE) != null) {
+                builder = (LmCreateBuilder) builder.frameSize(
+                        (short) lmNode.get(FRAME_SIZE).asInt());
+            }
+            if (lmNode.get(MESSAGE_PERIOD_MS) != null) {
+                builder = (LmCreateBuilder) builder.messagePeriod(Duration.ofMillis(
+                        lmNode.get(MESSAGE_PERIOD_MS).asInt()));
+            }
+            if (lmNode.get(MEASUREMENT_INTERVAL_MINS) != null) {
+                builder = (LmCreateBuilder) builder.measurementInterval(
+                        Duration.ofMinutes(
+                                lmNode.get(MEASUREMENT_INTERVAL_MINS).asInt()));
+            }
+            if (lmNode.get(ALIGN_MEASUREMENT_INTERVALS) != null) {
+                builder = (LmCreateBuilder) builder.alignMeasurementIntervals(
+                        lmNode.get(ALIGN_MEASUREMENT_INTERVALS).asBoolean());
+            }
+            if (lmNode.get(ALIGN_MEASUREMENT_OFFSET_MINS) != null) {
+                builder = (LmCreateBuilder) builder.alignMeasurementOffset(Duration.ofMinutes(
+                        lmNode.get(ALIGN_MEASUREMENT_OFFSET_MINS).asInt()));
+            }
+            if (lmNode.get(START_TIME) != null) {
+                builder = (LmCreateBuilder) builder.startTime(context.codec(StartTime.class)
+                        .decode((ObjectNode) lmNode.get(START_TIME), context));
+            }
+            if (lmNode.get(STOP_TIME) != null) {
+                builder = (LmCreateBuilder) builder.stopTime(context.codec(StopTime.class)
+                        .decode((ObjectNode) lmNode.get(STOP_TIME), context));
+            }
+
+
+            return builder.build();
+        } catch (SoamConfigException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    @Override
+    public ObjectNode encode(LossMeasurementCreate lm, CodecContext context) {
+        checkNotNull(lm, "LM cannot be null");
+        ObjectNode result = context.mapper().createObjectNode()
+                .put(LM_CFG_TYPE, lm.lmCfgType().name())
+                .put(VERSION, lm.version().name())
+                .put(REMOTE_MEP_ID, lm.remoteMepId().id())
+                .put(PRIORITY, lm.priority().name());
+
+        if (lm.countersEnabled() != null) {
+            result.set(COUNTERS_ENABLED, new LmCounterOptionCodec()
+                    .encode(lm.countersEnabled(), context));
+        }
+
+        if (lm.messagePeriod() != null) {
+            result.put(MESSAGE_PERIOD_MS, lm.messagePeriod().toMillis());
+        }
+        if (lm.frameSize() != null) {
+            result.put(FRAME_SIZE, lm.frameSize());
+        }
+
+
+        return result;
+    }
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/soam/web/LmEntryCodec.java b/apps/cfm/src/main/java/org/onosproject/soam/web/LmEntryCodec.java
new file mode 100644
index 0000000..251bcd9
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/soam/web/LmEntryCodec.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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 org.onosproject.soam.web;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.incubator.net.l2monitoring.soam.loss.LossMeasurementEntry;
+
+import java.util.Iterator;
+import java.util.Map.Entry;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Encode and decode to/from JSON to LossMeasurementEntry object.
+ */
+public class LmEntryCodec extends JsonCodec<LossMeasurementEntry> {
+
+    @Override
+    public ObjectNode encode(LossMeasurementEntry lm, CodecContext context) {
+        checkNotNull(lm, "LM cannot be null");
+        ObjectNode result = context.mapper().createObjectNode()
+                .put("lmId", lm.lmId().toString());
+
+        if (lm.measuredForwardFlr() != null) {
+            result.put("measuredForwardFlr", lm.measuredForwardFlr().percentValue());
+        }
+        if (lm.measuredBackwardFlr() != null) {
+            result.put("measuredBackwardFlr", lm.measuredBackwardFlr().percentValue());
+        }
+        if (lm.measuredAvailabilityForwardStatus() != null) {
+            result.put("measuredAvailabilityForwardStatus",
+                    lm.measuredAvailabilityForwardStatus().name());
+        }
+        if (lm.measuredAvailabilityBackwardStatus() != null) {
+            result.put("measuredAvailabilityBackwardStatus",
+                    lm.measuredAvailabilityBackwardStatus().name());
+        }
+        if (lm.measuredForwardLastTransitionTime() != null) {
+            result.put("measuredForwardLastTransitionTime",
+                    lm.measuredForwardLastTransitionTime().toString());
+        }
+        if (lm.measuredBackwardLastTransitionTime() != null) {
+            result.put("measuredBackwardLastTransitionTime",
+                    lm.measuredBackwardLastTransitionTime().toString());
+        }
+
+        ObjectNode lmAttrs = new LmCreateCodec().encode(lm, context);
+        Iterator<Entry<String, JsonNode>> elements = lmAttrs.fields();
+        while (elements.hasNext()) {
+            Entry<String, JsonNode> element = elements.next();
+            result.set(element.getKey(), element.getValue());
+        }
+
+        if (lm.measurementCurrent() != null) {
+            result.set("measurementCurrent", new LossMeasurementStatCurrentCodec()
+                    .encode(lm.measurementCurrent(), context));
+        }
+
+        if (lm.measurementHistories() != null) {
+            result.set("measurementHistories", new LossMeasurementStatHistoryCodec()
+                    .encode(lm.measurementHistories(), context));
+        }
+
+        if (lm.availabilityCurrent() != null) {
+            result.set("availabilityCurrent", new LossAvailabilityStatCurrentCodec()
+                    .encode(lm.availabilityCurrent(), context));
+        }
+
+        if (lm.availabilityHistories() != null) {
+            result.set("availabilityHistories", new LossAvailabilityStatHistoryCodec()
+                    .encode(lm.availabilityHistories(), context));
+        }
+
+        return result;
+    }
+
+    @Override
+    public ArrayNode encode(Iterable<LossMeasurementEntry> lmEntities, CodecContext context) {
+        ArrayNode an = context.mapper().createArrayNode();
+        lmEntities.forEach(dm -> an.add(encode(dm, context)));
+        return an;
+    }
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/soam/web/LmThresholdOptionCodec.java b/apps/cfm/src/main/java/org/onosproject/soam/web/LmThresholdOptionCodec.java
new file mode 100644
index 0000000..98c0b62
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/soam/web/LmThresholdOptionCodec.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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 org.onosproject.soam.web;
+
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.incubator.net.l2monitoring.soam.loss.LossMeasurementThreshold.ThresholdOption;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Encode and decode to/from JSON to ThresholdOption object.
+ */
+public class LmThresholdOptionCodec extends JsonCodec<ThresholdOption> {
+
+    @Override
+    public ArrayNode encode(Iterable<ThresholdOption> entities,
+            CodecContext context) {
+        ArrayNode an = context.mapper().createArrayNode();
+        entities.forEach(node -> an.add(node.name()));
+
+        return an;
+    }
+
+    @Override
+    public List<ThresholdOption> decode(ArrayNode json,
+            CodecContext context) {
+        if (json == null) {
+            return null;
+        }
+        List<ThresholdOption> moList = new ArrayList<>();
+        json.forEach(node -> moList.add(ThresholdOption.valueOf(node.asText())));
+        return moList;
+    }
+
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/soam/web/LossAvailabilityStatCodec.java b/apps/cfm/src/main/java/org/onosproject/soam/web/LossAvailabilityStatCodec.java
new file mode 100644
index 0000000..3124976
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/soam/web/LossAvailabilityStatCodec.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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 org.onosproject.soam.web;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.incubator.net.l2monitoring.soam.loss.LossAvailabilityStat;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Encode and decode to/from JSON to LossAvailabilityStat object.
+ */
+public class LossAvailabilityStatCodec extends JsonCodec<LossAvailabilityStat> {
+
+    @Override
+    public ObjectNode encode(LossAvailabilityStat laStat, CodecContext context) {
+        checkNotNull(laStat, "LA stat cannot be null");
+        ObjectNode result = context.mapper().createObjectNode()
+                .put("elapsedTime", laStat.elapsedTime().toString())
+                .put("suspectStatus", String.valueOf(laStat.suspectStatus()));
+
+        if (laStat.forwardHighLoss() != null) {
+            result = result.put("forwardHighLoss",
+                    laStat.forwardHighLoss().toString());
+        }
+        if (laStat.backwardHighLoss() != null) {
+            result = result.put("backwardHighLoss",
+                    laStat.backwardHighLoss().toString());
+        }
+        if (laStat.forwardConsecutiveHighLoss() != null) {
+            result = result.put("forwardConsecutiveHighLoss",
+                    laStat.forwardConsecutiveHighLoss().toString());
+        }
+        if (laStat.backwardConsecutiveHighLoss() != null) {
+            result = result.put("backwardConsecutiveHighLoss",
+                    laStat.backwardConsecutiveHighLoss().toString());
+        }
+        if (laStat.forwardAvailable() != null) {
+            result = result.put("forwardAvailable",
+                    laStat.forwardAvailable().toString());
+        }
+        if (laStat.backwardAvailable() != null) {
+            result = result.put("backwardAvailable",
+                    laStat.backwardAvailable().toString());
+        }
+        if (laStat.forwardUnavailable() != null) {
+            result = result.put("forwardUnavailable",
+                    laStat.forwardUnavailable().toString());
+        }
+        if (laStat.backwardUnavailable() != null) {
+            result = result.put("backwardUnavailable",
+                    laStat.backwardUnavailable().toString());
+        }
+        if (laStat.backwardMinFrameLossRatio() != null) {
+            result = result.put("backwardMinFrameLossRatio",
+                    laStat.backwardMinFrameLossRatio().toString());
+        }
+        if (laStat.backwardMaxFrameLossRatio() != null) {
+            result = result.put("backwardMaxFrameLossRatio",
+                    laStat.backwardMaxFrameLossRatio().toString());
+        }
+        if (laStat.backwardAverageFrameLossRatio() != null) {
+            result = result.put("backwardAverageFrameLossRatio",
+                    laStat.backwardAverageFrameLossRatio().toString());
+        }
+        if (laStat.forwardMinFrameLossRatio() != null) {
+            result = result.put("forwardMinFrameLossRatio",
+                    laStat.forwardMinFrameLossRatio().toString());
+        }
+        if (laStat.forwardMaxFrameLossRatio() != null) {
+            result = result.put("forwardMaxFrameLossRatio",
+                    laStat.forwardMaxFrameLossRatio().toString());
+        }
+        if (laStat.forwardAverageFrameLossRatio() != null) {
+            result = result.put("forwardAverageFrameLossRatio",
+                    laStat.forwardAverageFrameLossRatio().toString());
+        }
+
+        return result;
+    }
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/soam/web/LossAvailabilityStatCurrentCodec.java b/apps/cfm/src/main/java/org/onosproject/soam/web/LossAvailabilityStatCurrentCodec.java
new file mode 100644
index 0000000..a6dc5de
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/soam/web/LossAvailabilityStatCurrentCodec.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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 org.onosproject.soam.web;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.incubator.net.l2monitoring.soam.loss.LossAvailabilityStatCurrent;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Encode and decode to/from JSON to LossAvailabilityStatCurrent object.
+ */
+public class LossAvailabilityStatCurrentCodec extends JsonCodec<LossAvailabilityStatCurrent> {
+
+    @Override
+    public ObjectNode encode(LossAvailabilityStatCurrent laCurrent, CodecContext context) {
+        checkNotNull(laCurrent, "LA current cannot be null");
+        ObjectNode result = context.mapper().createObjectNode()
+                .put("startTime", laCurrent.startTime().toString())
+                .put("elapsedTime", laCurrent.elapsedTime().toString());
+
+        ObjectNode resultAbstract = new LossAvailabilityStatCodec().encode(laCurrent, context);
+        result.setAll(resultAbstract);
+
+        return result;
+    }
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/soam/web/LossAvailabilityStatHistoryCodec.java b/apps/cfm/src/main/java/org/onosproject/soam/web/LossAvailabilityStatHistoryCodec.java
new file mode 100644
index 0000000..54e5160
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/soam/web/LossAvailabilityStatHistoryCodec.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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 org.onosproject.soam.web;
+
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.incubator.net.l2monitoring.soam.loss.LossAvailabilityStatHistory;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Encode and decode to/from JSON to LossAvailabilityStatHistory object.
+ */
+public class LossAvailabilityStatHistoryCodec extends JsonCodec<LossAvailabilityStatHistory> {
+
+    @Override
+    public ObjectNode encode(LossAvailabilityStatHistory laHistory, CodecContext context) {
+        checkNotNull(laHistory, "LA history cannot be null");
+        ObjectNode result = context.mapper().createObjectNode()
+                .put("historyId", String.valueOf(laHistory.historyStatsId()))
+                .put("endTime", laHistory.endTime().toString());
+        ObjectNode resultAbstract = new LossAvailabilityStatCodec().encode(laHistory, context);
+        result.setAll(resultAbstract);
+        return result;
+    }
+
+    @Override
+    public ArrayNode encode(Iterable<LossAvailabilityStatHistory> historyEntities, CodecContext context) {
+        ArrayNode an = context.mapper().createArrayNode();
+        historyEntities.forEach(history -> an.add(encode(history, context)));
+        return an;
+    }
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/soam/web/LossMeasurementStatCodec.java b/apps/cfm/src/main/java/org/onosproject/soam/web/LossMeasurementStatCodec.java
new file mode 100644
index 0000000..eac21e6
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/soam/web/LossMeasurementStatCodec.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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 org.onosproject.soam.web;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.incubator.net.l2monitoring.soam.loss.LossMeasurementStat;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Encode and decode to/from JSON to LossMeasurementStat object.
+ */
+public class LossMeasurementStatCodec extends JsonCodec<LossMeasurementStat> {
+
+    @Override
+    public ObjectNode encode(LossMeasurementStat lmStat, CodecContext context) {
+        checkNotNull(lmStat, "LM stat cannot be null");
+        ObjectNode result = context.mapper().createObjectNode()
+                .put("elapsedTime", lmStat.elapsedTime().toString())
+                .put("suspectStatus", String.valueOf(lmStat.suspectStatus()));
+
+        if (lmStat.forwardTransmittedFrames() != null) {
+            result = result.put("forwardTransmittedFrames",
+                    lmStat.forwardTransmittedFrames().toString());
+        }
+        if (lmStat.forwardReceivedFrames() != null) {
+            result = result.put("forwardReceivedFrames",
+                    lmStat.forwardReceivedFrames().toString());
+        }
+        if (lmStat.forwardMinFrameLossRatio() != null) {
+            result = result.put("forwardMinFrameLossRatio",
+                    lmStat.forwardMinFrameLossRatio().toString());
+        }
+        if (lmStat.forwardMaxFrameLossRatio() != null) {
+            result = result.put("forwardMaxFrameLossRatio",
+                    lmStat.forwardMaxFrameLossRatio().toString());
+        }
+        if (lmStat.forwardAverageFrameLossRatio() != null) {
+            result = result.put("forwardAverageFrameLossRatio",
+                    lmStat.forwardAverageFrameLossRatio().toString());
+        }
+        if (lmStat.backwardTransmittedFrames() != null) {
+            result = result.put("backwardTransmittedFrames",
+                    lmStat.backwardTransmittedFrames().toString());
+        }
+        if (lmStat.backwardReceivedFrames() != null) {
+            result = result.put("backwardReceivedFrames",
+                    lmStat.backwardReceivedFrames().toString());
+        }
+        if (lmStat.backwardMinFrameLossRatio() != null) {
+            result = result.put("backwardMinFrameLossRatio",
+                    lmStat.backwardMinFrameLossRatio().toString());
+        }
+        if (lmStat.backwardMaxFrameLossRatio() != null) {
+            result = result.put("backwardMaxFrameLossRatio",
+                    lmStat.backwardMaxFrameLossRatio().toString());
+        }
+        if (lmStat.backwardAverageFrameLossRatio() != null) {
+            result = result.put("backwardAverageFrameLossRatio",
+                    lmStat.backwardAverageFrameLossRatio().toString());
+        }
+        if (lmStat.soamPdusSent() != null) {
+            result = result.put("soamPdusSent",
+                    lmStat.soamPdusSent().toString());
+        }
+        if (lmStat.soamPdusReceived() != null) {
+            result.put("soamPdusReceived",
+                    lmStat.soamPdusReceived().toString());
+        }
+
+        return result;
+    }
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/soam/web/LossMeasurementStatCurrentCodec.java b/apps/cfm/src/main/java/org/onosproject/soam/web/LossMeasurementStatCurrentCodec.java
new file mode 100644
index 0000000..d525493
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/soam/web/LossMeasurementStatCurrentCodec.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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 org.onosproject.soam.web;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.incubator.net.l2monitoring.soam.loss.LossMeasurementStatCurrent;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Encode and decode to/from JSON to LossMeasurementStatCurrent object.
+ */
+public class LossMeasurementStatCurrentCodec extends JsonCodec<LossMeasurementStatCurrent> {
+
+    @Override
+    public ObjectNode encode(LossMeasurementStatCurrent lmCurrent, CodecContext context) {
+        checkNotNull(lmCurrent, "LM current cannot be null");
+        ObjectNode result = context.mapper().createObjectNode()
+                .put("startTime", lmCurrent.startTime().toString())
+                .put("elapsedTime", lmCurrent.elapsedTime().toString());
+
+        ObjectNode resultAbstract = new LossMeasurementStatCodec().encode(lmCurrent, context);
+        result.setAll(resultAbstract);
+
+        return result;
+    }
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/soam/web/LossMeasurementStatHistoryCodec.java b/apps/cfm/src/main/java/org/onosproject/soam/web/LossMeasurementStatHistoryCodec.java
new file mode 100644
index 0000000..e4ec282
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/soam/web/LossMeasurementStatHistoryCodec.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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 org.onosproject.soam.web;
+
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.incubator.net.l2monitoring.soam.loss.LossMeasurementStatHistory;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Encode and decode to/from JSON to LossMeasurementStatHistory object.
+ */
+public class LossMeasurementStatHistoryCodec extends JsonCodec<LossMeasurementStatHistory> {
+
+    @Override
+    public ObjectNode encode(LossMeasurementStatHistory lmHistory, CodecContext context) {
+        checkNotNull(lmHistory, "LM history cannot be null");
+        ObjectNode result = context.mapper().createObjectNode()
+                .put("historyId", String.valueOf(lmHistory.historyStatsId()))
+                .put("endTime", lmHistory.endTime().toString());
+        ObjectNode resultAbstract = new LossMeasurementStatCodec().encode(lmHistory, context);
+        result.setAll(resultAbstract);
+        return result;
+    }
+
+    @Override
+    public ArrayNode encode(Iterable<LossMeasurementStatHistory> historyEntities, CodecContext context) {
+        ArrayNode an = context.mapper().createArrayNode();
+        historyEntities.forEach(history -> an.add(encode(history, context)));
+        return an;
+    }
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/soam/web/LossMeasurementThresholdCodec.java b/apps/cfm/src/main/java/org/onosproject/soam/web/LossMeasurementThresholdCodec.java
new file mode 100644
index 0000000..3a17f94
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/soam/web/LossMeasurementThresholdCodec.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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 org.onosproject.soam.web;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.incubator.net.l2monitoring.soam.MilliPct;
+import org.onosproject.incubator.net.l2monitoring.soam.SoamId;
+import org.onosproject.incubator.net.l2monitoring.soam.loss.LossMeasurementThreshold;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onlab.util.Tools.nullIsIllegal;
+import static org.onosproject.incubator.net.l2monitoring.soam.loss.DefaultLmThreshold.*;
+
+/**
+ * Encode and decode to/from JSON to LossMeasurementThreshold object.
+ */
+public class LossMeasurementThresholdCodec extends JsonCodec<LossMeasurementThreshold> {
+    static final String MEASUREDFLRFORWARD = "measuredFlrForward";
+    static final String MAXFLRFORWARD = "maxFlrForward";
+    static final String AVERAGEFLRFORWARD = "averageFlrForward";
+    static final String MEASUREDFLRBACKWARD = "measuredFlrBackward";
+    static final String MAXFLRBACKWARD = "maxFlrBackward";
+    static final String AVERAGEFLRBACKWARD = "averageFlrBackward";
+    static final String FORWARDHIGHLOSS = "forwardHighLoss";
+    static final String FORWARDCONSECUTIVEHIGHLOSS = "forwardConsecutiveHighLoss";
+    static final String BACKWARDHIGHLOSS = "backwardHighLoss";
+    static final String BACKWARDCONSECUTIVEHIGHLOSS = "backwardConsecutiveHighLoss";
+    static final String FORWARDUNAVAILABLECOUNT = "forwardUnavailableCount";
+    static final String FORWARDAVAILABLERATIO = "forwardAvailableRatio";
+    static final String BACKWARDUNAVAILABLECOUNT = "backwardUnavailableCount";
+    static final String BACKWARDAVAILABLERATIO = "backwardAvailableRatio";
+    static final String THRESHOLDOPTIONS = "thresholdOptions";
+
+    @Override
+    public ObjectNode encode(LossMeasurementThreshold lmt, CodecContext context) {
+        checkNotNull(lmt, "LM thresholds cannot be null");
+        ObjectNode result = context.mapper().createObjectNode()
+                .put("id", lmt.thresholdId().value());
+
+        if (lmt.thresholds() != null) {
+            result.set(THRESHOLDOPTIONS, new LmThresholdOptionCodec()
+                    .encode(lmt.thresholds(), context));
+        }
+
+        if (lmt.measuredFlrForward() != null) {
+            result.put(MEASUREDFLRFORWARD, lmt.measuredFlrForward().percentValue());
+        }
+        if (lmt.maxFlrForward() != null) {
+            result.put(MAXFLRFORWARD, lmt.maxFlrForward().percentValue());
+        }
+        if (lmt.averageFlrForward() != null) {
+            result.put(AVERAGEFLRFORWARD, lmt.averageFlrForward().percentValue());
+        }
+        if (lmt.measuredFlrBackward() != null) {
+            result.put(MEASUREDFLRBACKWARD, lmt.measuredFlrBackward().percentValue());
+        }
+        if (lmt.maxFlrBackward() != null) {
+            result.put(MAXFLRBACKWARD, lmt.maxFlrBackward().percentValue());
+        }
+        if (lmt.averageFlrBackward() != null) {
+            result.put(AVERAGEFLRBACKWARD, lmt.averageFlrBackward().percentValue());
+        }
+        if (lmt.forwardHighLoss() != null) {
+            result.put(FORWARDHIGHLOSS, lmt.forwardHighLoss().longValue());
+        }
+        if (lmt.forwardConsecutiveHighLoss() != null) {
+            result.put(FORWARDCONSECUTIVEHIGHLOSS, lmt.measuredFlrForward().longValue());
+        }
+        if (lmt.backwardHighLoss() != null) {
+            result.put(BACKWARDHIGHLOSS, lmt.backwardHighLoss().longValue());
+        }
+        if (lmt.backwardConsecutiveHighLoss() != null) {
+            result.put(BACKWARDCONSECUTIVEHIGHLOSS, lmt.backwardConsecutiveHighLoss().longValue());
+        }
+        if (lmt.forwardUnavailableCount() != null) {
+            result.put(FORWARDUNAVAILABLECOUNT, lmt.forwardUnavailableCount().longValue());
+        }
+        if (lmt.forwardAvailableRatio() != null) {
+            result.put(FORWARDAVAILABLERATIO, lmt.forwardAvailableRatio().percentValue());
+        }
+        if (lmt.backwardUnavailableCount() != null) {
+            result.put(BACKWARDUNAVAILABLECOUNT, lmt.backwardUnavailableCount().longValue());
+        }
+        if (lmt.backwardAvailableRatio() != null) {
+            result.put(BACKWARDAVAILABLERATIO, lmt.backwardAvailableRatio().percentValue());
+        }
+
+        return result;
+    }
+
+    @Override
+    public List<LossMeasurementThreshold> decode(ArrayNode json, CodecContext context) {
+        if (json == null) {
+            return null;
+        }
+        List<LossMeasurementThreshold> thrList = new ArrayList<>();
+        json.forEach(node -> thrList.add(decode((ObjectNode) node, context)));
+        return thrList;
+    }
+
+    @Override
+    public LossMeasurementThreshold decode(ObjectNode json, CodecContext context) {
+        if (json == null || !json.isObject()) {
+            return null;
+        }
+
+        JsonNode thrNode = json.get("threshold");
+
+        SoamId thresholdId = SoamId.valueOf(nullIsIllegal(thrNode.get("id"),
+                "thresholdId must not be null").asInt());
+        LossMeasurementThreshold.LmThresholdBuilder builder = builder(thresholdId);
+
+        if (thrNode.get("thresholds") != null) {
+            context.codec(ThresholdOption.class)
+                    .decode((ArrayNode) (thrNode.get("thresholds")), context)
+                    .forEach(builder::addToThreshold);
+        }
+
+        if (thrNode.get(MEASUREDFLRFORWARD) != null) {
+            builder.measuredFlrForward(MilliPct.ofPercent(
+                    (float) thrNode.get(MEASUREDFLRFORWARD).asDouble()));
+        }
+        if (thrNode.get(MAXFLRFORWARD) != null) {
+            builder.maxFlrForward(MilliPct.ofPercent(
+                    (float) thrNode.get(MAXFLRFORWARD).asDouble()));
+        }
+        if (thrNode.get(AVERAGEFLRFORWARD) != null) {
+            builder.averageFlrForward(MilliPct.ofPercent(
+                    (float) thrNode.get(AVERAGEFLRFORWARD).asDouble()));
+        }
+        if (thrNode.get(MEASUREDFLRBACKWARD) != null) {
+            builder.measuredFlrBackward(MilliPct.ofPercent(
+                    (float) thrNode.get(MEASUREDFLRBACKWARD).asDouble()));
+        }
+        if (thrNode.get(MAXFLRBACKWARD) != null) {
+            builder.maxFlrBackward(MilliPct.ofPercent(
+                    (float) thrNode.get(MAXFLRBACKWARD).asDouble()));
+        }
+        if (thrNode.get(AVERAGEFLRBACKWARD) != null) {
+            builder.averageFlrBackward(MilliPct.ofPercent(
+                    (float) thrNode.get(AVERAGEFLRBACKWARD).asDouble()));
+        }
+        if (thrNode.get(FORWARDHIGHLOSS) != null) {
+            builder.forwardHighLoss(thrNode.get(FORWARDHIGHLOSS).asLong());
+        }
+        if (thrNode.get(FORWARDCONSECUTIVEHIGHLOSS) != null) {
+            builder.forwardConsecutiveHighLoss(thrNode.get(FORWARDCONSECUTIVEHIGHLOSS).asLong());
+        }
+        if (thrNode.get(BACKWARDHIGHLOSS) != null) {
+            builder.backwardHighLoss(thrNode.get(BACKWARDHIGHLOSS).asLong());
+        }
+        if (thrNode.get(BACKWARDCONSECUTIVEHIGHLOSS) != null) {
+            builder.backwardConsecutiveHighLoss(thrNode.get(BACKWARDCONSECUTIVEHIGHLOSS).asLong());
+        }
+        if (thrNode.get(FORWARDUNAVAILABLECOUNT) != null) {
+            builder.forwardUnavailableCount(thrNode.get(FORWARDUNAVAILABLECOUNT).asLong());
+        }
+        if (thrNode.get(FORWARDAVAILABLERATIO) != null) {
+            builder.forwardAvailableRatio(MilliPct.ofPercent(
+                    (float) thrNode.get(FORWARDAVAILABLERATIO).asDouble()));
+        }
+        if (thrNode.get(BACKWARDUNAVAILABLECOUNT) != null) {
+            builder.backwardUnavailableCount(thrNode.get(BACKWARDUNAVAILABLECOUNT).asLong());
+        }
+        if (thrNode.get(BACKWARDAVAILABLERATIO) != null) {
+            builder.backwardAvailableRatio(MilliPct.ofPercent(
+                    (float) thrNode.get(BACKWARDAVAILABLERATIO).asDouble()));
+        }
+
+        return builder.build();
+    }
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/soam/web/StartTimeCodec.java b/apps/cfm/src/main/java/org/onosproject/soam/web/StartTimeCodec.java
new file mode 100644
index 0000000..f84bffe
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/soam/web/StartTimeCodec.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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 org.onosproject.soam.web;
+
+import java.time.Duration;
+import java.time.OffsetDateTime;
+
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.incubator.net.l2monitoring.soam.StartTime;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+/**
+ * Encode and decode to/from JSON to StartTime object.
+ */
+public class StartTimeCodec extends JsonCodec<StartTime> {
+
+    @Override
+    public StartTime decode(ObjectNode json, CodecContext context) {
+        if (json == null || !json.isObject()) {
+            return null;
+        }
+
+        if (json.get("immediate") != null) {
+            return StartTime.immediate();
+        } else if (json.get("absolute") != null) {
+            if (json.get("absolute").get("start-time") != null) {
+                return StartTime.absolute(OffsetDateTime
+                        .parse(json.get("absolute").get("start-time").asText())
+                        .toInstant());
+            }
+            throw new IllegalArgumentException("StartTime absolute must contain "
+                    + "a start-time in date-time format with offset");
+        } else if (json.get("relative") != null) {
+            if (json.get("relative").get("start-time") != null) {
+                return StartTime.relative(Duration.parse(json.get("relative").get("start-time").asText()));
+            }
+            throw new IllegalArgumentException("StartTime relative must contain a start-time duration");
+        } else {
+            throw new IllegalArgumentException("StartTime must be either immediate, absolute or relative");
+        }
+    }
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/soam/web/StopTimeCodec.java b/apps/cfm/src/main/java/org/onosproject/soam/web/StopTimeCodec.java
new file mode 100644
index 0000000..30a1538
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/soam/web/StopTimeCodec.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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 org.onosproject.soam.web;
+
+import java.time.Duration;
+import java.time.OffsetDateTime;
+
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.incubator.net.l2monitoring.soam.StopTime;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+/**
+ * Encode and decode to/from JSON to StopTime object.
+ */
+public class StopTimeCodec extends JsonCodec<StopTime> {
+
+    @Override
+    public StopTime decode(ObjectNode json, CodecContext context) {
+        if (json == null || !json.isObject()) {
+            return null;
+        }
+
+        if (json.get("none") != null) {
+            return StopTime.none();
+        } else if (json.get("absolute") != null) {
+            if (json.get("absolute").get("stop-time") != null) {
+                return StopTime.absolute(OffsetDateTime
+                        .parse(json.get("absolute").get("stop-time").asText())
+                        .toInstant());
+            }
+            throw new IllegalArgumentException("StopTime absolute must contain "
+                    + "a stop-time in date-time format with offset");
+        } else if (json.get("relative") != null) {
+            if (json.get("relative").get("stop-time") != null) {
+                return StopTime.relative(Duration.parse(json.get("relative").get("stop-time").asText()));
+            }
+            throw new IllegalArgumentException("StopTime relative must contain a stop-time duration");
+        } else {
+            throw new IllegalArgumentException("StopTime must be either none, absolute or relative");
+        }
+    }
+}
diff --git a/apps/cfm/src/main/java/org/onosproject/soam/web/package-info.java b/apps/cfm/src/main/java/org/onosproject/soam/web/package-info.java
new file mode 100644
index 0000000..473be24
--- /dev/null
+++ b/apps/cfm/src/main/java/org/onosproject/soam/web/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.
+ */
+/**
+ * Codecs for converting SOAM objects to and from JSON.
+ */
+package org.onosproject.soam.web;
\ No newline at end of file