/*
* #%L
* server
* %%
* Copyright (C) 2012 - 2015 valdasraps
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
package lt.emasina.resthub.server.factory;
import java.math.BigDecimal;
import java.sql.SQLException;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.inject.Singleton;
import lombok.extern.log4j.Log4j;
import lt.emasina.resthub.model.MdColumn;
import lt.emasina.resthub.server.cache.CcLob;
import lt.emasina.resthub.server.cache.CcCount;
import lt.emasina.resthub.server.cache.CcData;
import lt.emasina.resthub.server.exception.ClientErrorException;
import lt.emasina.resthub.server.exception.ServerErrorException;
import lt.emasina.resthub.server.handler.LobHandler;
import lt.emasina.resthub.server.handler.CountHandler;
import lt.emasina.resthub.server.handler.DataHandler;
import lt.emasina.resthub.server.handler.PagedHandler;
import lt.emasina.resthub.server.query.Query;
import org.hibernate.SQLQuery;
import org.hibernate.Session;
import org.hibernate.type.BigDecimalType;
import org.hibernate.type.BlobType;
import org.hibernate.type.CalendarType;
import org.hibernate.type.ClobType;
import org.hibernate.type.StringType;
import org.hibernate.type.TextType;
import org.hibernate.type.WrapperBinaryType;
import org.restlet.data.Status;
/**
* DataFactory
* @author valdo
*/
@Singleton
@Log4j
public class DataFactory {
private static final String START_ROW_PARAM = "START_ROW___";
private static final String NUM_ROWS_PARAM = "NUMBER_OF_ROWS___";
public CcData getData(final Session session, final DataHandler handler) throws Exception {
final Query q = handler.getQuery();
final SQLQuery query = getPagedSQLQuery(session, handler);
for (MdColumn c: q.getColumns()) {
switch (c.getType()) {
case BLOB:
query.addScalar(c.getName(), new BlobType());
break;
case CLOB:
query.addScalar(c.getName(), new ClobType());
break;
case DATE:
query.addScalar(c.getName(), new CalendarType());
break;
case NUMBER:
query.addScalar(c.getName(), new BigDecimalType());
break;
case STRING:
query.addScalar(c.getName(), new StringType());
break;
}
}
if (log.isDebugEnabled()) {
log.debug(query.getQueryString());
}
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<CcData> loopRows = executor.submit(
new Callable<CcData>() {
@Override
@SuppressWarnings("unchecked")
public CcData call() throws Exception {
CcData cc = new CcData();
for (Object o: query.list()) {
cc.addRow(q, o);
}
return cc;
};
});
try {
return loopRows.get(q.getTimeOut(), TimeUnit.SECONDS);
} catch (ExecutionException | InterruptedException ex) {
throw ex;
} catch (TimeoutException ex) {
throw new ServerErrorException(Status.SERVER_ERROR_GATEWAY_TIMEOUT, ex);
}
}
public CcLob getLob(final Session session, final LobHandler handler) throws Exception {
final Query q = handler.getQuery();
final SQLQuery query = getPagedSQLQuery(session, handler);
final MdColumn c = handler.getMdColumn();
switch (c.getType()) {
case BLOB:
query.addScalar(c.getName(), new WrapperBinaryType());
break;
case CLOB:
query.addScalar(c.getName(), new TextType());
break;
default:
throw new ClientErrorException(Status.CLIENT_ERROR_BAD_REQUEST,
"Column %d (%s) expected to be LOB found %s",
handler.getColumn(), c.getName(), c.getType().name());
}
if (log.isDebugEnabled()) {
log.debug(query.getQueryString());
}
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<CcLob> fetchData = executor.submit(
new Callable<CcLob>() {
@Override
@SuppressWarnings("unchecked")
public CcLob call() throws Exception {
CcLob cc = new CcLob();
Object o = query.uniqueResult();
if (o != null) {
switch (c.getType()) {
case CLOB:
cc.setValue((String) o);
break;
case BLOB:
cc.setValue((Byte[]) o);
break;
}
}
return cc;
};
});
try {
return fetchData.get(q.getTimeOut(), TimeUnit.SECONDS);
} catch (ExecutionException | InterruptedException ex) {
throw ex;
} catch (TimeoutException ex) {
throw new ServerErrorException(Status.SERVER_ERROR_GATEWAY_TIMEOUT, ex);
}
}
private SQLQuery getPagedSQLQuery(final Session session,
final PagedHandler<?,?> handler) throws SQLException {
final Query q = handler.getQuery();
Integer perPage = handler.getPerPage();
Integer page = handler.getPage();
if (page == null || perPage == null) {
perPage = q.getRowsLimit();
page = 1;
}
Integer startRow = perPage * (page - 1) + 1;
if (handler instanceof LobHandler) {
startRow = startRow + ((LobHandler) handler).getRow();
perPage = 1;
}
StringBuilder sb = new StringBuilder();
sb.append("select * from "
+ " (select ROWNUM ROW_NUMBER___, A.* from (");
sb.append(q.getSql())
.append(") A")
.append(" where ROWNUM < (:").append(START_ROW_PARAM).append(" + :").append(NUM_ROWS_PARAM).append(") ")
.append(") where ROW_NUMBER___ >= :").append(START_ROW_PARAM);
String sql = sb.toString();
final SQLQuery query = session.createSQLQuery(sql);
handler.applyParameters(query);
query.setInteger(START_ROW_PARAM, startRow);
query.setInteger(NUM_ROWS_PARAM, perPage);
return query;
}
public CcCount getCount(Session session, CountHandler handler) throws SQLException {
final Query q = handler.getQuery();
StringBuilder sb = new StringBuilder();
sb.append("select count(*) from (")
.append(handler.getQuery().getSql())
.append(") ");
String sql = sb.toString();
final SQLQuery query = session.createSQLQuery(sql);
handler.applyParameters(query);
if (log.isDebugEnabled()) {
log.debug(query.getQueryString());
}
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<CcCount> func = executor.submit(
new Callable<CcCount>() {
@Override
public CcCount call() throws Exception {
CcCount cc = new CcCount();
cc.setValue(((BigDecimal) query.uniqueResult()).longValue());
return cc;
}
});
try {
return func.get(q.getTimeOut(), TimeUnit.SECONDS);
} catch (ExecutionException | InterruptedException ex) {
throw new ServerErrorException(Status.SERVER_ERROR_INTERNAL, ex);
} catch (TimeoutException ex) {
throw new ServerErrorException(Status.SERVER_ERROR_GATEWAY_TIMEOUT, ex);
}
}
}