View Javadoc
1   /**
2    * @author mchyzer
3    * $Id$
4    */
5   package edu.internet2.middleware.grouperClient.jdbc.tableSync;
6   
7   import java.sql.ResultSet;
8   import java.sql.ResultSetMetaData;
9   import java.sql.Types;
10  import java.util.ArrayList;
11  import java.util.Collections;
12  import java.util.Comparator;
13  import java.util.HashMap;
14  import java.util.HashSet;
15  import java.util.List;
16  import java.util.Map;
17  import java.util.Set;
18  
19  import edu.internet2.middleware.grouperClient.collections.MultiKey;
20  import edu.internet2.middleware.grouperClient.jdbc.GcDbAccess;
21  import edu.internet2.middleware.grouperClient.jdbc.GcResultSetCallback;
22  import edu.internet2.middleware.grouperClient.jdbc.tableSync.GcTableSyncColumnMetadata.ColumnType;
23  import edu.internet2.middleware.grouperClient.util.ExpirableCache;
24  import edu.internet2.middleware.grouperClient.util.GrouperClientCommonUtils;
25  import edu.internet2.middleware.grouperClient.util.GrouperClientConfig;
26  import edu.internet2.middleware.grouperClient.util.GrouperClientUtils;
27  import org.apache.commons.lang3.StringUtils;
28  import org.apache.commons.logging.Log;
29  
30  
31  /**
32   * metadata for connection name and table name
33   */
34  public class GcTableSyncTableMetadata {
35  
36    /**
37     * keep this for error handling
38     */
39    private String metadataQuery = null;
40    
41    private boolean quotesInitted = false;
42    
43    /**
44     * dont refer to this directly, call method: quoteForColumnsInSql()
45     */
46    private String quoteForColumnsInSql = "XXX";
47  
48    public String quoteForColumnsInSql() {
49      if (!quotesInitted) {
50        
51        quoteForColumnsInSql = GcDbAccess.quoteForColumnsInSql(this.connectionName);
52        quotesInitted = true;
53      }
54      return quoteForColumnsInSql;
55    }
56    
57    /**
58     * input: a,b,c
59     * output: "a","b","c"
60     * @param columns
61     * @return
62     */
63    public String quoteStrings(String columns) {
64      if (GrouperClientUtils.isBlank(columns)) {
65        return columns;
66      }
67      StringBuilder result = new StringBuilder();
68      boolean first = true;
69      for (String column : GrouperClientUtils.splitTrim(columns, ",")) {
70        if (!first) {
71          result.append(",");
72        }
73        if (column.startsWith("\"") && column.endsWith("\"")) {
74          result.append(column);
75        } else {
76          result.append(quoteForColumnsInSql()).append(column).append(quoteForColumnsInSql());
77        }
78        
79        first = false;
80      }
81      return result.toString();
82    }
83    /**
84     * append the primary key where clause
85     * @param sql
86     */
87    public String queryWherePrimaryKey() {
88  
89      if (GrouperClientUtils.length(this.getPrimaryKey()) == 0) {
90        throw new RuntimeException("No primary key for '" + this.tableName + "'!");
91      }
92      boolean first = true;
93      StringBuilder result = new StringBuilder();
94      
95      for (GcTableSyncColumnMetadata gcTableSyncColumnMetadata : this.getPrimaryKey()) {
96        
97        if (!first) {
98          result.append(" and ");
99        }
100       
101       result.append(" ").append(quoteForColumnsInSql()).append(gcTableSyncColumnMetadata.getColumnName()).append(quoteForColumnsInSql()).append(" = ? ");
102       
103       first = false;
104     }
105     
106     return result.toString();
107   }
108 
109   /**
110    * append the nonprimary key update clause
111    * @param sql
112    */
113   public String queryUpdateNonPrimaryKey() {
114     
115     if (GrouperClientUtils.length(this.getNonPrimaryKey()) == 0) {
116       throw new RuntimeException("No non-primary key for '" + this.tableName + "'!");
117     }
118     boolean first = true;
119     StringBuilder result = new StringBuilder();
120     
121     for (GcTableSyncColumnMetadata gcTableSyncColumnMetadata : this.getNonPrimaryKey()) {
122       
123       if (!first) {
124         result.append(" , ");
125       }
126       
127       result.append(" ").append(quoteForColumnsInSql()).append(gcTableSyncColumnMetadata.getColumnName()).append(quoteForColumnsInSql()).append(" = ? ");
128       
129       first = false;
130     }
131     
132     return result.toString();
133   }
134 
135   /**
136    * non primary key col(s), which is sync'ed columns with primary key removed
137    */
138   private List<GcTableSyncColumnMetadata> nonPrimaryKey;
139 
140   /**
141    * column in progress table which increments as integer or timestamp
142    */
143   private GcTableSyncColumnMetadata incrementalProgressColumn;
144   
145   /**
146    * column in progress table which increments as integer or timestamp
147    * @return column
148    */
149   public GcTableSyncColumnMetadata getIncrementalProgressColumn() {
150     return this.incrementalProgressColumn;
151   }
152 
153   /**
154    * column in progress table which increments as integer or timestamp
155    * @param incrementalProgressColumn1
156    */
157   public void setIncrementalProgressColumn(
158       GcTableSyncColumnMetadata incrementalProgressColumn1) {
159     this.incrementalProgressColumn = incrementalProgressColumn1;
160   }
161 
162   /**
163    * 
164    * @param incrementalProgressColumnName
165    */
166   public void assignIncrementalProgressColumn(String incrementalProgressColumnName) {
167     this.incrementalProgressColumn = this.lookupColumn(incrementalProgressColumnName, true);
168   }
169 
170   /**
171    * "primary key" col(s), as assigned by configuration
172    */
173   private List<GcTableSyncColumnMetadata> primaryKeyColumns;
174 
175   /**
176    * 
177    */
178   private Map<String, GcTableSyncColumnMetadata> columnUpperNameToGcColumnMetadata;
179   
180   /**
181    * find metadata for columns
182    * @param columnNames
183    * @return the list of columns
184    */
185   public List<GcTableSyncColumnMetadata> lookupColumns(String columnNames) {
186     if (GrouperClientUtils.isBlank(columnNames)) {
187       throw new RuntimeException("Pass in columns for " + this.getConnectionName() + " -> " + this.getTableName());
188     }
189     
190     List<GcTableSyncColumnMetadata> result = new ArrayList<GcTableSyncColumnMetadata>();
191     
192     if (GrouperClientUtils.equals(columnNames, "*")) {
193       result.addAll(this.getColumnMetadata());
194     } else {
195       
196       for (String columnName : GrouperClientUtils.splitTrim(columnNames, ",")) {
197         GcTableSyncColumnMetadata gcTableSyncColumnMetadata = lookupColumn(columnName, true);
198         result.add(gcTableSyncColumnMetadata);
199       }
200     }
201     
202     return result;
203   }
204   
205   /**
206    * lookup a column by name (case insensitive)
207    * @param columnName
208    * @param exceptionOnNotFound
209    * @return the column metadata
210    */
211   public GcTableSyncColumnMetadata lookupColumn(String columnName, boolean exceptionOnNotFound) {
212     
213     if (this.columnUpperNameToGcColumnMetadata == null) {
214       
215       if (GrouperClientUtils.length(this.columnMetadata) == 0) {
216         throw new RuntimeException("Cant find table metadata for " + this.connectionName + " -> " + this.tableName + "!");
217       }
218       
219       Map<String, GcTableSyncColumnMetadata> tempColumnUpperNameToGcColumnMetadata = new HashMap<String, GcTableSyncColumnMetadata>();
220       
221       for (GcTableSyncColumnMetadata gcTableSyncColumnMetadata : this.columnMetadata) {
222         tempColumnUpperNameToGcColumnMetadata.put(gcTableSyncColumnMetadata.getColumnName().toUpperCase(), gcTableSyncColumnMetadata);
223       }
224       
225       this.columnUpperNameToGcColumnMetadata = tempColumnUpperNameToGcColumnMetadata;
226     }
227     
228     GcTableSyncColumnMetadata gcTableSyncColumnMetadata = this.columnUpperNameToGcColumnMetadata.get(columnName.toUpperCase());
229     
230     if (gcTableSyncColumnMetadata == null && exceptionOnNotFound) {
231       throw new RuntimeException("Cant find " + this.connectionName + " -> " 
232           + (GrouperClientUtils.isBlank(this.tableName) ? ("(" + this.metadataQuery + ")") : this.tableName) + " -> " + columnName);
233     }
234     
235     return gcTableSyncColumnMetadata;
236     
237   }
238   
239   /**
240    * cache database metadata for a little while
241    */
242   private static ExpirableCache<MultiKey, GcTableSyncTableMetadata> metadataCache = null;
243 
244   /**
245    * cache metadata for tables for a little while
246    * @return the cache
247    */
248   private static ExpirableCache<MultiKey, GcTableSyncTableMetadata> metadataCache() {
249     if (metadataCache == null) {
250       metadataCache = new ExpirableCache(GrouperClientConfig.retrieveConfig().propertyValueInt("tableSyncMetadataCacheMinutes", 10));
251     }
252     return metadataCache;
253   }
254 
255   /**
256    * get metadata for table
257    * @param connectionName
258    * @param tableName
259    * @return the metadata for a connection, table, and query
260    */
261   public static GcTableSyncTableMetadata retrieveTableMetadataFromCacheOrDatabase(String connectionName, String tableName) {
262     
263     MultiKeyrouperClient/collections/MultiKey.html#MultiKey">MultiKey multiKey = new MultiKey(connectionName, tableName);
264     GcTableSyncTableMetadata gcTableSyncTableMetadata = metadataCache().get(multiKey);
265     if (gcTableSyncTableMetadata != null) {
266       return gcTableSyncTableMetadata;
267     }
268     
269     gcTableSyncTableMetadata = retrieveTableMetadataFromDatabase(connectionName, tableName);
270     metadataCache().put(multiKey, gcTableSyncTableMetadata);
271     return gcTableSyncTableMetadata;
272   }
273 
274   /**
275    * get metadata for table
276    * @param connectionName
277    * @param query
278    * @return the metadata for a connection, table, and query
279    */
280   public static GcTableSyncTableMetadata retrieveQueryMetadataFromCacheOrDatabase(String connectionName, String query) {
281     
282     MultiKeyrouperClient/collections/MultiKey.html#MultiKey">MultiKey multiKey = new MultiKey(connectionName, query);
283     GcTableSyncTableMetadata gcTableSyncTableMetadata = metadataCache().get(multiKey);
284     if (gcTableSyncTableMetadata != null) {
285       return gcTableSyncTableMetadata;
286     }
287     
288     gcTableSyncTableMetadata = retrieveQueryMetadataFromDatabase(connectionName, query);
289     metadataCache().put(multiKey, gcTableSyncTableMetadata);
290     return gcTableSyncTableMetadata;
291   }
292 
293   /**
294    * get metadata for table
295    * @param theConnectionName
296    * @param tableName
297    * @return the metadata for a connection, table, and query
298    */
299   public static GcTableSyncTableMetadata retrieveTableMetadataFromDatabase(String theConnectionName, String tableName) {
300     
301     if (GrouperClientUtils.isBlank(tableName)) {
302       throw new RuntimeException("tableName cannot be blank");
303     }
304     
305     String sql = "select * from " + tableName;
306     GcTableSyncTableMetadata gcTableSyncTableMetadata = retrieveQueryMetadataFromDatabase(theConnectionName, sql);
307     if (GrouperClientUtils.isBlank(gcTableSyncTableMetadata.getTableName())) {
308       gcTableSyncTableMetadata.setTableName(tableName);
309     }
310     return gcTableSyncTableMetadata;
311   }
312 
313   /**
314    * get metadata for table
315    * @param theConnectionName
316    * @param tableName
317    * @return the metadata for a connection, table, and query
318    */
319   public static GcTableSyncTableMetadata retrieveQueryMetadataFromDatabase(String theConnectionName, String query) {
320     return retrieveQueryMetadataFromDatabase(theConnectionName, query, null);
321   }
322 
323   /**
324    * get metadata for table
325    * @param theConnectionName
326    * @param tableName
327    * @return the metadata for a connection, table, and query
328    */
329   public static GcTableSyncTableMetadata retrieveQueryMetadataFromDatabase(String theConnectionName, String query, List<Object> bindVars) {
330     
331     if (GrouperClientUtils.isBlank(query)) {
332       throw new RuntimeException("query cannot be blank");
333     }
334     if (GrouperClientUtils.isBlank(theConnectionName)) {
335       throw new RuntimeException("connectionName cannot be blank");
336     }
337     
338     if (StringUtils.endsWith(query, ";")) {
339       query = query.substring(0, query.length() - 1);
340     }
341     
342     //  + " where 1 != 1"
343     // does it already have no records?
344     // this isnt going to be perfect and is meant for simple queries without weird whitespace etc
345     if (!query.contains("1!=1") && !query.contains("1 != 1")
346         && !query.contains("0 = 1") && !query.contains("0=1") && !query.contains("1 = 0") && !query.contains("1=0")) {
347       // if union just run query
348       if (!query.toLowerCase().contains(" union ")) {
349         // does it have a where?
350         if (query.contains(" where ") || query.contains(" WHERE ")) {
351           // replace like this so it works with order by, group by, etc
352           // note this wont work with union
353           query = GrouperClientUtils.replace(query, " where ", " where 1!=1 and ");
354           query = GrouperClientUtils.replace(query, " WHERE ", " where 1!=1 and ");
355         } else {
356           if (!query.toLowerCase().contains("where") && !query.toLowerCase().contains(" order ")
357               && !query.toLowerCase().contains(" group ")) {
358             query += " where 1!=1";
359           }
360         }
361       }
362     }
363     
364     GcTableSyncTableMetadatableSyncTableMetadata.html#GcTableSyncTableMetadata">GcTableSyncTableMetadata gcTableSyncTableMetadata = new GcTableSyncTableMetadata();
365     gcTableSyncTableMetadata.metadataQuery = query;
366     gcTableSyncTableMetadata.setConnectionName(theConnectionName);
367     
368     final ArrayList<GcTableSyncColumnMetadata> gcTableSyncColumnMetadatas = new ArrayList<GcTableSyncColumnMetadata>();
369     gcTableSyncTableMetadata.setColumnMetadata(gcTableSyncColumnMetadatas);
370     
371     try {
372       // go to database from and look up metadata
373       GcDbAccesserClient/jdbc/GcDbAccess.html#GcDbAccess">GcDbAccess gcDbAccess = new GcDbAccess().connectionName(theConnectionName).sql(query);
374       
375       if (bindVars != null) {
376         gcDbAccess.bindVars(bindVars);
377       }
378       
379       gcDbAccess.callbackResultSet(new GcResultSetCallback() {
380   
381         @Override
382         public Object callback(ResultSet resultSet) throws Exception {
383           
384           ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
385   
386 //          if (resultSetMetaData.getColumnCount() > 0) {
387 //            gcTableSyncTableMetadata.setTableName(resultSetMetaData.getTableName(1));
388 //          }
389           
390           for (int i=0;i<resultSetMetaData.getColumnCount();i++) {
391             GcTableSyncColumnMetadataeSyncColumnMetadata.html#GcTableSyncColumnMetadata">GcTableSyncColumnMetadata gcTableSyncColumnMetadata = new GcTableSyncColumnMetadata();
392             // label is the alias and column name is the column name
393             String columnName = GrouperClientUtils.defaultIfBlank(resultSetMetaData.getColumnLabel(i+1), resultSetMetaData.getColumnName(i+1));
394             int dataType = resultSetMetaData.getColumnType(i+1);
395             String typeName = resultSetMetaData.getColumnTypeName(i+1);
396   
397             gcTableSyncColumnMetadata.setColumnIndexZeroIndexed(i);
398             
399             gcTableSyncColumnMetadata.setColumnName(columnName);
400             // dont add to set until the column name is set since it orders by that
401             gcTableSyncColumnMetadatas.add(gcTableSyncColumnMetadata);
402 
403             switch (dataType) {
404               case Types.BIGINT: 
405               case Types.DECIMAL:
406               case Types.DOUBLE:
407               case Types.FLOAT:
408               case Types.INTEGER:
409               case Types.NUMERIC:
410               case Types.REAL:
411               case Types.SMALLINT:
412               case Types.TINYINT:
413                 
414                 gcTableSyncColumnMetadata.setColumnType(ColumnType.NUMERIC);
415                 {
416                   int precision = resultSetMetaData.getPrecision(i+1);
417                   gcTableSyncColumnMetadata.setPrecision(precision);
418                 }
419   
420                 {
421                   int scale = resultSetMetaData.getScale(i+1);
422                   gcTableSyncColumnMetadata.setScale(scale);
423                 }
424                 break;
425                 
426               case Types.BIT:
427               case Types.BOOLEAN:
428   
429                 gcTableSyncColumnMetadata.setColumnType(ColumnType.BOOLEAN);
430                 {
431                   int columnDisplaySize = resultSetMetaData.getColumnDisplaySize(i+1);
432                   gcTableSyncColumnMetadata.setColumnDisplaySize(columnDisplaySize);
433                 }
434                 break;
435               case Types.CHAR:
436               case Types.VARCHAR:
437               case Types.LONGVARCHAR:
438               case Types.NCHAR:
439               case Types.NVARCHAR:
440               case Types.LONGNVARCHAR:
441   
442                 gcTableSyncColumnMetadata.setColumnType(ColumnType.STRING);
443                 {
444                   int columnDisplaySize = resultSetMetaData.getColumnDisplaySize(i+1);
445                   gcTableSyncColumnMetadata.setColumnDisplaySize(columnDisplaySize);
446                 }
447                 break;
448   
449               case Types.DATE:
450               case Types.TIMESTAMP:
451                 
452                 gcTableSyncColumnMetadata.setColumnType(ColumnType.TIMESTAMP);
453                 break; 
454                 
455               case Types.OTHER: 
456 
457                 gcTableSyncColumnMetadata.setColumnType(ColumnType.UUID);
458                 break; 
459               
460               default:
461                 throw new RuntimeException("Type not supported: " + dataType + ", " + typeName);
462                 
463   
464             }
465           }
466           
467           Collections.sort(gcTableSyncColumnMetadatas, new Comparator<GcTableSyncColumnMetadata>() {
468   
469             @Override
470             public int compare(GcTableSyncColumnMetadata./../edu/internet2/middleware/grouperClient/jdbc/tableSync/GcTableSyncColumnMetadata.html#GcTableSyncColumnMetadata">GcTableSyncColumnMetadata o1, GcTableSyncColumnMetadata o2) {
471               
472               if (o1 == o2) {
473                 return 0;
474               }
475               if (o1 == null) {
476                 return -1;
477               }
478               if (o2 == null) {
479                 return 1;
480               }
481               return o1.getColumnName().toLowerCase().compareTo(o2.getColumnName().toLowerCase());
482             }
483           });
484           
485           return null;
486         }
487         
488       });
489     } catch (RuntimeException e) {      
490       RuntimeException e2 = GrouperClientCommonUtils.createRuntimeExceptionWithMessage(e, "Error finding metadata for '" + query + "' in database: '" + theConnectionName + "'");
491       throw e2;
492     }
493 
494     if (gcTableSyncColumnMetadatas.size() == 0) {
495       throw new RuntimeException("Cant find table metadata for '" + query + "' in database: '" + theConnectionName + "'");
496     }
497 
498     return gcTableSyncTableMetadata;
499   }
500 
501 
502   
503   public String getMetadataQuery() {
504     return metadataQuery;
505   }
506 
507   /**
508    * @return the connectionName
509    */
510   public String getConnectionName() {
511     return this.connectionName;
512   }
513 
514   
515   /**
516    * @param connectionName the connectionName to set
517    */
518   public void setConnectionName(String connectionName) {
519     this.connectionName = connectionName;
520   }
521 
522   
523   /**
524    * @return the tableName
525    */
526   public String getTableName() {
527     return this.tableName;
528   }
529 
530   
531   /**
532    * @param tableName the tableName to set
533    */
534   public void setTableName(String tableName) {
535     this.tableName = tableName;
536   }
537 
538   /** 
539    * database connection name
540    */
541   private String connectionName;
542   
543   /**
544    * database connection name or readonly
545    */
546   private String connectionNameOrReadonly;
547   
548   /**
549    * database connection name or readonly
550    * @return connection name
551    */
552   public String getConnectionNameOrReadonly() {
553     return this.connectionNameOrReadonly;
554   }
555 
556   /**
557    * database connection name or readonly
558    * @param connectionNameOrReadonly1
559    */
560   public void setConnectionNameOrReadonly(String connectionNameOrReadonly1) {
561     this.connectionNameOrReadonly = connectionNameOrReadonly1;
562   }
563 
564   /**
565    * table name (could include schema at front)
566    */
567   private String tableName;
568   
569   /**
570    * 
571    */
572   public GcTableSyncTableMetadata() {
573   }
574   
575   /**
576    * columns in table
577    */
578   private List<GcTableSyncColumnMetadata> columnMetadata;
579 
580   /**
581    * all columns to sync that we care about
582    */
583   private List<GcTableSyncColumnMetadata> columns;
584 
585   /**
586    * 
587    */
588   private static Log LOG = GrouperClientUtils.retrieveLog(GcTableSyncTableMetadata.class);
589 
590   /**
591    * if group this is the group column
592    */
593   private GcTableSyncColumnMetadata groupColumn;
594 
595   /**
596    * if full sync with change flag this is the column
597    */
598   private GcTableSyncColumnMetadata changeFlagColumn;
599 
600   /**
601    * column in FROM table which has incrementing timestamp or integer
602    */
603   private GcTableSyncColumnMetadata incrementalAllCoumnsColumn;
604 
605   /**
606    * column in FROM table which has incrementing timestamp or integer
607    * @return metadata
608    */
609   public GcTableSyncColumnMetadata getIncrementalAllCoumnsColumn() {
610     return this.incrementalAllCoumnsColumn;
611   }
612 
613   /**
614    * column in FROM table which has incrementing timestamp or integer
615    * @param incrementalAllCoumnsColumn1
616    */
617   public void setIncrementalAllCoumnsColumn(
618       GcTableSyncColumnMetadata incrementalAllCoumnsColumn1) {
619     this.incrementalAllCoumnsColumn = incrementalAllCoumnsColumn1;
620   }
621 
622   /**
623    * if full sync with change flag this is the column
624    * @return change flag
625    */
626   public GcTableSyncColumnMetadata getChangeFlagColumn() {
627     return this.changeFlagColumn;
628   }
629 
630   /**
631    * if full sync with change flag this is the column
632    * @param changeFlagColumn1
633    */
634   public void setChangeFlagColumn(GcTableSyncColumnMetadata changeFlagColumn1) {
635     this.changeFlagColumn = changeFlagColumn1;
636   }
637 
638   /**
639    * columns in table
640    * @return the columnMetadata
641    */
642   public List<GcTableSyncColumnMetadata> getColumnMetadata() {
643     return this.columnMetadata;
644   }
645 
646   /**
647    * columns in table
648    * @return the columnMetadata
649    */
650   public List<GcTableSyncColumnMetadata> retrieveColumnMetadataOrdered() {
651     if (this.columnMetadata == null) {
652       return null;
653     }
654     List<GcTableSyncColumnMetadata> result = new ArrayList<>(this.columnMetadata);
655     for (GcTableSyncColumnMetadata gcTableSyncColumnMetadata : this.columnMetadata) {
656       result.set(gcTableSyncColumnMetadata.getColumnIndexZeroIndexed(), gcTableSyncColumnMetadata);
657     }
658     return result;
659   }
660 
661   
662   /**
663    * columns in table
664    * @param columnMetadata1 the columnMetadata to set
665    */
666   public void setColumnMetadata(List<GcTableSyncColumnMetadata> columnMetadata1) {
667     this.columnMetadata = columnMetadata1;
668   }
669 
670   /**
671    * @param theColumns could be * or list of columns
672    */
673   public void assignPrimaryKeyColumns(String theColumns) {
674     this.primaryKeyColumns = this.lookupColumns(theColumns);
675   }
676 
677   /**
678    * mtadata for columns synced
679    * @return the columns
680    */
681   public List<GcTableSyncColumnMetadata> getColumns() {
682     return this.columns;
683   }
684 
685   /**
686    * non primary key col(s), lazy loaded
687    * @return the primary key
688    */
689   public List<GcTableSyncColumnMetadata> getNonPrimaryKey() {
690     if (this.nonPrimaryKey == null) {
691       
692       List<GcTableSyncColumnMetadata> result = new ArrayList();
693       result.addAll(this.getColumns());
694       result.removeAll(this.getPrimaryKey());
695       this.nonPrimaryKey = result;
696     }
697     
698     return this.nonPrimaryKey;
699   }
700 
701   /**
702    * primary key col(s), lazy loaded
703    * @return the primary key
704    */
705   public List<GcTableSyncColumnMetadata> getPrimaryKey() {
706     return this.primaryKeyColumns;
707   }
708 
709   /**
710    * @param theColumns could be * or list of columns
711    */
712   public void assignColumns(String theColumns) {
713     this.columns = this.lookupColumns(theColumns);
714   }
715 
716   /**
717    * get comma separated list of all columns
718    * @return the columns
719    */
720   public String columnListAll() {
721     StringBuilder result = new StringBuilder();
722     boolean first = true;
723     for (GcTableSyncColumnMetadata gcTableSyncColumnMetadata : GrouperClientUtils.nonNull(this.columns)) {
724       if (!first) {
725         result.append(", ");
726       }
727       result.append(gcTableSyncColumnMetadata.getColumnName());
728       
729       first = false;
730     }
731     return result.toString();
732   }
733   
734   /**
735    * get comma separated list of all columns
736    * @return the columns
737    */
738   public String columnListAllQuoted() {
739     StringBuilder result = new StringBuilder();
740     boolean first = true;
741     for (GcTableSyncColumnMetadata gcTableSyncColumnMetadata : GrouperClientUtils.nonNull(this.columns)) {
742       if (!first) {
743         result.append(", ");
744       }
745       result.append(quoteForColumnsInSql()).append(gcTableSyncColumnMetadata.getColumnName()).append(quoteForColumnsInSql());
746       
747       first = false;
748     }
749     return result.toString();
750   }
751   
752   /**
753    * get comma separated list of primary key and change flag, and optional incremental change column
754    * @return the columns
755    */
756   public String columnListPrimaryKeyAndChangeFlagAndOptionalIncrementalProgress() {
757     StringBuilder result = new StringBuilder();
758 
759     Set<String> columnNames = new HashSet<String>();
760     boolean first = true;
761     for (GcTableSyncColumnMetadata gcTableSyncColumnMetadata : GrouperClientUtils.nonNull(this.columns)) {
762 
763       if (!first) {
764         result.append(", ");
765       }
766 
767       result.append(gcTableSyncColumnMetadata.getColumnName());
768       columnNames.add(gcTableSyncColumnMetadata.getColumnName());
769       first = false;
770     }
771     if (!columnNames.contains(this.getChangeFlagColumn().getColumnName())) {
772       result.append(", ");
773       result.append(this.getChangeFlagColumn().getColumnName());
774       columnNames.add(this.getChangeFlagColumn().getColumnName());
775     }
776     if (this.getIncrementalAllCoumnsColumn() != null) {
777       if (!columnNames.contains(this.getIncrementalAllCoumnsColumn().getColumnName())) {
778         result.append(", ");
779         result.append(this.getIncrementalAllCoumnsColumn().getColumnName());
780         columnNames.add(this.getChangeFlagColumn().getColumnName());
781       }
782     }
783     return result.toString();
784   }
785   
786   /**
787    * get comma separated list of primary key and change flag
788    * @return the columns
789    */
790   public String columnListInputtedColumnsAndIncrementalProgressColumn(List<GcTableSyncColumnMetadata> otherTablePrimaryKey) {
791     StringBuilder result = new StringBuilder();
792 
793     for (GcTableSyncColumnMetadata gcTableSyncColumnMetadataOther : otherTablePrimaryKey) {
794 
795       GcTableSyncColumnMetadata gcTableSyncColumnMetadataThis = this.lookupColumn(gcTableSyncColumnMetadataOther.getColumnName(), true);
796       result.append(gcTableSyncColumnMetadataThis.getColumnName());
797       
798       result.append(", ");
799     }
800     result.append(this.getIncrementalProgressColumn().getColumnName());
801     return result.toString();
802   }
803   
804   /**
805    * mtadata for columns synced
806    * @param columns1 the columns to set
807    */
808   public void setColumns(List<GcTableSyncColumnMetadata> columns1) {
809     this.columns = columns1;
810   }
811 
812   /**
813    * 
814    * @param groupColumnName
815    */
816   public void assignGroupColumn(String groupColumnName) {
817     this.groupColumn = this.lookupColumn(groupColumnName, true);
818   }
819 
820   /**
821    * 
822    * @param changeFlagColumnName
823    */
824   public void assignChangeFlagColumn(String changeFlagColumnName) {
825     this.changeFlagColumn = this.lookupColumn(changeFlagColumnName, true);
826   }
827 
828   /**
829    * get group column metadata
830    * @return the metadata
831    */
832   public GcTableSyncColumnMetadata getGroupColumnMetadata() {
833     return this.groupColumn;
834   }
835 
836   /**
837    * 
838    * @param incrementalAllColumnsColumnName
839    */
840   public void assignIncrementalAllCoumnsColumn(String incrementalAllColumnsColumnName) {
841     this.incrementalAllCoumnsColumn = this.lookupColumn(incrementalAllColumnsColumnName, true);
842   }
843   
844 }