2 * Copyright © 2014, 2017 Red Hat, Inc. and others. All rights reserved.
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
9 package org.opendaylight.ovsdb.lib.schema.typed;
11 import com.google.common.annotations.VisibleForTesting;
12 import com.google.common.base.Objects;
13 import com.google.common.base.Preconditions;
14 import com.google.common.collect.Range;
15 import com.google.common.reflect.Reflection;
16 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
17 import java.lang.reflect.InvocationHandler;
18 import java.lang.reflect.Method;
19 import java.util.HashMap;
20 import java.util.Locale;
22 import org.opendaylight.ovsdb.lib.error.ColumnSchemaNotFoundException;
23 import org.opendaylight.ovsdb.lib.error.SchemaVersionMismatchException;
24 import org.opendaylight.ovsdb.lib.error.TableSchemaNotFoundException;
25 import org.opendaylight.ovsdb.lib.error.TyperException;
26 import org.opendaylight.ovsdb.lib.error.UnsupportedMethodException;
27 import org.opendaylight.ovsdb.lib.message.TableUpdate;
28 import org.opendaylight.ovsdb.lib.message.TableUpdates;
29 import org.opendaylight.ovsdb.lib.notation.Column;
30 import org.opendaylight.ovsdb.lib.notation.Row;
31 import org.opendaylight.ovsdb.lib.notation.UUID;
32 import org.opendaylight.ovsdb.lib.notation.Version;
33 import org.opendaylight.ovsdb.lib.schema.ColumnSchema;
34 import org.opendaylight.ovsdb.lib.schema.DatabaseSchema;
35 import org.opendaylight.ovsdb.lib.schema.GenericTableSchema;
36 import org.opendaylight.ovsdb.lib.schema.TableSchema;
39 * Utility methods for typed OVSDB schema data.
41 public final class TyperUtils {
43 private static final String GET_STARTS_WITH = "get";
44 private static final String SET_STARTS_WITH = "set";
45 private static final String GETCOLUMN_ENDS_WITH = "Column";
46 private static final String GETROW_ENDS_WITH = "Row";
48 private TyperUtils() {
49 // Prevent instantiating a utility class
53 * Retrieve the table schema for the given table in the given database schema.
55 * @param dbSchema The database schema.
56 * @param klazz The class whose table schema should be retrieved. Classes are matched in the database schema either
57 * using their {@link TypedTable} annotation, if they have one, or by name.
58 * @return the table schema.
60 public static GenericTableSchema getTableSchema(final DatabaseSchema dbSchema, final Class<?> klazz) {
61 return dbSchema.table(TypedReflections.getTableName(klazz), GenericTableSchema.class);
64 public static ColumnSchema<GenericTableSchema, Object> getColumnSchema(final GenericTableSchema tableSchema,
65 final String columnName, final Class<Object> metaClass) {
66 return tableSchema.column(columnName, metaClass);
69 @SuppressFBWarnings(value = "UPM_UNCALLED_PRIVATE_METHOD",
70 justification = "https://github.com/spotbugs/spotbugs/issues/811")
71 private static String getColumnName(final Method method) {
72 TypedColumn typedColumn = method.getAnnotation(TypedColumn.class);
73 if (typedColumn != null) {
74 return typedColumn.name();
78 * Attempting to get the column name by parsing the method name with a following convention :
79 * 1. GETDATA : get<ColumnName>
80 * 2. SETDATA : set<ColumnName>
81 * 3. GETCOLUMN : get<ColumnName>Column
82 * where <ColumnName> is the name of the column that we are interested in.
84 int index = GET_STARTS_WITH.length();
85 if (isGetData(method) || isSetData(method)) {
86 return method.getName().substring(index, method.getName().length()).toLowerCase(Locale.ROOT);
87 } else if (isGetColumn(method)) {
88 return method.getName().substring(index, method.getName().indexOf(GETCOLUMN_ENDS_WITH,
89 index)).toLowerCase(Locale.ROOT);
95 @SuppressFBWarnings(value = "UPM_UNCALLED_PRIVATE_METHOD",
96 justification = "https://github.com/spotbugs/spotbugs/issues/811")
97 private static boolean isGetTableSchema(final Method method) {
98 TypedColumn typedColumn = method.getAnnotation(TypedColumn.class);
99 return typedColumn != null && typedColumn.method().equals(MethodType.GETTABLESCHEMA);
102 @SuppressFBWarnings(value = "UPM_UNCALLED_PRIVATE_METHOD",
103 justification = "https://github.com/spotbugs/spotbugs/issues/811")
104 private static boolean isGetRow(final Method method) {
105 TypedColumn typedColumn = method.getAnnotation(TypedColumn.class);
106 if (typedColumn != null) {
107 return typedColumn.method().equals(MethodType.GETROW);
110 return method.getName().startsWith(GET_STARTS_WITH) && method.getName().endsWith(GETROW_ENDS_WITH);
113 private static boolean isGetColumn(final Method method) {
114 TypedColumn typedColumn = method.getAnnotation(TypedColumn.class);
115 if (typedColumn != null) {
116 return typedColumn.method().equals(MethodType.GETCOLUMN);
119 return method.getName().startsWith(GET_STARTS_WITH) && method.getName().endsWith(GETCOLUMN_ENDS_WITH);
122 private static boolean isGetData(final Method method) {
123 TypedColumn typedColumn = method.getAnnotation(TypedColumn.class);
124 if (typedColumn != null) {
125 return typedColumn.method().equals(MethodType.GETDATA);
128 return method.getName().startsWith(GET_STARTS_WITH) && !method.getName().endsWith(GETCOLUMN_ENDS_WITH);
131 private static boolean isSetData(final Method method) {
132 TypedColumn typedColumn = method.getAnnotation(TypedColumn.class);
133 if (typedColumn != null) {
134 return typedColumn.method().equals(MethodType.SETDATA);
137 return method.getName().startsWith(SET_STARTS_WITH);
141 * Method that checks validity of the parameter passed to getTypedRowWrapper.
142 * This method checks for a valid Database Schema matching the expected Database for a given table
143 * and checks for the presence of the Table in Database Schema.
145 * @param dbSchema DatabaseSchema as learnt from a OVSDB connection
146 * @param klazz Typed Class that represents a Table
147 * @return true if valid, false otherwise
149 private static <T> boolean isValid(final DatabaseSchema dbSchema, final Class<T> klazz) {
150 if (dbSchema == null) {
154 final String dbName = TypedReflections.getTableDatabase(klazz);
155 if (dbName != null && !dbSchema.getName().equalsIgnoreCase(dbName)) {
159 checkTableSchemaVersion(dbSchema, klazz);
164 @SuppressFBWarnings(value = "UPM_UNCALLED_PRIVATE_METHOD",
165 justification = "https://github.com/spotbugs/spotbugs/issues/811")
166 private static void checkColumnSchemaVersion(final DatabaseSchema dbSchema, final Method method) {
167 checkVersion(dbSchema.getVersion(), TypedReflections.getColumnVersionRange(method));
170 private static <T> void checkTableSchemaVersion(final DatabaseSchema dbSchema, final Class<T> klazz) {
171 checkVersion(dbSchema.getVersion(), TypedReflections.getTableVersionRange(klazz));
175 static void checkVersion(final Version schemaVersion, final Range<Version> range) {
176 if (!range.contains(schemaVersion)) {
177 throw new SchemaVersionMismatchException(schemaVersion,
178 range.hasLowerBound() ? range.lowerEndpoint() : Version.NULL,
179 range.hasUpperBound() ? range.upperEndpoint() : Version.NULL);
184 * Returns a Typed Proxy implementation for the klazz passed as a parameter.
185 * Per design choice, the Typed Proxy implementation is just a Wrapper on top of the actual
186 * Row which is untyped.
188 * <p>Being just a wrapper, it is state-less and more of a convenience functionality to
189 * provide a type-safe infrastructure for the applications to built on top of.
190 * And this Typed infra is completely optional.
192 * <p>It is the applications responsibility to pass on the raw Row parameter and this method will
193 * return the appropriate Proxy wrapper for the passed klazz Type.
194 * The raw Row parameter may be null if the caller is interested in just the ColumnSchema.
195 * But that is not a very common use-case.
197 * @param dbSchema DatabaseSchema as learnt from a OVSDB connection
198 * @param klazz Typed Class that represents a Table
200 public static <T> T getTypedRowWrapper(final DatabaseSchema dbSchema, final Class<T> klazz) {
201 return getTypedRowWrapper(dbSchema, klazz, new Row<>());
205 * Returns a Typed Proxy implementation for the klazz passed as a parameter.
206 * Per design choice, the Typed Proxy implementation is just a Wrapper on top of the actual
207 * Row which is untyped.
209 * <p>Being just a wrapper, it is state-less and more of a convenience functionality
210 * to provide a type-safe infrastructure for the applications to built on top of.
211 * And this Typed infra is completely optional.
213 * <p>It is the applications responsibility to pass on the raw Row parameter and this method
214 * will return the appropriate Proxy wrapper for the passed klazz Type.
215 * The raw Row parameter may be null if the caller is interested in just the
216 * ColumnSchema. But that is not a very common use-case.
218 * @param dbSchema DatabaseSchema as learnt from a OVSDB connection
219 * @param klazz Typed Class that represents a Table
220 * @param row The actual Row that the wrapper is operating on. It can be null if the caller
221 * is just interested in getting ColumnSchema.
223 public static <T> T getTypedRowWrapper(final DatabaseSchema dbSchema, final Class<T> klazz,
224 final Row<GenericTableSchema> row) {
225 if (!isValid(dbSchema, klazz)) {
229 row.setTableSchema(getTableSchema(dbSchema, klazz));
231 return Reflection.newProxy(klazz, new InvocationHandler() {
232 private Object processGetData(final Method method) {
233 String columnName = getColumnName(method);
234 checkColumnSchemaVersion(dbSchema, method);
235 if (columnName == null) {
236 throw new TyperException("Error processing Getter : " + method.getName());
238 GenericTableSchema tableSchema = getTableSchema(dbSchema, klazz);
239 if (tableSchema == null) {
240 String message = TableSchemaNotFoundException.createMessage(TypedReflections.getTableName(klazz),
242 throw new TableSchemaNotFoundException(message);
244 ColumnSchema<GenericTableSchema, Object> columnSchema =
245 getColumnSchema(tableSchema, columnName, (Class<Object>) method.getReturnType());
246 if (columnSchema == null) {
247 String message = ColumnSchemaNotFoundException.createMessage(columnName, tableSchema.getName());
248 throw new ColumnSchemaNotFoundException(message);
250 if (row == null || row.getColumn(columnSchema) == null) {
253 return row.getColumn(columnSchema).getData();
256 private Object processGetRow() {
260 private Object processGetColumn(final Method method) {
261 String columnName = getColumnName(method);
262 checkColumnSchemaVersion(dbSchema, method);
263 if (columnName == null) {
264 throw new TyperException("Error processing GetColumn : " + method.getName());
266 GenericTableSchema tableSchema = getTableSchema(dbSchema, klazz);
267 if (tableSchema == null) {
268 String message = TableSchemaNotFoundException.createMessage(TypedReflections.getTableName(klazz),
270 throw new TableSchemaNotFoundException(message);
272 ColumnSchema<GenericTableSchema, Object> columnSchema =
273 getColumnSchema(tableSchema, columnName, (Class<Object>) method.getReturnType());
274 if (columnSchema == null) {
275 String message = ColumnSchemaNotFoundException.createMessage(columnName, tableSchema.getName());
276 throw new ColumnSchemaNotFoundException(message);
278 // When the row is null, that might indicate that the user maybe interested
279 // only in the ColumnSchema and not on the Data.
281 return new Column<>(columnSchema, null);
283 return row.getColumn(columnSchema);
286 private Object processSetData(final Object proxy, final Method method, final Object[] args) {
287 if (args == null || args.length != 1) {
288 throw new TyperException("Setter method : " + method.getName() + " requires 1 argument");
290 checkColumnSchemaVersion(dbSchema, method);
291 String columnName = getColumnName(method);
292 if (columnName == null) {
293 throw new TyperException("Unable to locate Column Name for " + method.getName());
295 GenericTableSchema tableSchema = getTableSchema(dbSchema, klazz);
296 ColumnSchema<GenericTableSchema, Object> columnSchema =
297 getColumnSchema(tableSchema, columnName, (Class<Object>) args[0].getClass());
298 Column<GenericTableSchema, Object> column =
299 new Column<>(columnSchema, args[0]);
300 row.addColumn(columnName, column);
304 private Object processGetTableSchema() {
305 if (dbSchema == null) {
308 return getTableSchema(dbSchema, klazz);
311 private Boolean isHashCodeMethod(final Method method, final Object[] args) {
312 return (args == null || args.length == 0) && method.getName().equals("hashCode");
315 private Boolean isEqualsMethod(final Method method, final Object[] args) {
318 && method.getName().equals("equals")
319 && Object.class.equals(method.getParameterTypes()[0]);
322 private Boolean isToStringMethod(final Method method, final Object[] args) {
323 return (args == null || args.length == 0) && method.getName().equals("toString");
327 public Object invoke(final Object proxy, final Method method, final Object[] args) throws Exception {
328 if (isGetTableSchema(method)) {
329 return processGetTableSchema();
330 } else if (isGetRow(method)) {
331 return processGetRow();
332 } else if (isSetData(method)) {
333 return processSetData(proxy, method, args);
334 } else if (isGetData(method)) {
335 return processGetData(method);
336 } else if (isGetColumn(method)) {
337 return processGetColumn(method);
338 } else if (isHashCodeMethod(method, args)) {
340 } else if (isEqualsMethod(method, args)) {
341 return proxy.getClass().isInstance(args[0]) && this.equals(args[0]);
342 } else if (isToStringMethod(method, args)) {
343 return this.toString();
345 throw new UnsupportedMethodException("Method not supported " + method.toString());
349 @SuppressFBWarnings({"EQ_CHECK_FOR_OPERAND_NOT_COMPATIBLE_WITH_THIS", "EQ_UNUSUAL"})
350 public boolean equals(final Object obj) {
351 if (!(obj instanceof TypedBaseTable)) {
354 TypedBaseTable<?> typedRowObj = (TypedBaseTable<?>)obj;
355 return Objects.equal(row, typedRowObj.getRow());
359 public int hashCode() {
363 return row.hashCode();
367 public String toString() {
369 TableSchema<?> schema = (TableSchema<?>)processGetTableSchema();
370 if (schema != null) {
371 tableName = schema.getName();
378 return tableName + " : " + row.toString();
385 * This method extracts all row updates of Class<T> klazz from a TableUpdates
386 * that correspond to insertion or updates of rows of type klazz.
389 * Map<UUID,Bridge> updatedBridges = extractRowsUpdated(Bridge.class,updates,dbSchema)
392 * @param klazz Class for row type to be extracted
393 * @param updates TableUpdates from which to extract rowUpdates
394 * @param dbSchema Dbschema for the TableUpdates
395 * @return Map<UUID,T> for the type of things being sought
397 public static <T> Map<UUID,T> extractRowsUpdated(final Class<T> klazz, final TableUpdates updates,
398 final DatabaseSchema dbSchema) {
399 Preconditions.checkNotNull(klazz);
400 Preconditions.checkNotNull(updates);
401 Preconditions.checkNotNull(dbSchema);
402 Map<UUID,T> result = new HashMap<>();
403 Map<UUID,TableUpdate<GenericTableSchema>.RowUpdate<GenericTableSchema>> rowUpdates =
404 extractRowUpdates(klazz,updates,dbSchema);
405 for (TableUpdate<GenericTableSchema>.RowUpdate<GenericTableSchema> rowUpdate : rowUpdates.values()) {
406 if (rowUpdate != null && rowUpdate.getNew() != null) {
407 Row<GenericTableSchema> row = rowUpdate.getNew();
408 result.put(rowUpdate.getUuid(),TyperUtils.getTypedRowWrapper(dbSchema,klazz,row));
415 * This method extracts all row updates of Class<T> klazz from a TableUpdates
416 * that correspond to old version of rows of type klazz that have been updated.
419 * Map<UUID,Bridge> oldBridges = extractRowsOld(Bridge.class,updates,dbSchema)
422 * @param klazz Class for row type to be extracted
423 * @param updates TableUpdates from which to extract rowUpdates
424 * @param dbSchema Dbschema for the TableUpdates
425 * @return Map<UUID,T> for the type of things being sought
427 public static <T> Map<UUID, T> extractRowsOld(final Class<T> klazz, final TableUpdates updates,
428 final DatabaseSchema dbSchema) {
429 Preconditions.checkNotNull(klazz);
430 Preconditions.checkNotNull(updates);
431 Preconditions.checkNotNull(dbSchema);
432 Map<UUID,T> result = new HashMap<>();
433 Map<UUID,TableUpdate<GenericTableSchema>.RowUpdate<GenericTableSchema>> rowUpdates =
434 extractRowUpdates(klazz,updates,dbSchema);
435 for (TableUpdate<GenericTableSchema>.RowUpdate<GenericTableSchema> rowUpdate : rowUpdates.values()) {
436 if (rowUpdate != null && rowUpdate.getOld() != null) {
437 Row<GenericTableSchema> row = rowUpdate.getOld();
438 result.put(rowUpdate.getUuid(),TyperUtils.getTypedRowWrapper(dbSchema,klazz,row));
445 * This method extracts all row updates of Class<T> klazz from a TableUpdates
446 * that correspond to removal of rows of type klazz.
449 * Map<UUID,Bridge> updatedBridges = extractRowsRemoved(Bridge.class,updates,dbSchema)
452 * @param klazz Class for row type to be extracted
453 * @param updates TableUpdates from which to extract rowUpdates
454 * @param dbSchema Dbschema for the TableUpdates
455 * @return Map<UUID,T> for the type of things being sought
457 public static <T> Map<UUID,T> extractRowsRemoved(final Class<T> klazz, final TableUpdates updates,
458 final DatabaseSchema dbSchema) {
459 Preconditions.checkNotNull(klazz);
460 Preconditions.checkNotNull(updates);
461 Preconditions.checkNotNull(dbSchema);
462 Map<UUID,T> result = new HashMap<>();
463 Map<UUID,TableUpdate<GenericTableSchema>.RowUpdate<GenericTableSchema>> rowUpdates =
464 extractRowUpdates(klazz,updates,dbSchema);
465 for (TableUpdate<GenericTableSchema>.RowUpdate<GenericTableSchema> rowUpdate : rowUpdates.values()) {
466 if (rowUpdate != null && rowUpdate.getNew() == null && rowUpdate.getOld() != null) {
467 Row<GenericTableSchema> row = rowUpdate.getOld();
468 result.put(rowUpdate.getUuid(),TyperUtils.getTypedRowWrapper(dbSchema,klazz,row));
475 * This method extracts all RowUpdates of Class<T> klazz from a TableUpdates
476 * that correspond to rows of type klazz.
479 * Map<UUID,TableUpdate<GenericTableSchema>.RowUpdate<GenericTableSchema>> updatedBridges =
480 * extractRowsUpdates(Bridge.class,updates,dbSchema)
483 * @param klazz Class for row type to be extracted
484 * @param updates TableUpdates from which to extract rowUpdates
485 * @param dbSchema Dbschema for the TableUpdates
486 * @return Map<UUID,TableUpdate<GenericTableSchema>.RowUpdate<GenericTableSchema>>
487 * for the type of things being sought
489 static Map<UUID,TableUpdate<GenericTableSchema>.RowUpdate<GenericTableSchema>> extractRowUpdates(
490 final Class<?> klazz,final TableUpdates updates,final DatabaseSchema dbSchema) {
491 Preconditions.checkNotNull(klazz);
492 Preconditions.checkNotNull(updates);
493 Preconditions.checkNotNull(dbSchema);
494 Map<UUID, TableUpdate<GenericTableSchema>.RowUpdate<GenericTableSchema>> result =
496 TableUpdate<GenericTableSchema> update = updates.getUpdate(TyperUtils.getTableSchema(dbSchema, klazz));
497 if (update != null) {
498 Map<UUID, TableUpdate<GenericTableSchema>.RowUpdate<GenericTableSchema>> rows = update.getRows();