001/* 
002 * JKNIV, whinstone one contract to access your database.
003 * 
004 * Copyright (C) 2017, the original author or authors.
005 *
006 * This library is free software; you can redistribute it and/or
007 * modify it under the terms of the GNU Lesser General Public
008 * License as published by the Free Software Foundation; either
009 * version 2.1 of the License.
010 * 
011 * This library is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014 * Lesser General Public License for more details.
015 * 
016 * You should have received a copy of the GNU Lesser General Public
017 * License along with this library; if not, write to the Free Software Foundation, Inc., 
018 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
019 */
020package net.sf.jkniv.whinstone.commands;
021
022import java.sql.Statement;
023import java.util.Collections;
024import java.util.Iterator;
025import java.util.List;
026
027import net.sf.jkniv.cache.Cacheable;
028import net.sf.jkniv.sqlegance.LanguageType;
029import net.sf.jkniv.sqlegance.RepositoryException;
030import net.sf.jkniv.sqlegance.Selectable;
031import net.sf.jkniv.sqlegance.Sql;
032import net.sf.jkniv.sqlegance.builder.xml.SqlTag;
033import net.sf.jkniv.sqlegance.builder.xml.TagFactory;
034import net.sf.jkniv.sqlegance.dialect.SqlFeatureSupport;
035import net.sf.jkniv.whinstone.Param;
036import net.sf.jkniv.whinstone.QueryFactory;
037import net.sf.jkniv.whinstone.Queryable;
038
039/**
040 * 
041 * @author Alisson Gomes
042 * @since 0.6.0
043 */
044public abstract class DefaultQueryHandler extends DefaultCommandHandler
045{
046    public DefaultQueryHandler(CommandAdapter cmdAdapter)
047    {
048        super(cmdAdapter);
049    }
050    
051    @Override
052    @SuppressWarnings({ "unchecked", "rawtypes" })
053    public <T> T run()
054    {
055        checkSqlConstraints();
056        if (LOG.isTraceEnabled())
057            LOG.trace("Executing [{}] as {} command", queryable, sql.getSqlType());
058        
059        List<?> list = Collections.emptyList();
060        sql.getValidateType().assertValidate(queryable.getParams());
061        Selectable selectable = sql.asSelectable();
062        if (!queryable.isBoundSql())
063            queryable.bind(selectable);
064        Cacheable.Entry entry = null;
065
066        if (!queryable.isCacheIgnore())
067            entry = selectable.getCache().getEntry(queryable);
068        try
069        {
070            if (entry == null)
071            {
072                try
073                {
074                    preCallback();
075                    Command command = asCommand();
076                    list = command.execute();
077                    if(queryable.hasFilter())
078                        filtering(list);
079                    if(queryable.hasSorter())
080                        Collections.sort(list, queryable.getSorter());                    
081                    postCallback();
082                    if (selectable.hasCache() && !list.isEmpty())
083                        selectable.getCache().put(queryable, list);
084                    
085                    paging(list);
086                }
087                catch(Exception e)
088                {
089                    queryable.setTotal(Statement.EXECUTE_FAILED);
090                    postException();
091                    handleableException.handle(e);                    
092                }
093            }
094            else
095            {
096                queryable.cached();
097                queryable.setTotal(Statement.SUCCESS_NO_INFO);
098                list = (List<?>) entry.getValue();
099                if (LOG.isDebugEnabled())
100                    LOG.debug("{} object(s) was returned from [{}] cache using query [{}] since {} reach [{}] times",
101                            list.size(), selectable.getCache().getName(), sql.getName(), entry.getTimestamp(),
102                            entry.hits());
103            }
104        }
105        finally
106        {
107            this.cmdAdapter.close();
108        }
109        //queryable.setTotal(list.size());
110        if (LOG.isDebugEnabled())
111            LOG.debug("Executed [{}] query as {} command, {} rows fetched", queryable.getName(), sql.getSqlType(),
112                    list.size());
113        
114        return (T) list;
115    }
116    
117    private void filtering(List<?> list)
118    {
119        Iterator<?> it = list.iterator();
120        while(it.hasNext())
121        {
122            if(!queryable.getFilter().isEqual(it.next()))
123            {
124                it.remove();
125            }
126        }
127    }
128    
129    private void paging(List<?> list)
130    {
131        if (queryable.isPaging())
132        {
133            Sql dynamicSql = queryable.getDynamicSql(); 
134            if (dynamicSql.getSqlDialect().supportsFeature(SqlFeatureSupport.PAGING_ROUNDTRIP))
135            {
136                try
137                {
138                    Command command = cmdAdapter.asSelectCommand(createQueryableForPaging(), null);
139                    List<Number> rows = command.execute();
140                    if (rows.isEmpty())
141                        queryable.setTotal(0);
142                    else
143                        queryable.setTotal(rows.get(0).longValue());
144                }
145                catch (RepositoryException e)
146                {
147                    // FIXME BUG select count with ORDER BY 
148                    // The ORDER BY clause is invalid in views, inline functions, derived tables, subqueries, and common table expressions, unless TOP or FOR XML is also specified.
149                    queryable.setTotal(Statement.SUCCESS_NO_INFO);
150                    LOG.error("Could not count the total of rows from full query [{}]", queryable.getName(), e);
151                }
152                /*
153                StatementAdapter<Number, ResultSet> adapterStmtCount = cmdAdapter.newStatement(queryable.queryCount(), dynamicSql.getLanguageType());
154                queryable.bind(adapterStmtCount).on();
155                adapterStmtCount.returnType(Number.class).scalar();
156                try
157                {
158                    Long rows = adapterStmtCount.rows().get(0).longValue();
159                    queryable.setTotal(rows);
160                }
161                catch (RepositoryException e)
162                {
163                    // FIXME BUG select count with ORDER BY 
164                    // The ORDER BY clause is invalid in views, inline functions, derived tables, subqueries, and common table expressions, unless TOP or FOR XML is also specified.
165                    queryable.setTotal(Statement.SUCCESS_NO_INFO);
166                    LOG.error("Cannot count the total of rows from full query [{}]", queryable.getName(), e);
167                }
168                */
169            }
170            else
171                queryable.setTotal(Statement.SUCCESS_NO_INFO);
172        }
173        else if (queryable.getTotal() < 0)
174            queryable.setTotal(list.size());
175    }
176    
177    private Queryable createQueryableForPaging()
178    {
179        String queryName = "#paging_"+System.currentTimeMillis()+"_for_"+queryable.getName();
180        //String[] paramNames = queryable.getDynamicSql().extractNames(queryable.getParams());
181        Param[] paramValues = queryable.values();//(paramNames);
182        // TODO improve get values as array
183        Object[] paramArray = new Object[paramValues.length];
184        for(int i=0; i<paramValues.length; i++)
185            paramArray[i] = paramValues[i].getValue();
186            
187        Queryable paging = QueryFactory.ofArray(queryName, queryable.getRegisterType(), paramArray);
188        Selectable selectable = TagFactory.newSelect(queryName, LanguageType.NATIVE, queryable.getDynamicSql().getSqlDialect());
189        if (selectable instanceof SqlTag)
190        {
191            SqlTag sqlTag = (SqlTag)selectable;
192            sqlTag.addTag(queryable.queryCount());
193            paging.bind(selectable);
194        }
195        paging.scalar();
196        return paging;
197    }
198    
199    private void checkSqlConstraints()
200    {
201        Selectable selectable = this.sql.asSelectable();
202        LanguageType type = selectable.getLanguageType();
203        if ((type == LanguageType.JPQL || type == LanguageType.HQL) && selectable.getGroupBy().trim().length() > 0)
204        {
205            throw new IllegalArgumentException("JPQL cannot have group by, just NATIVE or STORED, change the type ["
206                    + type + "] from SQL [" + selectable.getName() + "] to execute");
207        }
208    }
209
210}