1   /*
2    * Copyright 2003 - 2014 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.db.print;
22  
23  import java.sql.ResultSet;
24  import java.sql.SQLException;
25  import java.sql.Statement;
26  import java.util.ArrayList;
27  import java.util.List;
28  
29  import org.apache.commons.dbutils.handlers.ArrayListHandler;
30  import org.apache.commons.lang3.builder.ToStringBuilder;
31  import org.efaps.admin.datamodel.Attribute;
32  import org.efaps.admin.datamodel.Type;
33  import org.efaps.db.AbstractPrintQuery;
34  import org.efaps.db.Context;
35  import org.efaps.db.Instance;
36  import org.efaps.db.InstanceQuery;
37  import org.efaps.db.QueryCache;
38  import org.efaps.db.QueryKey;
39  import org.efaps.db.transaction.ConnectionResource;
40  import org.efaps.db.wrapper.SQLPart;
41  import org.efaps.db.wrapper.SQLSelect;
42  import org.efaps.util.EFapsException;
43  import org.efaps.util.cache.CacheReloadException;
44  import org.infinispan.Cache;
45  import org.slf4j.Logger;
46  import org.slf4j.LoggerFactory;
47  
48  /**
49   * Select Part for <code>linkfrom[TYPERNAME#ATTRIBUTENAME]</code>.
50   *
51   * @author The eFaps Team
52   * @version $Id$
53   */
54  public class LinkFromSelect
55      extends AbstractPrintQuery
56  {
57      /**
58       * Logging instance used in this class.
59       */
60      private static final Logger LOG = LoggerFactory.getLogger(OneSelect.class);
61  
62      /**
63       * Name of the Attribute the link to is based on.
64       */
65      private final String attrName;
66  
67      /**
68       * Type the {@link #attrName} belongs to.
69       */
70      private final Type type;
71  
72      /**
73       * Did this query return a result.
74       */
75      private boolean hasResult;
76  
77      /**
78       * Used for Caching.
79       */
80      private final String key;
81  
82      /**
83       * @param _linkFrom linkfrom element of the query
84       * @param _key used to cache the result
85       * @throws CacheReloadException on error
86       */
87      public LinkFromSelect(final String _linkFrom,
88                            final String _key)
89          throws CacheReloadException
90      {
91          this.key = _key;
92          final String[] linkfrom = _linkFrom.split("#");
93          this.type = Type.get(linkfrom[0]);
94          this.attrName = linkfrom[1];
95          if (this.type == null) {
96              LinkFromSelect.LOG.error("Could not get type for linkfrom: '{}'", _linkFrom);
97          }
98          LinkFromSelect.LOG.debug("adding linkfrom: '{}'", _linkFrom);
99          final OneSelect onsel = new OneSelect(this, _linkFrom);
100         addOneSelect(onsel);
101         onsel.setFromSelect(this);
102         onsel.getSelectParts().add(new ISelectPart() {
103 
104             @Override
105             public Type getType()
106             {
107                 return LinkFromSelect.this.type;
108             }
109 
110             @Override
111             public int join(final OneSelect _oneselect,
112                             final SQLSelect _select,
113                             final int _relIndex)
114             {
115                 // nothing to join here
116                 return 0;
117             }
118 
119             @Override
120             public void addObject(final Object[] _rs)
121                 throws SQLException
122             {
123                 // no objects must be added
124             }
125 
126             @Override
127             public Object getObject()
128             {
129                 return null;
130             }
131 
132             @Override
133             public void add2Where(final OneSelect _oneselect,
134                                   final SQLSelect _select)
135             {
136                 // no clause must be added
137             }
138 
139             @Override
140             public void next()
141                 throws EFapsException
142             {
143                 // no clause must be added
144             }
145         });
146     }
147 
148     /**
149      * Getter method for instance variable {@link #hasResult}.
150      *
151      * @return value of instance variable {@link #hasResult}
152      */
153     public boolean hasResult()
154     {
155         return this.hasResult;
156     }
157 
158     /**
159      * Execute the from select for the given instance.
160      * @param _onesel instance
161      * @throws EFapsException on error
162      * @return true if statement didi return values, else false
163      */
164     public boolean execute(final OneSelect _onesel)
165         throws EFapsException
166     {
167         this.hasResult = executeOneCompleteStmt(createSQLStatement(_onesel), getAllSelects());
168         return this.hasResult;
169     }
170 
171     /**
172      * Method to create on Statement out of the different parts.
173      *
174      * @param _parentOnesel instance
175      * @return String containing the SQL statement
176      * @throws EFapsException on error
177      */
178     private String createSQLStatement(final OneSelect _parentOnesel)
179         throws EFapsException
180     {
181         final Attribute attr = this.type.getAttribute(this.attrName);
182 
183         if (attr == null) {
184             LOG.error("Could not find Attribute '{}' in Type '{}'", this.attrName, this.type.getName());
185         }
186 
187         final SQLSelect select = new SQLSelect()
188                 .column(0, "ID")
189                 .column(0, attr.getSqlColNames().get(0))
190                 .from(this.type.getMainTable().getSqlTable(), 0);
191 
192         // on a from  select only one table is the base
193         getAllSelects().get(0).append2SQLFrom(select);
194 
195         int colIndex = select.getColumns().size() + 1;
196 
197         for (final OneSelect oneSel : getAllSelects()) {
198             colIndex += oneSel.append2SQLSelect(select, colIndex);
199         }
200         select.addPart(SQLPart.WHERE)
201             .addColumnPart(0, attr.getSqlColNames().get(0))
202             .addPart(SQLPart.IN).addPart(SQLPart.PARENTHESIS_OPEN);
203 
204         if (_parentOnesel.isMultiple()) {
205             boolean first = true;
206             final List<?> ids = (List<?>) _parentOnesel.getObject();
207             for (final Object id : ids) {
208                 if (first) {
209                     first = false;
210                 } else {
211                     select.addPart(SQLPart.COMMA);
212                 }
213                 select.addValuePart(id);
214             }
215         } else {
216             select.addValuePart(_parentOnesel.getObject());
217         }
218         select.addPart(SQLPart.PARENTHESIS_CLOSE);
219 
220         _parentOnesel.setValueSelect(null);
221 
222         // in a subquery the type must also be set
223         if (this.type.getMainTable().getSqlColType() != null) {
224             select.addPart(SQLPart.AND)
225                 .addColumnPart(0, this.type.getMainTable().getSqlColType())
226                 .addPart(SQLPart.IN).addPart(SQLPart.PARENTHESIS_OPEN);
227             boolean first = true;
228             if (this.type.isAbstract()) {
229                 for (final Type atype : getAllChildTypes(this.type)) {
230                     if (first) {
231                         first = false;
232                     } else {
233                         select.addPart(SQLPart.COMMA);
234                     }
235                     select.addValuePart(atype.getId());
236                 }
237                 if (first) {
238                     LinkFromSelect.LOG.error("The type is declared abstract but does not have children: {}", this.type);
239                 }
240             } else {
241                 select.addValuePart(this.type.getId());
242             }
243 
244             select.addPart(SQLPart.PARENTHESIS_CLOSE);
245         }
246 
247         return select.getSQL();
248     }
249 
250     /**
251      * Recursive method to get all child types for a type.
252      *
253      * @param _parent parent type
254      * @return list of all child types
255      * @throws CacheReloadException on error
256      */
257     private List<Type> getAllChildTypes(final Type _parent)
258         throws CacheReloadException
259     {
260         final List<Type> ret = new ArrayList<Type>();
261         for (final Type child : _parent.getChildTypes()) {
262             ret.addAll(getAllChildTypes(child));
263             ret.add(child);
264         }
265         return ret;
266     }
267 
268     /**
269      * {@inheritDoc}
270      */
271     @SuppressWarnings("unchecked")
272     @Override
273     protected boolean executeOneCompleteStmt(final String _complStmt,
274                                              final List<OneSelect> _oneSelects)
275         throws EFapsException
276     {
277         boolean ret = false;
278         ConnectionResource con = null;
279         try {
280             LOG.debug("Executing SQLL: {}", _complStmt);
281             List<Object[]> rows = null;
282             boolean cached = false;
283             if (isCacheEnabled()) {
284                 final QueryKey querykey = QueryKey.get(getKey(), _complStmt);
285                 final Cache<QueryKey, Object> cache = QueryCache.getSqlCache();
286                 if (cache.containsKey(querykey)) {
287                     final Object object = cache.get(querykey);
288                     if (object instanceof List) {
289                         rows = (List<Object[]>) object;
290                     }
291                     cached = true;
292                 }
293             }
294             if (!cached) {
295                 con = Context.getThreadContext().getConnectionResource();
296                 final Statement stmt = con.getConnection().createStatement();
297 
298                 final ResultSet rs = stmt.executeQuery(_complStmt.toString());
299                 final ArrayListHandler handler = new ArrayListHandler(Context.getDbType().getRowProcessor());
300                 rows = handler.handle(rs);
301                 rs.close();
302                 stmt.close();
303                 con.commit();
304                 if (isCacheEnabled()) {
305                     final QueryKey querykey = QueryKey.get(getKey(), _complStmt);
306                     final Cache<QueryKey, Object> cache = QueryCache.getSqlCache();
307                     cache.put(querykey, rows);
308                 }
309             }
310             for (final Object[] row : rows) {
311                 for (final OneSelect onesel : _oneSelects) {
312                     onesel.addObject(row);
313                 }
314                 ret = true;
315             }
316 
317         } catch (final SQLException e) {
318             throw new EFapsException(InstanceQuery.class, "executeOneCompleteStmt", e);
319         } finally {
320             if (con != null && con.isOpened()) {
321                 con.abort();
322             }
323         }
324         return ret;
325     }
326 
327     /**
328      * Getter method for instance variable {@link #oneSelect}.
329      *
330      * @return value of instance variable {@link #oneSelect}
331      */
332     public OneSelect getMainOneSelect()
333     {
334         return getAllSelects().get(0);
335     }
336 
337     /**
338      * Returns the {@link #type} for which the attribute {@link #attrName}
339      * belongs to.
340      *
341      * @return type
342      * @see #type
343      */
344     public Type getType()
345     {
346         return this.type;
347     }
348 
349     /**
350      * {@inheritDoc}
351      */
352     @Override
353     public Instance getCurrentInstance()
354     {
355         return null;
356     }
357 
358     /**
359      * {@inheritDoc}
360      */
361     @Override
362     public List<Instance> getInstanceList()
363     {
364         return null;
365     }
366 
367     /**
368      * {@inheritDoc}
369      */
370     @Override
371     public Type getMainType()
372     {
373         return null;
374     }
375 
376     @Override
377     public String toString()
378     {
379         return ToStringBuilder.reflectionToString(this);
380     }
381 
382     /**
383      * {@inheritDoc}
384      */
385     @Override
386     public boolean isCacheEnabled()
387     {
388         return this.key != null;
389     }
390 }