Merge "Added Security Rule for Custom ICMP"
[ovsdb.git] / library / impl / src / main / java / org / opendaylight / ovsdb / lib / schema / typed / TyperUtils.java
1 /*
2  * Copyright (c) 2014, 2015 Red Hat, Inc. 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
9 package org.opendaylight.ovsdb.lib.schema.typed;
10
11 import java.lang.reflect.InvocationHandler;
12 import java.lang.reflect.Method;
13 import java.util.HashMap;
14 import java.util.Map;
15
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;
31
32 import com.google.common.base.Preconditions;
33 import com.google.common.reflect.Reflection;
34
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";
40
41     private TyperUtils() {
42         // Prevent instantiating a utility class
43     }
44
45     private static <T> String getTableName(Class<T> klazz) {
46         TypedTable typedTable = klazz.getAnnotation(TypedTable.class);
47         if (typedTable != null) {
48             return typedTable.name();
49         }
50         return klazz.getSimpleName();
51     }
52
53     public static <T> GenericTableSchema getTableSchema(DatabaseSchema dbSchema, Class<T> klazz) {
54         String tableName = getTableName(klazz);
55         return dbSchema.table(tableName, GenericTableSchema.class);
56     }
57
58     public static ColumnSchema<GenericTableSchema, Object>
59         getColumnSchema(GenericTableSchema tableSchema, String columnName, Class<Object> metaClass) {
60         return tableSchema.column(columnName, metaClass);
61     }
62
63     private static String getColumnName(Method method) {
64         TypedColumn typedColumn = method.getAnnotation(TypedColumn.class);
65         if (typedColumn != null) {
66             return typedColumn.name();
67         }
68
69         /*
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.
75          */
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();
82         }
83
84         return null;
85     }
86
87     private static boolean isGetTableSchema(Method method) {
88         TypedColumn typedColumn = method.getAnnotation(TypedColumn.class);
89         return typedColumn != null && typedColumn.method().equals(MethodType.GETTABLESCHEMA);
90     }
91
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);
96         }
97
98         return method.getName().startsWith(GET_STARTS_WITH) && method.getName().endsWith(GETROW_ENDS_WITH);
99     }
100
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);
105         }
106
107         return method.getName().startsWith(GET_STARTS_WITH) && method.getName().endsWith(GETCOLUMN_ENDS_WITH);
108     }
109
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);
114         }
115
116         return method.getName().startsWith(GET_STARTS_WITH) && !method.getName().endsWith(GETCOLUMN_ENDS_WITH);
117     }
118
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);
123         }
124
125         return method.getName().startsWith(SET_STARTS_WITH);
126     }
127
128     public static Version getColumnFromVersion(Method method) {
129         TypedColumn typedColumn = method.getAnnotation(TypedColumn.class);
130         if (typedColumn != null) {
131             return Version.fromString(typedColumn.fromVersion());
132         }
133         return Version.NULL;
134     }
135
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());
140         }
141         return Version.NULL;
142     }
143
144     public static Version getColumnUntilVersion(Method method) {
145         TypedColumn typedColumn = method.getAnnotation(TypedColumn.class);
146         if (typedColumn != null) {
147             return Version.fromString(typedColumn.untilVersion());
148         }
149         return Version.NULL;
150     }
151
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());
156         }
157         return Version.NULL;
158     }
159
160     /**
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.
164      *
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
168      */
169     private static <T> boolean isValid(DatabaseSchema dbSchema, final Class<T> klazz) {
170         if (dbSchema == null) {
171             return false;
172         }
173
174         TypedTable typedTable = klazz.getAnnotation(TypedTable.class);
175         if (typedTable != null && !dbSchema.getName().equalsIgnoreCase(typedTable.database())) {
176             return false;
177         }
178
179         checkTableSchemaVersion(dbSchema, klazz);
180
181         return true;
182     }
183
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);
189     }
190
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);
196     }
197
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);
202         }
203         if (!untilVersion.equals(Version.NULL) && schemaVersion.compareTo(untilVersion) > 0) {
204             String message = SchemaVersionMismatchException.createMessage(schemaVersion, untilVersion);
205             throw new SchemaVersionMismatchException(message);
206         }
207     }
208
209     /**
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.
216      *
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.
221      *
222      * @param dbSchema DatabaseSchema as learnt from a OVSDB connection
223      * @param klazz Typed Class that represents a Table
224      * @return
225      */
226     public static <T> T getTypedRowWrapper(final DatabaseSchema dbSchema, final Class<T> klazz) {
227         return getTypedRowWrapper(dbSchema, klazz,new Row<GenericTableSchema>());
228     }
229
230     /**
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.
237      *
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.
242      *
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.
247      * @return
248      */
249     public static <T> T getTypedRowWrapper(final DatabaseSchema dbSchema, final Class<T> klazz,
250                                            final Row<GenericTableSchema> row) {
251         if (!isValid(dbSchema, klazz)) {
252             return null;
253         }
254         if (row != null) {
255             row.setTableSchema(getTableSchema(dbSchema, klazz));
256         }
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());
263                 }
264                 GenericTableSchema tableSchema = getTableSchema(dbSchema, klazz);
265                 if (tableSchema == null) {
266                     String message =
267                             TableSchemaNotFoundException.createMessage(getTableName(klazz), dbSchema.getName());
268                     throw new TableSchemaNotFoundException(message);
269                 }
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);
275                 }
276                 if (row == null || row.getColumn(columnSchema) == null) {
277                     return null;
278                 }
279                 return row.getColumn(columnSchema).getData();
280             }
281
282             private Object processGetRow() {
283                 return row;
284             }
285
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());
291                 }
292                 GenericTableSchema tableSchema = getTableSchema(dbSchema, klazz);
293                 if (tableSchema == null) {
294                     String message =
295                             TableSchemaNotFoundException.createMessage(getTableName(klazz), dbSchema.getName());
296                     throw new TableSchemaNotFoundException(message);
297                 }
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);
303                 }
304                 // When the row is null, that might indicate that the user maybe interested
305                 // only in the ColumnSchema and not on the Data.
306                 if (row == null) {
307                     return new Column<GenericTableSchema, Object>(columnSchema, null);
308                 }
309                 return row.getColumn(columnSchema);
310             }
311
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");
315                 }
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());
320                 }
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);
327                 return proxy;
328             }
329
330             private Object processGetTableSchema() {
331                 if (dbSchema == null) {
332                     return null;
333                 }
334                 return getTableSchema(dbSchema, klazz);
335             }
336
337             private Boolean isHashCodeMethod(Method method, Object[] args) {
338                 return (args == null || args.length == 0) && method.getName().equals("hashCode");
339             }
340             private Boolean isEqualsMethod(Method method, Object[] args) {
341                 return (args != null
342                         && args.length == 1
343                         && method.getName().equals("equals")
344                         && Object.class.equals(method.getParameterTypes()[0]));
345             }
346             private Boolean isToStringMethod(Method method, Object[] args) {
347                 return (args == null || args.length == 0) && method.getName().equals("toString");
348             }
349             @Override
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)) {
362                     return hashCode();
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();
367                 }
368                 throw new UnsupportedMethodException("Method not supported " + method.toString());
369             }
370
371             @Override
372             public boolean equals(Object obj) {
373                 if (obj == null) {
374                     return false;
375                 }
376                 TypedBaseTable<?> typedRowObj = (TypedBaseTable<?>)obj;
377                 if (row == null && typedRowObj.getRow() == null) {
378                     return true;
379                 }
380                 if (row.equals(typedRowObj.getRow())) {
381                     return true;
382                 }
383                 return false;
384             }
385
386             @Override public int hashCode() {
387                 if (row == null) {
388                     return 0;
389                 }
390                 return row.hashCode();
391             }
392
393             @Override public String toString() {
394                 String tableName;
395                 try {
396                     TableSchema<?> schema = (TableSchema<?>)processGetTableSchema();
397                     tableName = schema.getName();
398                 } catch (Exception e) {
399                     tableName = "";
400                 }
401                 if (row == null) {
402                     return tableName;
403                 }
404                 return tableName + " : " + row.toString();
405             }
406         }
407         );
408     }
409
410     /**
411      * This method extracts all row updates of Class&lt;T&gt; klazz from a TableUpdates
412      * that correspond to insertion or updates of rows of type klazz.
413      * Example:
414      * <code>
415      * Map&lt;UUID,Bridge&gt; updatedBridges = extractRowsUpdated(Bridge.class,updates,dbSchema)
416      * </code>
417      *
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&lt;UUID,T&gt; for the type of things being sought
422      */
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));
434             }
435         }
436         return result;
437     }
438
439     /**
440      * This method extracts all row updates of Class&lt;T&gt; klazz from a TableUpdates
441      * that correspond to old version of rows of type klazz that have been updated
442      * Example:
443      * <code>
444      * Map&lt;UUID,Bridge&gt; oldBridges = extractRowsOld(Bridge.class,updates,dbSchema)
445      * </code>
446      *
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&lt;UUID,T&gt; for the type of things being sought
451      */
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));
463             }
464         }
465         return result;
466     }
467
468     /**
469      * This method extracts all row updates of Class&lt;T&gt; klazz from a TableUpdates
470      * that correspond to removal of rows of type klazz.
471      * Example:
472      * <code>
473      * Map&lt;UUID,Bridge&gt; updatedBridges = extractRowsRemoved(Bridge.class,updates,dbSchema)
474      * </code>
475      *
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&lt;UUID,T&gt; for the type of things being sought
480      */
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));
492             }
493         }
494         return result;
495     }
496
497     /**
498      * This method extracts all RowUpdates of Class&lt;T&gt; klazz from a TableUpdates
499      * that correspond to rows of type klazz.
500      * Example:
501      * <code>
502      * Map&lt;UUID,TableUpdate&lt;GenericTableSchema&gt;.RowUpdate&lt;GenericTableSchema&gt;&gt; updatedBridges =
503      *     extractRowsUpdates(Bridge.class,updates,dbSchema)
504      * </code>
505      *
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&lt;UUID,TableUpdate&lt;GenericTableSchema&gt;.RowUpdate&lt;GenericTableSchema&gt;&gt;
510      *     for the type of things being sought
511      */
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 =
518                 new HashMap<>();
519         TableUpdate<GenericTableSchema> update = updates.getUpdate(TyperUtils.getTableSchema(dbSchema, klazz));
520         if (update != null) {
521             Map<UUID, TableUpdate<GenericTableSchema>.RowUpdate<GenericTableSchema>> rows = update.getRows();
522             if (rows != null) {
523                 result = rows;
524             }
525         }
526         return result;
527     }
528
529 }