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.jpa2;
021
022import java.sql.ResultSet;
023import java.util.Map;
024
025import javax.persistence.EntityManager;
026import javax.persistence.Query;
027
028import org.slf4j.Logger;
029
030import net.sf.jkniv.asserts.Assertable;
031import net.sf.jkniv.asserts.AssertsFactory;
032import net.sf.jkniv.exception.HandleableException;
033import net.sf.jkniv.reflect.beans.ObjectProxy;
034import net.sf.jkniv.reflect.beans.ObjectProxyFactory;
035import net.sf.jkniv.sqlegance.LanguageType;
036import net.sf.jkniv.sqlegance.QueryNotFoundException;
037import net.sf.jkniv.sqlegance.Sql;
038import net.sf.jkniv.sqlegance.logger.DataMasking;
039import net.sf.jkniv.whinstone.Queryable;
040import net.sf.jkniv.whinstone.ResultRow;
041import net.sf.jkniv.whinstone.commands.Command;
042import net.sf.jkniv.whinstone.commands.CommandAdapter;
043import net.sf.jkniv.whinstone.jpa2.commands.DefaultJpaCommand;
044import net.sf.jkniv.whinstone.jpa2.commands.DefaultJpaQuery;
045import net.sf.jkniv.whinstone.jpa2.commands.MergeCommand;
046import net.sf.jkniv.whinstone.jpa2.commands.PersistCommand;
047import net.sf.jkniv.whinstone.jpa2.commands.RemoveCommand;
048import net.sf.jkniv.whinstone.jpa2.statement.JpaStatementAdapter;
049import net.sf.jkniv.whinstone.statement.AutoKey;
050import net.sf.jkniv.whinstone.statement.StatementAdapter;
051
052/**
053 * 
054 * @author Alisson Gomes
055 * @since 0.6.0
056 */
057public class JpaCommandAdapter implements CommandAdapter
058{
059    private static final Logger       LOG               = LoggerFactory.getLogger();
060    private static final Logger       SQLLOG            = net.sf.jkniv.whinstone.jpa2.LoggerFactory.getLogger();
061    private static final DataMasking  MASKING           = net.sf.jkniv.whinstone.jpa2.LoggerFactory.getDataMasking();
062    private static final Assertable   NOT_NULL          = AssertsFactory.getNotNull();
063    private static final String       ENTITY_ANNOTATION = "javax.persistence.Entity";
064    private final String              contextName;
065    private final HandleableException handlerException;
066    private JpaEmFactory              emFactory;
067    
068    public JpaCommandAdapter(String contextName, JpaEmFactory emFactory, HandleableException handlerException)
069    {
070        NOT_NULL.verify(contextName, emFactory, handlerException);
071        this.contextName = contextName;
072        this.handlerException = handlerException;
073        this.emFactory = emFactory;
074    }
075    
076    @Override
077    public String getContextName()
078    {
079        return this.contextName;
080    }
081    
082    @Override
083    public void close() //throws SQLException
084    {
085    }
086    
087    @SuppressWarnings({ "unchecked", "rawtypes" })
088    @Override
089    public <T, R> Command asSelectCommand(Queryable queryable, ResultRow<T, R> overloadResultRow)
090    {
091        Command command = null;
092        String sql = queryable.query();
093        if (SQLLOG.isInfoEnabled())
094            SQLLOG.info("Bind Native SQL\n{}", sql);
095        
096        Query queryJpa = build(queryable);
097        StatementAdapter<T, R> stmt = new JpaStatementAdapter(queryJpa, queryable, this.handlerException);
098        queryable.bind(stmt).on();
099        stmt.with(overloadResultRow);
100        command = new DefaultJpaQuery(queryable).with(stmt);
101        return command;
102    }
103    
104    @Override
105    public <T, R> Command asUpdateCommand(Queryable queryable)
106    {
107        Command command = null;
108        if (isEntity(queryable))
109            command = new MergeCommand(getEntityManager(), queryable);
110        else
111            command = buildCommand(queryable);
112        
113        return command;
114    }
115    
116    @Override
117    public <T, R> Command asRemoveCommand(Queryable queryable)//, ResultRow<T, R> overloadResultRow)
118    {
119        Command command = null;
120        if (isEntity(queryable))
121            command = new RemoveCommand(queryable).with(getEntityManager());
122        else
123            command = buildCommand(queryable);
124        
125        return command;
126    }
127    
128    @Override
129    public <T, R> Command asAddCommand(Queryable queryable)
130    {
131        Command command = null;
132        if (isEntity(queryable))
133            command = new PersistCommand(queryable).with(getEntityManager());
134        else
135            command = buildCommand(queryable);
136        
137        return command;
138    }
139    
140    @SuppressWarnings(
141    { "rawtypes" })
142    private <T, R> Command buildCommand(Queryable queryable)
143    {
144        Command command = null;
145        String sql = queryable.query();
146        AutoKey auto = null;
147        if (SQLLOG.isInfoEnabled())
148            SQLLOG.info("Bind Native SQL\n{}", sql);
149        
150        Query queryJpa = build(queryable);
151        
152        JpaStatementAdapter<Number, ResultSet> stmt = new JpaStatementAdapter<Number, ResultSet>(queryJpa, queryable,
153                this.handlerException);
154        
155        //        if (queryable.getDynamicSql().isBatch() || queryable.isTypeOfBulk())
156        //            command = new BulkCommand((CassandraPreparedStatementAdapter) stmt, queryable);
157        //        else if (queryable.getDynamicSql().isInsertable()
158        //                && queryable.getDynamicSql().asInsertable().isAutoGenerateKey())
159        //        {
160        //            Insertable isql = queryable.getDynamicSql().asInsertable();
161        //            // isSequenceStrategy and isAutoStrategy are the the same in cassandra uuid value is generated
162        //            // if(isql.getAutoGeneratedKey().isAutoStrategy())
163        //            auto = new CassandraSequenceGeneratedKey(isql, this.session, handlerException);
164        //            command = new AddSequenceKeyJdbcCommand(stmt, queryable);
165        //        }
166        //        else
167        //            command = new DefaultCommand((CassandraPreparedStatementAdapter) stmt, queryable);
168        
169        command = new DefaultJpaCommand(queryable).with(stmt);
170        stmt.with(auto);
171        return command;
172    }
173    
174    private boolean isEntity(Queryable queryable)
175    {
176        boolean isEntity = false;
177        if (queryable.getParams() != null)
178        {
179            isEntity = ObjectProxyFactory.of(queryable.getParams()).mute(ClassNotFoundException.class)
180                    .hasAnnotation(ENTITY_ANNOTATION);
181        }
182        return (isEntity && 
183                (queryable.getDynamicSql().getLanguageType() == LanguageType.HQL
184                || queryable.getDynamicSql().getLanguageType() == LanguageType.JPQL));
185    }
186    
187//    @Override
188//    public <T, R> StatementAdapter<T, R> newStatement(String sql, LanguageType languageType)
189//    {
190//        Query queryJpa = getEntityManager().createQuery(sql);
191//        StatementAdapter<T, R> stmt = new JpaStatementAdapter(queryJpa, null, this.handlerException);
192//        return stmt;
193//    }
194
195    private Query build(Queryable queryable)
196    {
197        Sql sql = queryable.getDynamicSql();
198        LanguageType languageType = sql.getLanguageType();
199        Class<?> overloadReturnedType = sql.getReturnTypeAsClass();
200        EntityManager em = getEntityManager();
201        Query queryJpa = null;
202        String stringSql = queryable.query();
203        boolean returnTypeIsEntity = false;
204        if (queryable.getDynamicSql().hasReturnType())
205        {
206            ObjectProxy<?> proxyReturnType = ObjectProxyFactory.of(queryable.getDynamicSql().getReturnType());
207            returnTypeIsEntity = proxyReturnType.hasAnnotation(ENTITY_ANNOTATION);
208        }
209        switch (languageType)
210        {
211            case JPQL:
212                if (sql instanceof NamedQueryForSql)
213                {
214                    try
215                    {
216                        if (!Map.class.getName().equals(queryable.getReturnType().getName()))
217                            queryJpa = em.createNamedQuery(sql.getName(), sql.getReturnTypeAsClass());
218                        else
219                            queryJpa = em.createNamedQuery(sql.getName());
220                        
221                        this.initParams(queryable, queryJpa);
222                    }
223                    catch (IllegalArgumentException notFound)
224                    {
225                        throw new QueryNotFoundException("Named Query not found [" + queryable.getName() + "] check if orm.xml have the query named or it's annotated");
226                    }                
227                }
228                else if (returnTypeIsEntity)
229                    queryJpa = em.createQuery(stringSql, overloadReturnedType);
230                else
231                    queryJpa = em.createQuery(stringSql);
232                
233                break;
234            case NATIVE:
235                if (returnTypeIsEntity)
236                    queryJpa = em.createNativeQuery(stringSql, overloadReturnedType);
237                else
238                    queryJpa = em.createNativeQuery(stringSql);
239                break;
240            case STORED:
241                throw new UnsupportedOperationException(
242                        "RepositoryJpa supports JPQL or NATIVE queries none other, Stored Procedure is pending to implements");
243            //              queryJpa = em.createStoredProcedureQuery(isql.asStorable().getSpName());          
244            default:
245                throw new UnsupportedOperationException(
246                        "RepositoryJpa supports JPQL or NATIVE queries none other, Stored Procedure is pending to implements");
247        }
248        
249        return queryJpa;
250        /*
251        Class<?> mandatoryReturnType = null;
252        Sql isql = null;
253        boolean containQuery = sqlContext.containsQuery(queryable.getName());
254        if (containQuery)
255        {
256            isql = sqlContext.getQuery(queryable.getName());
257            if (overloadReturnedType != null)
258                mandatoryReturnType = overloadReturnedType;
259            else if (isql.getReturnTypeAsClass() != null)
260                mandatoryReturnType = isql.getReturnTypeAsClass();
261            
262            adapter = new QueryJpaAdapter(em, queryable, isql, mandatoryReturnType);
263            if (queryable.isPaging())
264                adapter.setQueryJpaForPaging(getQueryForPaging(em, sqlContext, queryable, isql));
265        }
266        else
267        {
268            adapter = new NamedQueryJpaAdapter(em, queryable, overloadReturnedType);
269        }
270        return adapter;
271        */
272    }
273    
274    private void initParams(Queryable queryable, Query queryJpa)
275    {
276        if (!queryable.isTypeOfNull())
277        {
278            if (queryable.isTypeOfMap())
279            {
280                //PrepareParams<Query> prepareParams = PrepareParamsFactory.newPrepareParams(queryJpa, new ParamParserColonMark(), queryable);
281                setMapParams(queryJpa, (Map) queryable.getParams());
282            }
283            else if (queryable.getParams().getClass().isArray())
284            {
285                //PrepareParams<Query> prepareParams = PrepareParamsFactory.newPrepareParams(queryJpa, new ParamParserQuestionMark(), queryable);
286                setArrayParams(queryJpa, (Object[]) queryable.getParams());
287            }
288        }
289        if (queryable.isPaging())
290            queryJpa.setFirstResult(queryable.getOffset()).setMaxResults(queryable.getMax());
291    }
292    
293    private void setMapParams(Query query, Map<String, Object> params)
294    {
295        int i = 0;
296        for (String s : params.keySet())
297        {
298            Object o = params.get(s);
299            if (SQLLOG.isDebugEnabled())// TODO making data sqlLogger.mask
300                SQLLOG.debug("Setting SQL Parameter from index [{}] with name [{}] with value of [{}] type of [{}]", i++,
301                        s, o, (o == null ? "NULL" : o.getClass()));
302            
303            query.setParameter(s, o);
304        }
305    }
306    
307    private void setArrayParams(Query query, Object[] params)
308    {
309        int i = 1;
310        for (Object o : params)
311        {
312            if (SQLLOG.isDebugEnabled())// TODO making data sqlLogger.mask
313                SQLLOG.debug("Setting SQL Parameter from index [{}] with name [{}] with value of [{}] type of [{}]", i,
314                        "?", o, (o == null ? "NULL" : o.getClass()));
315            query.setParameter(i++, o);
316        }
317    }
318    
319    private EntityManager getEntityManager()
320    {
321        EntityManager em = emFactory.createEntityManager();
322        LOG.trace("Lookup Entity Manager " + em);
323        return em;
324    }
325    
326    @Override
327    public String toString()
328    {
329        return "JpaCommandAdapter [contextName=" + contextName + ", emFactory=" + emFactory + "]";
330    }
331    
332}