1   /*
2    * Copyright 2003 - 2013 The eFaps Team
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   *
16   * Revision:        $Rev$
17   * Last Changed:    $Date$
18   * Last Changed By: $Author$
19   */
20  
21  package org.efaps.admin.datamodel;
22  
23  import java.sql.Connection;
24  import java.sql.PreparedStatement;
25  import java.sql.ResultSet;
26  import java.sql.SQLException;
27  import java.util.Collections;
28  import java.util.HashSet;
29  import java.util.Set;
30  import java.util.UUID;
31  
32  import org.efaps.db.Context;
33  import org.efaps.db.databases.information.TableInformation;
34  import org.efaps.db.transaction.ConnectionResource;
35  import org.efaps.db.wrapper.SQLPart;
36  import org.efaps.db.wrapper.SQLSelect;
37  import org.efaps.util.EFapsException;
38  import org.efaps.util.cache.CacheLogListener;
39  import org.efaps.util.cache.CacheReloadException;
40  import org.efaps.util.cache.InfinispanCache;
41  import org.infinispan.Cache;
42  import org.slf4j.Logger;
43  import org.slf4j.LoggerFactory;
44  
45  /**
46   * This is the class for the table description. The table description holds
47   * information in which table attributes are stored.
48   *
49   * @author The eFaps Team
50   * @version $Id$
51   */
52  public final class SQLTable
53      extends AbstractDataModelObject
54  {
55      /**
56       * Needed for serialization.
57       */
58      private static final long serialVersionUID = 1L;
59  
60      /**
61       * Logging instance used in this class.
62       */
63      private static final Logger LOG = LoggerFactory.getLogger(SQLTable.class);
64  
65      /**
66       * This is the SQL select statement to select a role from the database by
67       * ID.
68       */
69      private static final String SQL_ID = new SQLSelect()
70                      .column("ID")
71                      .column("UUID")
72                      .column("NAME")
73                      .column("SQLTABLE")
74                      .column("SQLCOLUMNID")
75                      .column("SQLCOLUMNTYPE")
76                      .column("DMTABLEMAIN")
77                      .from("V_ADMINSQLTABLE", 0)
78                      .addPart(SQLPart.WHERE).addColumnPart(0, "ID").addPart(SQLPart.EQUAL).addValuePart("?").toString();
79  
80      /**
81       * This is the SQL select statement to select a role from the database by
82       * Name.
83       */
84      private static final String SQL_NAME = new SQLSelect()
85                      .column("ID")
86                      .column("UUID")
87                      .column("NAME")
88                      .column("SQLTABLE")
89                      .column("SQLCOLUMNID")
90                      .column("SQLCOLUMNTYPE")
91                      .column("DMTABLEMAIN")
92                      .from("V_ADMINSQLTABLE", 0)
93                      .addPart(SQLPart.WHERE).addColumnPart(0, "NAME").addPart(SQLPart.EQUAL).addValuePart("?")
94                      .toString();
95  
96      /**
97       * This is the SQL select statement to select a role from the database by
98       * UUID.
99       */
100     private static final String SQL_UUID = new SQLSelect()
101                     .column("ID")
102                     .column("UUID")
103                     .column("NAME")
104                     .column("SQLTABLE")
105                     .column("SQLCOLUMNID")
106                     .column("SQLCOLUMNTYPE")
107                     .column("DMTABLEMAIN")
108                     .from("V_ADMINSQLTABLE", 0)
109                     .addPart(SQLPart.WHERE).addColumnPart(0, "UUID").addPart(SQLPart.EQUAL).addValuePart("?")
110                     .toString();
111 
112     /**
113      * Name of the Cache by UUID.
114      */
115     private static final String UUIDCACHE = SQLTable.class.getName() + ".UUID";
116 
117     /**
118      * Name of the Cache by ID.
119      */
120     private static final String IDCACHE =  SQLTable.class.getName() + ".ID";
121 
122     /**
123      * Name of the Cache by Name.
124      */
125     private static final String NAMECACHE =  SQLTable.class.getName() + ".Name";
126 
127     /**
128      * Instance variable for the name of the SQL table.
129      *
130      * @see #getSqlTable
131      */
132     private final String sqlTable;
133 
134     /**
135      * This instance variable stores the SQL column name of the id of a table.
136      *
137      * @see #getSqlColId
138      */
139     private final String sqlColId;
140 
141     /**
142      * The instance variable stores the SQL column name of the type id.
143      *
144      * @see #getSqlColType
145      */
146     private final String sqlColType;
147 
148     /**
149      * Stores the information about the SQL table within the database.
150      *
151      * @see #getTableInformation
152      */
153     private final TableInformation tableInformation;
154 
155     /**
156      * The instance variable stores the main table for this table instance. The
157      * main table is the table, which holds the information about the SQL select
158      * statement to get a new id. Also the main table must be inserted as first
159      * insert (e.g. the id in the table with a main table has a foreign key to
160      * the id of the main table).
161      *
162      * @see #getMainTable()
163      */
164     private SQLTable mainTable = null;
165 
166     /**
167      * The instance variable stores all types which stores information in this
168      * table.
169      *
170      * @see #getTypes()
171      */
172     private final Set<Long> types = new HashSet<Long>();
173 
174     /**
175      * The instance variables is set to <i>true</i> if this table is only a read
176      * only SQL table. This means, that no insert and no update on this table is
177      * allowed and made.
178      *
179      * @see #isReadOnly()
180      */
181     private boolean readOnly = false;
182 
183     /**
184      * This is the constructor for class {@link Attribute}. Every instance of
185      * class {@link Attribute} must have a name (parameter <i>_name</i>) and an
186      * identifier (parameter <i>_id</i>).
187      *
188      * @param _con Connection
189      * @param _id eFaps id of the SQL table
190      * @param _uuid unique identifier
191      * @param _name eFaps name of the SQL table
192      * @param _sqlTable name of the SQL Table in the database
193      * @param _sqlColId name of column for the id within SQL table
194      * @param _sqlColType name of column for the type within SQL table
195      * @throws SQLException on error
196      */
197     private SQLTable(final Connection _con,
198                      final long _id,
199                      final String _uuid,
200                      final String _name,
201                      final String _sqlTable,
202                      final String _sqlColId,
203                      final String _sqlColType)
204         throws SQLException
205     {
206         super(_id, _uuid, _name);
207         this.sqlTable = _sqlTable.trim();
208         this.sqlColId = _sqlColId.trim();
209         this.sqlColType = (_sqlColType != null) ? _sqlColType.trim() : null;
210         this.tableInformation = Context.getDbType().getCachedTableInformation(this.sqlTable);
211     }
212 
213     /**
214      * The instance method adds a new type to the type list.
215      *
216      * @param _typeId id of the Type to add
217      * @see #types
218      */
219     protected void addType(final Long _typeId)
220     {
221         if (!this.types.contains(_typeId)) {
222             this.types.add(_typeId);
223             setDirty();
224         }
225     }
226 
227     /**
228      * The instance method sets a new property value.
229      *
230      * @param _name name of the property
231      * @param _value value of the property
232      * @throws CacheReloadException on error
233      */
234     @Override
235     protected void setProperty(final String _name,
236                                final String _value)
237         throws CacheReloadException
238     {
239         if (_name.equals("ReadOnly")) {
240             this.readOnly = "true".equalsIgnoreCase("true");
241         }
242     }
243 
244     /**
245      * This is the getter method for instance variable {@link #sqlTable}.
246      *
247      * @return value of instance variable {@link #sqlTable}
248      * @see #sqlTable
249      */
250     public String getSqlTable()
251     {
252         return this.sqlTable;
253     }
254 
255     /**
256      * This is the getter method for instance variable {@link #sqlColId}.
257      *
258      * @return value of instance variable {@link #sqlColId}
259      * @see #sqlColId
260      */
261     public String getSqlColId()
262     {
263         return this.sqlColId;
264     }
265 
266     /**
267      * This is the getter method for instance variable {@link #sqlColType}.
268      *
269      * @return value of instance variable {@link #sqlColType}
270      * @see #sqlColType
271      */
272     public String getSqlColType()
273     {
274         return this.sqlColType;
275     }
276 
277     /**
278      * This is the getter method for instance variable {@link #tableInformation}
279      * .
280      *
281      * @return value of instance variable {@link #tableInformation}
282      * @see #tableInformation
283      */
284     public TableInformation getTableInformation()
285     {
286         return this.tableInformation;
287     }
288 
289     /**
290      * This is the getter method for instance variable {@link #mainTable}.
291      *
292      * @return value of instance variable {@link #mainTable}
293      * @see #mainTable
294      */
295     public SQLTable getMainTable()
296     {
297         return this.mainTable;
298     }
299 
300     /**
301      * This is the getter method for instance variable {@link #types}.
302      *
303      * @return value of instance variable {@link #types}
304      * @see #types
305      * @throws CacheReloadException on error
306      */
307     public Set<Type> getTypes()
308         throws CacheReloadException
309     {
310         final Set<Type> ret = new HashSet<Type>();
311         for (final Long id : this.types) {
312             ret.add(Type.get(id));
313         }
314         return Collections.unmodifiableSet(ret);
315     }
316 
317     /**
318      * This is the getter method for instance variable {@link #readOnly}.
319      *
320      * @return value of instance variable {@link #readOnly}
321      * @see #readOnly
322      */
323     public boolean isReadOnly()
324     {
325         return this.readOnly;
326     }
327 
328     @Override
329     public boolean equals(final Object _obj)
330     {
331         boolean ret;
332         if (_obj instanceof SQLTable) {
333             ret = ((SQLTable) _obj).getId() == getId();
334         } else {
335             ret = super.equals(_obj);
336         }
337         return ret;
338     }
339 
340     @Override
341     public int hashCode()
342     {
343         return  Long.valueOf(getId()).intValue();
344     }
345 
346     /**
347      * Method to initialize the Cache of this CacheObjectInterface.
348      *
349      * @param _class Clas that started the initialization
350      */
351     public static void initialize(final Class<?> _class)
352     {
353         if (InfinispanCache.get().exists(SQLTable.UUIDCACHE)) {
354             InfinispanCache.get().<UUID, SQLTable>getCache(SQLTable.UUIDCACHE).clear();
355         } else {
356             InfinispanCache.get().<UUID, SQLTable>getCache(SQLTable.UUIDCACHE)
357                             .addListener(new CacheLogListener(SQLTable.LOG));
358         }
359         if (InfinispanCache.get().exists(SQLTable.IDCACHE)) {
360             InfinispanCache.get().<Long, SQLTable>getCache(SQLTable.IDCACHE).clear();
361         } else {
362             InfinispanCache.get().<Long, SQLTable>getCache(SQLTable.IDCACHE)
363                             .addListener(new CacheLogListener(SQLTable.LOG));
364         }
365         if (InfinispanCache.get().exists(SQLTable.NAMECACHE)) {
366             InfinispanCache.get().<String, SQLTable>getCache(SQLTable.NAMECACHE).clear();
367         } else {
368             InfinispanCache.get().<String, SQLTable>getCache(SQLTable.NAMECACHE)
369                             .addListener(new CacheLogListener(SQLTable.LOG));
370         }
371     }
372 
373     /**
374      * Method to initialize the Cache of this CacheObjectInterface.
375      */
376     public static void initialize()
377     {
378         SQLTable.initialize(SQLTable.class);
379     }
380 
381     /**
382      * Returns for given parameter <i>_id</i> the instance of class
383      * {@link SQLTable}.
384      *
385      * @param _id id to search in the cache
386      * @return instance of class {@link SQLTable}
387      * @throws CacheReloadException on error
388      * @see #getCache
389      */
390     public static SQLTable get(final long _id)
391         throws CacheReloadException
392     {
393         final Cache<Long, SQLTable> cache = InfinispanCache.get().<Long, SQLTable>getCache(SQLTable.IDCACHE);
394         if (!cache.containsKey(_id)) {
395             SQLTable.getSQLTableFromDB(SQLTable.SQL_ID, _id);
396         }
397         return cache.get(_id);
398     }
399 
400     /**
401      * Returns for given parameter <i>_name</i> the instance of class
402      * {@link SQLTable}.
403      *
404      * @param _name name to search in the cache
405      * @return instance of class {@link SQLTable}
406      * @throws CacheReloadException on error
407      * @see #getCache
408      */
409     public static SQLTable get(final String _name)
410         throws CacheReloadException
411     {
412         final Cache<String, SQLTable> cache = InfinispanCache.get().<String, SQLTable>getCache(SQLTable.NAMECACHE);
413         if (!cache.containsKey(_name)) {
414             SQLTable.getSQLTableFromDB(SQLTable.SQL_NAME, _name);
415         }
416         return cache.get(_name);
417     }
418 
419     /**
420      * Returns for given parameter <i>_uuid</i> the instance of class
421      * {@link SQLTable}.
422      *
423      * @param _uuid UUID the tanel is wanted for
424      * @return instance of class {@link Type}
425      * @throws CacheReloadException on error
426      */
427     public static SQLTable get(final UUID _uuid)
428         throws CacheReloadException
429     {
430         final Cache<UUID, SQLTable> cache = InfinispanCache.get().<UUID, SQLTable>getCache(SQLTable.UUIDCACHE);
431         if (!cache.containsKey(_uuid)) {
432             SQLTable.getSQLTableFromDB(SQLTable.SQL_UUID, String.valueOf(_uuid));
433         }
434         return cache.get(_uuid);
435     }
436 
437     /**
438      * @param _sqlTable SQLTable to be cached
439      */
440     private static void cacheSQLTable(final SQLTable _sqlTable)
441     {
442         final Cache<UUID, SQLTable> cache4UUID = InfinispanCache.get().<UUID, SQLTable>getIgnReCache(
443                         SQLTable.UUIDCACHE);
444         cache4UUID.putIfAbsent(_sqlTable.getUUID(), _sqlTable);
445 
446         final Cache<String, SQLTable> nameCache = InfinispanCache.get().<String, SQLTable>getIgnReCache(
447                         SQLTable.NAMECACHE);
448         nameCache.putIfAbsent(_sqlTable.getName(), _sqlTable);
449         final Cache<Long, SQLTable> idCache = InfinispanCache.get().<Long, SQLTable>getIgnReCache(
450                         SQLTable.IDCACHE);
451         idCache.putIfAbsent(_sqlTable.getId(), _sqlTable);
452     }
453 
454     /**
455      * @param _sql      SQL Statement to be executed
456      * @param _criteria filter criteria
457      * @return true if successful
458      * @throws CacheReloadException on error
459      */
460     private static boolean getSQLTableFromDB(final String _sql,
461                                              final Object _criteria)
462         throws CacheReloadException
463     {
464         final boolean ret = false;
465         ConnectionResource con = null;
466         try {
467             SQLTable table = null;
468             long tableMainId = 0;
469             con = Context.getThreadContext().getConnectionResource();
470             PreparedStatement stmt = null;
471             try {
472                 stmt = con.getConnection().prepareStatement(_sql);
473                 stmt.setObject(1, _criteria);
474                 final ResultSet rs = stmt.executeQuery();
475                 if (rs.next()) {
476                     final long id = rs.getLong(1);
477                     final String name = rs.getString(3).trim();
478                     table = new SQLTable(con.getConnection(),
479                                     id,
480                                     rs.getString(2),
481                                     name,
482                                     rs.getString(4),
483                                     rs.getString(5),
484                                     rs.getString(6));
485                     tableMainId = rs.getLong(7);
486                     SQLTable.cacheSQLTable(table);
487                     SQLTable.LOG.debug("read SQLTable '{}' (id = {}))", name, id);
488                 }
489                 rs.close();
490             } finally {
491                 if (stmt != null) {
492                     stmt.close();
493                 }
494             }
495             con.commit();
496             if (table != null) {
497                 table.readFromDB4Properties();
498                 if (tableMainId > 0) {
499                     final SQLTable mainTable = SQLTable.get(tableMainId);
500                     table.mainTable = mainTable;
501                 }
502                 // needed due to cluster serialization that does not update automatically
503                 SQLTable.cacheSQLTable(table);
504             }
505         } catch (final SQLException e) {
506             throw new CacheReloadException("could not read sql tables", e);
507         } catch (final EFapsException e) {
508             throw new CacheReloadException("could not read sql tables", e);
509         } finally {
510             if ((con != null) && con.isOpened()) {
511                 try {
512                     con.abort();
513                 } catch (final EFapsException e) {
514                     throw new CacheReloadException("could not read sql tables", e);
515                 }
516             }
517         }
518         return ret;
519     }
520 }