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) || (!untilVersion.equals(
200 Version.NULL) && schemaVersion.compareTo(untilVersion) > 0)) {
201 throw new SchemaVersionMismatchException(schemaVersion, fromVersion, untilVersion);
206 * This method returns a Typed Proxy implementation for the klazz passed as a parameter.
207 * Per design choice, the Typed Proxy implementation is just a Wrapper on top of the actual
208 * Row which is untyped.
209 * Being just a wrapper, it is state-less and more of a convenience functionality to
210 * provide a type-safe infrastructure for the applications to built on top of.
211 * And this Typed infra is completely optional.
213 * It is the applications responsibility to pass on the raw Row parameter and this method will
214 * 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 ColumnSchema.
216 * 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
222 public static <T> T getTypedRowWrapper(final DatabaseSchema dbSchema, final Class<T> klazz) {
223 return getTypedRowWrapper(dbSchema, klazz,new Row<GenericTableSchema>());
227 * This method returns a Typed Proxy implementation for the klazz passed as a parameter.
228 * Per design choice, the Typed Proxy implementation is just a Wrapper on top of the actual
229 * Row which is untyped.
230 * Being just a wrapper, it is state-less and more of a convenience functionality
231 * to provide a type-safe infrastructure for the applications to built on top of.
232 * And this Typed infra is completely optional.
234 * It is the applications responsibility to pass on the raw Row parameter and this method
235 * will return the appropriate Proxy wrapper for the passed klazz Type.
236 * The raw Row parameter may be null if the caller is interested in just the
237 * ColumnSchema. But that is not a very common use-case.
239 * @param dbSchema DatabaseSchema as learnt from a OVSDB connection
240 * @param klazz Typed Class that represents a Table
241 * @param row The actual Row that the wrapper is operating on. It can be null if the caller
242 * is just interested in getting ColumnSchema.
245 public static <T> T getTypedRowWrapper(final DatabaseSchema dbSchema, final Class<T> klazz,
246 final Row<GenericTableSchema> row) {
247 if (!isValid(dbSchema, klazz)) {
251 row.setTableSchema(getTableSchema(dbSchema, klazz));
253 return Reflection.newProxy(klazz, new InvocationHandler() {
254 private Object processGetData(Method method) {
255 String columnName = getColumnName(method);
256 checkColumnSchemaVersion(dbSchema, method);
257 if (columnName == null) {
258 throw new TyperException("Error processing Getter : " + method.getName());
260 GenericTableSchema tableSchema = getTableSchema(dbSchema, klazz);
261 if (tableSchema == null) {
263 TableSchemaNotFoundException.createMessage(getTableName(klazz), dbSchema.getName());
264 throw new TableSchemaNotFoundException(message);
266 ColumnSchema<GenericTableSchema, Object> columnSchema =
267 getColumnSchema(tableSchema, columnName, (Class<Object>) method.getReturnType());
268 if (columnSchema == null) {
269 String message = ColumnSchemaNotFoundException.createMessage(columnName, tableSchema.getName());
270 throw new ColumnSchemaNotFoundException(message);
272 if (row == null || row.getColumn(columnSchema) == null) {
275 return row.getColumn(columnSchema).getData();
278 private Object processGetRow() {
282 private Object processGetColumn(Method method) {
283 String columnName = getColumnName(method);
284 checkColumnSchemaVersion(dbSchema, method);
285 if (columnName == null) {
286 throw new TyperException("Error processing GetColumn : " + method.getName());
288 GenericTableSchema tableSchema = getTableSchema(dbSchema, klazz);
289 if (tableSchema == null) {
291 TableSchemaNotFoundException.createMessage(getTableName(klazz), dbSchema.getName());
292 throw new TableSchemaNotFoundException(message);
294 ColumnSchema<GenericTableSchema, Object> columnSchema =
295 getColumnSchema(tableSchema, columnName, (Class<Object>) method.getReturnType());
296 if (columnSchema == null) {
297 String message = ColumnSchemaNotFoundException.createMessage(columnName, tableSchema.getName());
298 throw new ColumnSchemaNotFoundException(message);
300 // When the row is null, that might indicate that the user maybe interested
301 // only in the ColumnSchema and not on the Data.
303 return new Column<>(columnSchema, null);
305 return row.getColumn(columnSchema);
308 private Object processSetData(Object proxy, Method method, Object[] args) {
309 if (args == null || args.length != 1) {
310 throw new TyperException("Setter method : " + method.getName() + " requires 1 argument");
312 checkColumnSchemaVersion(dbSchema, method);
313 String columnName = getColumnName(method);
314 if (columnName == null) {
315 throw new TyperException("Unable to locate Column Name for " + method.getName());
317 GenericTableSchema tableSchema = getTableSchema(dbSchema, klazz);
318 ColumnSchema<GenericTableSchema, Object> columnSchema =
319 getColumnSchema(tableSchema, columnName, (Class<Object>) args[0].getClass());
320 Column<GenericTableSchema, Object> column =
321 new Column<>(columnSchema, args[0]);
322 row.addColumn(columnName, column);
326 private Object processGetTableSchema() {
327 if (dbSchema == null) {
330 return getTableSchema(dbSchema, klazz);
333 private Boolean isHashCodeMethod(Method method, Object[] args) {
334 return (args == null || args.length == 0) && method.getName().equals("hashCode");
336 private Boolean isEqualsMethod(Method method, Object[] args) {
339 && method.getName().equals("equals")
340 && Object.class.equals(method.getParameterTypes()[0]));
342 private Boolean isToStringMethod(Method method, Object[] args) {
343 return (args == null || args.length == 0) && method.getName().equals("toString");
346 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
347 if (isGetTableSchema(method)) {
348 return processGetTableSchema();
349 } else if (isGetRow(method)) {
350 return processGetRow();
351 } else if (isSetData(method)) {
352 return processSetData(proxy, method, args);
353 } else if (isGetData(method)) {
354 return processGetData(method);
355 } else if (isGetColumn(method)) {
356 return processGetColumn(method);
357 } else if (isHashCodeMethod(method, args)) {
359 } else if (isEqualsMethod(method, args)) {
360 return proxy.getClass().isInstance(args[0]) && this.equals(args[0]);
361 } else if (isToStringMethod(method, args)) {
362 return this.toString();
364 throw new UnsupportedMethodException("Method not supported " + method.toString());
368 public boolean equals(Object obj) {
372 TypedBaseTable<?> typedRowObj = (TypedBaseTable<?>)obj;
373 if (row == null && typedRowObj.getRow() == null) {
376 if (row.equals(typedRowObj.getRow())) {
382 @Override public int hashCode() {
386 return row.hashCode();
389 @Override public String toString() {
392 TableSchema<?> schema = (TableSchema<?>)processGetTableSchema();
393 tableName = schema.getName();
394 } catch (Exception e) {
400 return tableName + " : " + row.toString();
407 * This method extracts all row updates of Class<T> klazz from a TableUpdates
408 * that correspond to insertion or updates of rows of type klazz.
411 * Map<UUID,Bridge> updatedBridges = extractRowsUpdated(Bridge.class,updates,dbSchema)
414 * @param klazz Class for row type to be extracted
415 * @param updates TableUpdates from which to extract rowUpdates
416 * @param dbSchema Dbschema for the TableUpdates
417 * @return Map<UUID,T> for the type of things being sought
419 public static <T> Map<UUID,T> extractRowsUpdated(Class<T> klazz,TableUpdates updates,DatabaseSchema dbSchema) {
420 Preconditions.checkNotNull(klazz);
421 Preconditions.checkNotNull(updates);
422 Preconditions.checkNotNull(dbSchema);
423 Map<UUID,T> result = new HashMap<>();
424 Map<UUID,TableUpdate<GenericTableSchema>.RowUpdate<GenericTableSchema>> rowUpdates =
425 extractRowUpdates(klazz,updates,dbSchema);
426 for (TableUpdate<GenericTableSchema>.RowUpdate<GenericTableSchema> rowUpdate : rowUpdates.values()) {
427 if (rowUpdate != null && rowUpdate.getNew() != null) {
428 Row<GenericTableSchema> row = rowUpdate.getNew();
429 result.put(rowUpdate.getUuid(),TyperUtils.getTypedRowWrapper(dbSchema,klazz,row));
436 * This method extracts all row updates of Class<T> klazz from a TableUpdates
437 * that correspond to old version of rows of type klazz that have been updated
440 * Map<UUID,Bridge> oldBridges = extractRowsOld(Bridge.class,updates,dbSchema)
443 * @param klazz Class for row type to be extracted
444 * @param updates TableUpdates from which to extract rowUpdates
445 * @param dbSchema Dbschema for the TableUpdates
446 * @return Map<UUID,T> for the type of things being sought
448 public static <T> Map<UUID,T> extractRowsOld(Class<T> klazz,TableUpdates updates,DatabaseSchema dbSchema) {
449 Preconditions.checkNotNull(klazz);
450 Preconditions.checkNotNull(updates);
451 Preconditions.checkNotNull(dbSchema);
452 Map<UUID,T> result = new HashMap<>();
453 Map<UUID,TableUpdate<GenericTableSchema>.RowUpdate<GenericTableSchema>> rowUpdates =
454 extractRowUpdates(klazz,updates,dbSchema);
455 for (TableUpdate<GenericTableSchema>.RowUpdate<GenericTableSchema> rowUpdate : rowUpdates.values()) {
456 if (rowUpdate != null && rowUpdate.getOld() != null) {
457 Row<GenericTableSchema> row = rowUpdate.getOld();
458 result.put(rowUpdate.getUuid(),TyperUtils.getTypedRowWrapper(dbSchema,klazz,row));
465 * This method extracts all row updates of Class<T> klazz from a TableUpdates
466 * that correspond to removal of rows of type klazz.
469 * Map<UUID,Bridge> updatedBridges = extractRowsRemoved(Bridge.class,updates,dbSchema)
472 * @param klazz Class for row type to be extracted
473 * @param updates TableUpdates from which to extract rowUpdates
474 * @param dbSchema Dbschema for the TableUpdates
475 * @return Map<UUID,T> for the type of things being sought
477 public static <T> Map<UUID,T> extractRowsRemoved(Class<T> klazz,TableUpdates updates,DatabaseSchema dbSchema) {
478 Preconditions.checkNotNull(klazz);
479 Preconditions.checkNotNull(updates);
480 Preconditions.checkNotNull(dbSchema);
481 Map<UUID,T> result = new HashMap<>();
482 Map<UUID,TableUpdate<GenericTableSchema>.RowUpdate<GenericTableSchema>> rowUpdates =
483 extractRowUpdates(klazz,updates,dbSchema);
484 for (TableUpdate<GenericTableSchema>.RowUpdate<GenericTableSchema> rowUpdate : rowUpdates.values()) {
485 if (rowUpdate != null && rowUpdate.getNew() == null && rowUpdate.getOld() != null) {
486 Row<GenericTableSchema> row = rowUpdate.getOld();
487 result.put(rowUpdate.getUuid(),TyperUtils.getTypedRowWrapper(dbSchema,klazz,row));
494 * This method extracts all RowUpdates of Class<T> klazz from a TableUpdates
495 * that correspond to rows of type klazz.
498 * Map<UUID,TableUpdate<GenericTableSchema>.RowUpdate<GenericTableSchema>> updatedBridges =
499 * extractRowsUpdates(Bridge.class,updates,dbSchema)
502 * @param klazz Class for row type to be extracted
503 * @param updates TableUpdates from which to extract rowUpdates
504 * @param dbSchema Dbschema for the TableUpdates
505 * @return Map<UUID,TableUpdate<GenericTableSchema>.RowUpdate<GenericTableSchema>>
506 * for the type of things being sought
508 public static Map<UUID,TableUpdate<GenericTableSchema>.RowUpdate<GenericTableSchema>>
509 extractRowUpdates(Class<?> klazz,TableUpdates updates,DatabaseSchema dbSchema) {
510 Preconditions.checkNotNull(klazz);
511 Preconditions.checkNotNull(updates);
512 Preconditions.checkNotNull(dbSchema);
513 Map<UUID, TableUpdate<GenericTableSchema>.RowUpdate<GenericTableSchema>> result =
515 TableUpdate<GenericTableSchema> update = updates.getUpdate(TyperUtils.getTableSchema(dbSchema, klazz));
516 if (update != null) {
517 Map<UUID, TableUpdate<GenericTableSchema>.RowUpdate<GenericTableSchema>> rows = update.getRows();