Do not allow TableSchema columns to be directly set
[ovsdb.git] / library / impl / src / main / java / org / opendaylight / ovsdb / lib / schema / TableSchema.java
1 /*
2  * Copyright (c) 2014, 2015 EBay Software Foundation and others. All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.ovsdb.lib.schema;
9
10 import com.fasterxml.jackson.databind.JsonNode;
11 import com.fasterxml.jackson.databind.node.ObjectNode;
12 import java.lang.reflect.Constructor;
13 import java.lang.reflect.InvocationTargetException;
14 import java.util.ArrayList;
15 import java.util.HashMap;
16 import java.util.Iterator;
17 import java.util.List;
18 import java.util.Map;
19 import java.util.Map.Entry;
20 import java.util.Set;
21 import org.opendaylight.ovsdb.lib.message.TableUpdate;
22 import org.opendaylight.ovsdb.lib.notation.Column;
23 import org.opendaylight.ovsdb.lib.notation.Row;
24 import org.opendaylight.ovsdb.lib.notation.UUID;
25 import org.opendaylight.ovsdb.lib.operations.Insert;
26 import org.opendaylight.ovsdb.lib.schema.BaseType.UuidBaseType;
27
28 public abstract class TableSchema<E extends TableSchema<E>> {
29
30     private final String name;
31     private final Map<String, ColumnSchema> columns;
32
33     protected TableSchema(final String name) {
34         this.name = name;
35         this.columns = new HashMap<>();
36     }
37
38     protected TableSchema(final String name, final Map<String, ColumnSchema> columns) {
39         this.name = name;
40         this.columns = columns;
41     }
42
43     public Set<String> getColumns() {
44         return this.columns.keySet();
45     }
46
47     public Map<String, ColumnSchema> getColumnSchemas() {
48         return columns;
49     }
50
51     public boolean hasColumn(final String column) {
52         return this.getColumns().contains(column);
53     }
54
55     public ColumnType getColumnType(final String column) {
56         return this.columns.get(column).getType();
57     }
58
59     public <E extends TableSchema<E>> E as(final Class<E> clazz) {
60         try {
61             Constructor<E> instance = clazz.getConstructor(TableSchema.class);
62             return instance.newInstance(this);
63         } catch (InstantiationException | IllegalAccessException
64                 | InvocationTargetException | NoSuchMethodException e) {
65             throw new RuntimeException("exception constructing instance of clazz " + clazz, e);
66         }
67     }
68
69     public Insert<E> insert() {
70         return new Insert<>(this);
71     }
72
73     public <D> ColumnSchema<E, Set<D>> multiValuedColumn(final String column, final Class<D> type) {
74         //todo exception handling
75
76         ColumnSchema<E, Set<D>> columnSchema = columns.get(column);
77         columnSchema.validateType(type);
78         return columnSchema;
79     }
80
81     public <K,V> ColumnSchema<E, Map<K,V>> multiValuedColumn(final String column, final Class<K> keyType,
82             final Class<V> valueType) {
83         //todo exception handling
84
85         ColumnSchema<E, Map<K, V>> columnSchema = columns.get(column);
86         columnSchema.validateType(valueType);
87         return columnSchema;
88     }
89
90     public <D> ColumnSchema<E, D> column(final String column, final Class<D> type) {
91         //todo exception handling
92
93         ColumnSchema<E, D> columnSchema = columns.get(column);
94         if (columnSchema != null) {
95             columnSchema.validateType(type);
96         }
97         return columnSchema;
98     }
99
100     public ColumnSchema column(final String column) {
101         return this.columns.get(column);
102     }
103
104     public String getName() {
105         return name;
106     }
107
108     public TableUpdate<E> updatesFromJson(final JsonNode value) {
109         TableUpdate<E> tableUpdate = new TableUpdate<>();
110         Iterator<Entry<String, JsonNode>> fields = value.fields();
111         while (fields.hasNext()) {
112             Map.Entry<String, JsonNode> idOldNew = fields.next();
113             String uuid = idOldNew.getKey();
114
115             ObjectNode newObjectNode = (ObjectNode) idOldNew.getValue().get("new");
116             ObjectNode oldObjectNode = (ObjectNode) idOldNew.getValue().get("old");
117
118             Row<E> newRow = newObjectNode != null ? createRow(newObjectNode) : null;
119             Row<E> oldRow = oldObjectNode != null ? createRow(oldObjectNode) : null;
120
121             tableUpdate.addRow(new UUID(uuid), oldRow, newRow);
122         }
123         return tableUpdate;
124     }
125
126     public Row<E> createRow(final ObjectNode rowNode) {
127         List<Column<E, ?>> newColumns = new ArrayList<>();
128         for (Iterator<Map.Entry<String, JsonNode>> iter = rowNode.fields(); iter.hasNext();) {
129             Map.Entry<String, JsonNode> next = iter.next();
130             ColumnSchema<E, Object> schema = column(next.getKey(), Object.class);
131             /*
132              * Ideally the ColumnSchema shouldn't be null at this stage. But there can be cases in which
133              * the OVSDB manager Schema implementation might decide to include some "hidden" columns that
134              * are NOT reported in getSchema, but decide to report it in unfiltered monitor.
135              * Hence adding some safety checks around that.
136              */
137             if (schema != null) {
138                 Object value = schema.valueFromJson(next.getValue());
139                 newColumns.add(new Column<>(schema, value));
140             }
141         }
142         return new Row<>(this, newColumns);
143     }
144
145     public List<Row<E>> createRows(final JsonNode rowsNode) {
146         List<Row<E>> rows = new ArrayList<>();
147         for (JsonNode rowNode : rowsNode.get("rows")) {
148             rows.add(createRow((ObjectNode)rowNode));
149         }
150
151         return rows;
152     }
153
154     /*
155      * RFC 7047 Section 3.2 specifies 2 internally generated columns in each table
156      * namely _uuid and _version which are not exposed in get_schema call.
157      * Since these 2 columns are extremely useful for Mutate, update and select operations,
158      * the ColumnSchema for these 2 columns are manually populated.
159      *
160      * It is to be noted that these 2 columns are specified as part of the RFC7047 and not
161      * a specific Schema implementation detail & hence adding it by default in the Library
162      * for better application experience using the library.
163      */
164     public void populateInternallyGeneratedColumns() {
165         columns.put("_uuid", new ColumnSchema("_uuid", new AtomicColumnType(new UuidBaseType())));
166         columns.put("_version", new ColumnSchema("_version", new AtomicColumnType(new UuidBaseType())));
167     }
168 }