class TextFileQueryExecutor implements ContextQueryExecutor<TextFileQuery, ClientCustomDataSource> {

    @Override
    public Object executeQuery(final TextFileQuery query, ClientCustomDataSource connection,
            Map<String, String[]> queryParameters, Map<String, Object> data) {
        if(query == null || connection == null){
            return null;
        }
        final List<List<String>> resultSet = new ArrayList<List<String>>();
        final CustomReportDataSource customReportDataSource = customDataSourceResourceConverter.toServer(connection,
                ToServerConversionOptions.getDefault().setSuppressValidation(true));
        final DataAdapterDefinition definition = (DataAdapterDefinition) customDataSourceFactory.getDefinition(customReportDataSource);
        try {
            final JRCsvDataSource jrDataSource = (JRCsvDataSource) definition.getJRDataSource(customReportDataSource);
            final Map<String, String> columnTypes = new HashMap<String, String>(){{
                if(query.getConvert() != null && query.getConvert().getRules() != null){
                    final List<TextFileCastConversionRule> rules = query.getConvert().getRules();
                    for(int i = 0; i < rules.size(); i++){
                        TextFileCastConversionRule rule = rules.get(i);
                        if(rule.getColumn() == null) throw new MandatoryParameterNotFoundException("query.convert.rules[" + i + "].column");
                        if(rule.getType() == null) throw new MandatoryParameterNotFoundException("query.convert.rules[" + i + "].type");
                        put(rule.getColumn(), rule.getType());
                    }
                }
            }};
            boolean hasNext = true;
            if(query.getOffset() != null) for(int i = 0; hasNext && i < query.getOffset(); i++) hasNext = jrDataSource.next();
            int maxCount = query.getLimit() != null ? query.getLimit() : Integer.MAX_VALUE;
            for(int i = 0; i < maxCount; i++){
                if(jrDataSource.next()){
                    final List<String> columns = query.getSelect() != null ? query.getSelect().getColumns() : null;
                    if(columns != null){
                        final List<String> row = new ArrayList<String>(columns.size());
                        for(String column : columns){
                            JRDesignField field = new JRDesignField();
                            field.setName(column);
                            final String valueClassName = columnTypes.get(column);
                            if(valueClassName != null) {
                                field.setValueClassName(valueClassName);
                            }
                            try {
                                final Object value = jrDataSource.getFieldValue(field);
                                row.add(dataConverterService.formatSingleValue(value));
                            } catch (JRRuntimeException e) {
                                if(e.getCause() instanceof ClassNotFoundException){
                                    throw new IllegalParameterValueException("query.convert.rules[" + column + "].type", valueClassName);
                                } else throw e;
                            } catch (JRException e){
                                if("data.csv.field.value.not.retrieved".equals(e.getMessageKey())){
                                    throw new IllegalParameterValueException("query.convert.rules[" + column + "].type", valueClassName);
                                } else throw e;
                            }
                        }
                        resultSet.add(row);
                    }
                } else {
                    break;
                }
            }
        } catch (ErrorDescriptorException e){
            throw e;
        } catch (Exception e) {
            throw new ErrorDescriptorException(e, secureExceptionHandler);
        }
        return resultSet;
    }

}