2 * Copyright (c) 2014, 2015 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 java.lang.reflect.InvocationHandler;
12 import java.lang.reflect.Method;
13 import java.util.HashMap;
16 import org.opendaylight.ovsdb.lib.error.ColumnSchemaNotFoundException;
17 import org.opendaylight.ovsdb.lib.error.SchemaVersionMismatchException;
18 import org.opendaylight.ovsdb.lib.error.TableSchemaNotFoundException;
19 import org.opendaylight.ovsdb.lib.error.TyperException;
20 import org.opendaylight.ovsdb.lib.error.UnsupportedMethodException;
21 import org.opendaylight.ovsdb.lib.message.TableUpdate;
22 import org.opendaylight.ovsdb.lib.message.TableUpdates;
23 import org.opendaylight.ovsdb.lib.notation.Column;
24 import org.opendaylight.ovsdb.lib.notation.Row;
25 import org.opendaylight.ovsdb.lib.notation.UUID;
26 import org.opendaylight.ovsdb.lib.notation.Version;
27 import org.opendaylight.ovsdb.lib.schema.ColumnSchema;
28 import org.opendaylight.ovsdb.lib.schema.DatabaseSchema;
29 import org.opendaylight.ovsdb.lib.schema.GenericTableSchema;
30 import org.opendaylight.ovsdb.lib.schema.TableSchema;
32 import com.google.common.base.Preconditions;
33 import com.google.common.reflect.Reflection;
35 public class TyperUtils {
36 private static final String GET_STARTS_WITH = "get";
37 private static final String SET_STARTS_WITH = "set";
38 private static final String GETCOLUMN_ENDS_WITH = "Column";
39 private static final String GETROW_ENDS_WITH = "Row";
41 private TyperUtils() {
42 // Prevent instantiating a utility class
45 private static <T> String getTableName(Class<T> klazz) {
46 TypedTable typedTable = klazz.getAnnotation(TypedTable.class);
47 if (typedTable != null) {
48 return typedTable.name();
50 return klazz.getSimpleName();
53 public static <T> GenericTableSchema getTableSchema(DatabaseSchema dbSchema, Class<T> klazz) {
54 String tableName = getTableName(klazz);
55 return dbSchema.table(tableName, GenericTableSchema.class);
58 public static ColumnSchema<GenericTableSchema, Object>
59 getColumnSchema(GenericTableSchema tableSchema, String columnName, Class<Object> metaClass) {
60 return tableSchema.column(columnName, metaClass);
63 private static String getColumnName(Method method) {
64 TypedColumn typedColumn = method.getAnnotation(TypedColumn.class);
65 if (typedColumn != null) {
66 return typedColumn.name();
70 * Attempting to get the column name by parsing the method name with a following convention :
71 * 1. GETDATA : get<ColumnName>
72 * 2. SETDATA : set<ColumnName>
73 * 3. GETCOLUMN : get<ColumnName>Column
74 * where <ColumnName> is the name of the column that we are interested in.
76 int index = GET_STARTS_WITH.length();
77 if (isGetData(method) || isSetData(method)) {
78 return method.getName().substring(index, method.getName().length()).toLowerCase();
79 } else if (isGetColumn(method)) {
80 return method.getName().substring(index, method.getName().indexOf(GETCOLUMN_ENDS_WITH,
81 index)).toLowerCase();
87 private static boolean isGetTableSchema(Method method) {
88 TypedColumn typedColumn = method.getAnnotation(TypedColumn.class);
89 return typedColumn != null && typedColumn.method().equals(MethodType.GETTABLESCHEMA);
92 private static boolean isGetRow(Method method) {
93 TypedColumn typedColumn = method.getAnnotation(TypedColumn.class);
94 if (typedColumn != null) {
95 return typedColumn.method().equals(MethodType.GETROW);
98 return method.getName().startsWith(GET_STARTS_WITH) && method.getName().endsWith(GETROW_ENDS_WITH);
101 private static boolean isGetColumn(Method method) {
102 TypedColumn typedColumn = method.getAnnotation(TypedColumn.class);
103 if (typedColumn != null) {
104 return typedColumn.method().equals(MethodType.GETCOLUMN);
107 return method.getName().startsWith(GET_STARTS_WITH) && method.getName().endsWith(GETCOLUMN_ENDS_WITH);
110 private static boolean isGetData(Method method) {
111 TypedColumn typedColumn = method.getAnnotation(TypedColumn.class);
112 if (typedColumn != null) {
113 return typedColumn.method().equals(MethodType.GETDATA);
116 return method.getName().startsWith(GET_STARTS_WITH) && !method.getName().endsWith(GETCOLUMN_ENDS_WITH);
119 private static boolean isSetData(Method method) {
120 TypedColumn typedColumn = method.getAnnotation(TypedColumn.class);
121 if (typedColumn != null) {
122 return typedColumn.method().equals(MethodType.SETDATA);
125 return method.getName().startsWith(SET_STARTS_WITH);
128 public static Version getColumnFromVersion(Method method) {
129 TypedColumn typedColumn = method.getAnnotation(TypedColumn.class);
130 if (typedColumn != null) {
131 return Version.fromString(typedColumn.fromVersion());
136 public static <T> Version getTableFromVersion(final Class<T> klazz) {
137 TypedTable typedTable = klazz.getAnnotation(TypedTable.class);
138 if (typedTable != null) {
139 return Version.fromString(typedTable.fromVersion());
144 public static Version getColumnUntilVersion(Method method) {
145 TypedColumn typedColumn = method.getAnnotation(TypedColumn.class);
146 if (typedColumn != null) {
147 return Version.fromString(typedColumn.untilVersion());
152 public static <T> Version getTableUntilVersion(final Class<T> klazz) {
153 TypedTable typedTable = klazz.getAnnotation(TypedTable.class);
154 if (typedTable != null) {
155 return Version.fromString(typedTable.untilVersion());
161 * Method that checks validity of the parameter passed to getTypedRowWrapper.
162 * This method checks for a valid Database Schema matching the expected Database for a given table
163 * and checks for the presence of the Table in Database Schema.
165 * @param dbSchema DatabaseSchema as learnt from a OVSDB connection
166 * @param klazz Typed Class that represents a Table
167 * @return true if valid, false otherwise
169 private static <T> boolean isValid(DatabaseSchema dbSchema, final Class<T> klazz) {
170 if (dbSchema == null) {
174 TypedTable typedTable = klazz.getAnnotation(TypedTable.class);
175 if (typedTable != null && !dbSchema.getName().equalsIgnoreCase(typedTable.database())) {
179 checkTableSchemaVersion(dbSchema, klazz);
184 private static void checkColumnSchemaVersion(DatabaseSchema dbSchema, Method method) {
185 Version fromVersion = getColumnFromVersion(method);
186 Version untilVersion = getColumnUntilVersion(method);
187 Version schemaVersion = dbSchema.getVersion();
188 checkVersion(schemaVersion, fromVersion, untilVersion);
191 private static <T> void checkTableSchemaVersion(DatabaseSchema dbSchema, Class<T> klazz) {
192 Version fromVersion = getTableFromVersion(klazz);
193 Version untilVersion = getTableUntilVersion(klazz);
194 Version schemaVersion = dbSchema.getVersion();
195 checkVersion(schemaVersion, fromVersion, untilVersion);
198 private static void checkVersion(Version schemaVersion, Version fromVersion, Version untilVersion) {
199 if (!fromVersion.equals(Version.NULL) && schemaVersion.compareTo(fromVersion) < 0) {
200 String message = SchemaVersionMismatchException.createMessage(schemaVersion, fromVersion);
201 throw new SchemaVersionMismatchException(message);
203 if (!untilVersion.equals(Version.NULL) && schemaVersion.compareTo(untilVersion) > 0) {
204 String message = SchemaVersionMismatchException.createMessage(schemaVersion, untilVersion);
205 throw new SchemaVersionMismatchException(message);
210 * This method returns a Typed Proxy implementation for the klazz passed as a parameter.
211 * Per design choice, the Typed Proxy implementation is just a Wrapper on top of the actual
212 * Row which is untyped.
213 * Being just a wrapper, it is state-less and more of a convenience functionality to
214 * provide a type-safe infrastructure for the applications to built on top of.
215 * And this Typed infra is completely optional.
217 * It is the applications responsibility to pass on the raw Row parameter and this method will
218 * return the appropriate Proxy wrapper for the passed klazz Type.
219 * The raw Row parameter may be null if the caller is interested in just the ColumnSchema.
220 * But that is not a very common use-case.
222 * @param dbSchema DatabaseSchema as learnt from a OVSDB connection
223 * @param klazz Typed Class that represents a Table
226 public static <T> T getTypedRowWrapper(final DatabaseSchema dbSchema, final Class<T> klazz) {
227 return getTypedRowWrapper(dbSchema, klazz,new Row<GenericTableSchema>());
231 * This method returns a Typed Proxy implementation for the klazz passed as a parameter.
232 * Per design choice, the Typed Proxy implementation is just a Wrapper on top of the actual
233 * Row which is untyped.
234 * Being just a wrapper, it is state-less and more of a convenience functionality
235 * to provide a type-safe infrastructure for the applications to built on top of.
236 * And this Typed infra is completely optional.
238 * It is the applications responsibility to pass on the raw Row parameter and this method
239 * will return the appropriate Proxy wrapper for the passed klazz Type.
240 * The raw Row parameter may be null if the caller is interested in just the
241 * ColumnSchema. But that is not a very common use-case.
243 * @param dbSchema DatabaseSchema as learnt from a OVSDB connection
244 * @param klazz Typed Class that represents a Table
245 * @param row The actual Row that the wrapper is operating on. It can be null if the caller
246 * is just interested in getting ColumnSchema.
249 public static <T> T getTypedRowWrapper(final DatabaseSchema dbSchema, final Class<T> klazz,
250 final Row<GenericTableSchema> row) {
251 if (!isValid(dbSchema, klazz)) {
255 row.setTableSchema(getTableSchema(dbSchema, klazz));
257 return Reflection.newProxy(klazz, new InvocationHandler() {
258 private Object processGetData(Method method) {
259 String columnName = getColumnName(method);
260 checkColumnSchemaVersion(dbSchema, method);
261 if (columnName == null) {
262 throw new TyperException("Error processing Getter : " + method.getName());
264 GenericTableSchema tableSchema = getTableSchema(dbSchema, klazz);
265 if (tableSchema == null) {
267 TableSchemaNotFoundException.createMessage(getTableName(klazz), dbSchema.getName());
268 throw new TableSchemaNotFoundException(message);
270 ColumnSchema<GenericTableSchema, Object> columnSchema =
271 getColumnSchema(tableSchema, columnName, (Class<Object>) method.getReturnType());
272 if (columnSchema == null) {
273 String message = ColumnSchemaNotFoundException.createMessage(columnName, tableSchema.getName());
274 throw new ColumnSchemaNotFoundException(message);
276 if (row == null || row.getColumn(columnSchema) == null) {
279 return row.getColumn(columnSchema).getData();
282 private Object processGetRow() {
286 private Object processGetColumn(Method method) {
287 String columnName = getColumnName(method);
288 checkColumnSchemaVersion(dbSchema, method);
289 if (columnName == null) {
290 throw new TyperException("Error processing GetColumn : " + method.getName());
292 GenericTableSchema tableSchema = getTableSchema(dbSchema, klazz);
293 if (tableSchema == null) {
295 TableSchemaNotFoundException.createMessage(getTableName(klazz), dbSchema.getName());
296 throw new TableSchemaNotFoundException(message);
298 ColumnSchema<GenericTableSchema, Object> columnSchema =
299 getColumnSchema(tableSchema, columnName, (Class<Object>) method.getReturnType());
300 if (columnSchema == null) {
301 String message = ColumnSchemaNotFoundException.createMessage(columnName, tableSchema.getName());
302 throw new ColumnSchemaNotFoundException(message);
304 // When the row is null, that might indicate that the user maybe interested
305 // only in the ColumnSchema and not on the Data.
307 return new Column<GenericTableSchema, Object>(columnSchema, null);
309 return row.getColumn(columnSchema);
312 private Object processSetData(Object proxy, Method method, Object[] args) {
313 if (args == null || args.length != 1) {
314 throw new TyperException("Setter method : " + method.getName() + " requires 1 argument");
316 checkColumnSchemaVersion(dbSchema, method);
317 String columnName = getColumnName(method);
318 if (columnName == null) {
319 throw new TyperException("Unable to locate Column Name for " + method.getName());
321 GenericTableSchema tableSchema = getTableSchema(dbSchema, klazz);
322 ColumnSchema<GenericTableSchema, Object> columnSchema =
323 getColumnSchema(tableSchema, columnName, (Class<Object>) args[0].getClass());
324 Column<GenericTableSchema, Object> column =
325 new Column<>(columnSchema, args[0]);
326 row.addColumn(columnName, column);
330 private Object processGetTableSchema() {
331 if (dbSchema == null) {
334 return getTableSchema(dbSchema, klazz);
337 private Boolean isHashCodeMethod(Method method, Object[] args) {
338 return (args == null || args.length == 0) && method.getName().equals("hashCode");
340 private Boolean isEqualsMethod(Method method, Object[] args) {
343 && method.getName().equals("equals")
344 && Object.class.equals(method.getParameterTypes()[0]));
346 private Boolean isToStringMethod(Method method, Object[] args) {
347 return (args == null || args.length == 0) && method.getName().equals("toString");
350 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
351 if (isGetTableSchema(method)) {
352 return processGetTableSchema();
353 } else if (isGetRow(method)) {
354 return processGetRow();
355 } else if (isSetData(method)) {
356 return processSetData(proxy, method, args);
357 } else if (isGetData(method)) {
358 return processGetData(method);
359 } else if (isGetColumn(method)) {
360 return processGetColumn(method);
361 } else if (isHashCodeMethod(method, args)) {
363 } else if (isEqualsMethod(method, args)) {
364 return proxy.getClass().isInstance(args[0]) && this.equals(args[0]);
365 } else if (isToStringMethod(method, args)) {
366 return this.toString();
368 throw new UnsupportedMethodException("Method not supported " + method.toString());
372 public boolean equals(Object obj) {
376 TypedBaseTable<?> typedRowObj = (TypedBaseTable<?>)obj;
377 if (row == null && typedRowObj.getRow() == null) {
380 if (row.equals(typedRowObj.getRow())) {
386 @Override public int hashCode() {
390 return row.hashCode();
393 @Override public String toString() {
396 TableSchema<?> schema = (TableSchema<?>)processGetTableSchema();
397 tableName = schema.getName();
398 } catch (Exception e) {
404 return tableName + " : " + row.toString();
411 * This method extracts all row updates of Class<T> klazz from a TableUpdates
412 * that correspond to insertion or updates of rows of type klazz.
415 * Map<UUID,Bridge> updatedBridges = extractRowsUpdated(Bridge.class,updates,dbSchema)
418 * @param klazz Class for row type to be extracted
419 * @param updates TableUpdates from which to extract rowUpdates
420 * @param dbSchema Dbschema for the TableUpdates
421 * @return Map<UUID,T> for the type of things being sought
423 public static <T> Map<UUID,T> extractRowsUpdated(Class<T> klazz,TableUpdates updates,DatabaseSchema dbSchema) {
424 Preconditions.checkNotNull(klazz);
425 Preconditions.checkNotNull(updates);
426 Preconditions.checkNotNull(dbSchema);
427 Map<UUID,T> result = new HashMap<>();
428 Map<UUID,TableUpdate<GenericTableSchema>.RowUpdate<GenericTableSchema>> rowUpdates =
429 extractRowUpdates(klazz,updates,dbSchema);
430 for (TableUpdate<GenericTableSchema>.RowUpdate<GenericTableSchema> rowUpdate : rowUpdates.values()) {
431 if (rowUpdate != null && rowUpdate.getNew() != null) {
432 Row<GenericTableSchema> row = rowUpdate.getNew();
433 result.put(rowUpdate.getUuid(),TyperUtils.getTypedRowWrapper(dbSchema,klazz,row));
440 * This method extracts all row updates of Class<T> klazz from a TableUpdates
441 * that correspond to old version of rows of type klazz that have been updated
444 * Map<UUID,Bridge> oldBridges = extractRowsOld(Bridge.class,updates,dbSchema)
447 * @param klazz Class for row type to be extracted
448 * @param updates TableUpdates from which to extract rowUpdates
449 * @param dbSchema Dbschema for the TableUpdates
450 * @return Map<UUID,T> for the type of things being sought
452 public static <T> Map<UUID,T> extractRowsOld(Class<T> klazz,TableUpdates updates,DatabaseSchema dbSchema) {
453 Preconditions.checkNotNull(klazz);
454 Preconditions.checkNotNull(updates);
455 Preconditions.checkNotNull(dbSchema);
456 Map<UUID,T> result = new HashMap<>();
457 Map<UUID,TableUpdate<GenericTableSchema>.RowUpdate<GenericTableSchema>> rowUpdates =
458 extractRowUpdates(klazz,updates,dbSchema);
459 for (TableUpdate<GenericTableSchema>.RowUpdate<GenericTableSchema> rowUpdate : rowUpdates.values()) {
460 if (rowUpdate != null && rowUpdate.getOld() != null) {
461 Row<GenericTableSchema> row = rowUpdate.getOld();
462 result.put(rowUpdate.getUuid(),TyperUtils.getTypedRowWrapper(dbSchema,klazz,row));
469 * This method extracts all row updates of Class<T> klazz from a TableUpdates
470 * that correspond to removal of rows of type klazz.
473 * Map<UUID,Bridge> updatedBridges = extractRowsRemoved(Bridge.class,updates,dbSchema)
476 * @param klazz Class for row type to be extracted
477 * @param updates TableUpdates from which to extract rowUpdates
478 * @param dbSchema Dbschema for the TableUpdates
479 * @return Map<UUID,T> for the type of things being sought
481 public static <T> Map<UUID,T> extractRowsRemoved(Class<T> klazz,TableUpdates updates,DatabaseSchema dbSchema) {
482 Preconditions.checkNotNull(klazz);
483 Preconditions.checkNotNull(updates);
484 Preconditions.checkNotNull(dbSchema);
485 Map<UUID,T> result = new HashMap<>();
486 Map<UUID,TableUpdate<GenericTableSchema>.RowUpdate<GenericTableSchema>> rowUpdates =
487 extractRowUpdates(klazz,updates,dbSchema);
488 for (TableUpdate<GenericTableSchema>.RowUpdate<GenericTableSchema> rowUpdate : rowUpdates.values()) {
489 if (rowUpdate != null && rowUpdate.getNew() == null && rowUpdate.getOld() != null) {
490 Row<GenericTableSchema> row = rowUpdate.getOld();
491 result.put(rowUpdate.getUuid(),TyperUtils.getTypedRowWrapper(dbSchema,klazz,row));
498 * This method extracts all RowUpdates of Class<T> klazz from a TableUpdates
499 * that correspond to rows of type klazz.
502 * Map<UUID,TableUpdate<GenericTableSchema>.RowUpdate<GenericTableSchema>> updatedBridges =
503 * extractRowsUpdates(Bridge.class,updates,dbSchema)
506 * @param klazz Class for row type to be extracted
507 * @param updates TableUpdates from which to extract rowUpdates
508 * @param dbSchema Dbschema for the TableUpdates
509 * @return Map<UUID,TableUpdate<GenericTableSchema>.RowUpdate<GenericTableSchema>>
510 * for the type of things being sought
512 public static Map<UUID,TableUpdate<GenericTableSchema>.RowUpdate<GenericTableSchema>>
513 extractRowUpdates(Class<?> klazz,TableUpdates updates,DatabaseSchema dbSchema) {
514 Preconditions.checkNotNull(klazz);
515 Preconditions.checkNotNull(updates);
516 Preconditions.checkNotNull(dbSchema);
517 Map<UUID, TableUpdate<GenericTableSchema>.RowUpdate<GenericTableSchema>> result =
519 TableUpdate<GenericTableSchema> update = updates.getUpdate(TyperUtils.getTableSchema(dbSchema, klazz));
520 if (update != null) {
521 Map<UUID, TableUpdate<GenericTableSchema>.RowUpdate<GenericTableSchema>> rows = update.getRows();