All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.mateu.mdd.vaadin.components.views.JPAListViewComponent Maven / Gradle / Ivy

The newest version!
package io.mateu.mdd.vaadin.components.views;

import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.vaadin.data.provider.QuerySortOrder;
import com.vaadin.shared.data.sort.SortDirection;
import com.vaadin.ui.Grid;
import io.mateu.mdd.core.MDD;
import io.mateu.mdd.core.app.AbstractAction;
import io.mateu.mdd.core.interfaces.GridDecorator;
import io.mateu.mdd.core.interfaces.StyledEnum;
import io.mateu.mdd.core.ui.MDDUIAccessor;
import io.mateu.mdd.core.views.ExtraFilters;
import io.mateu.mdd.shared.annotations.*;
import io.mateu.mdd.shared.data.ChartData;
import io.mateu.mdd.shared.data.ChartValue;
import io.mateu.mdd.shared.data.SumData;
import io.mateu.mdd.shared.interfaces.IResource;
import io.mateu.mdd.shared.reflection.FieldInterfaced;
import io.mateu.mdd.vaadin.data.MDDBinder;
import io.mateu.reflection.ReflectionHelper;
import io.mateu.util.Helper;
import io.mateu.util.interfaces.AuditRecord;
import io.mateu.util.interfaces.Translated;
import io.mateu.util.notification.Notifier;
import io.mateu.util.persistence.JPAHelper;
import io.mateu.util.persistence.JPATransaction;
import lombok.extern.slf4j.Slf4j;

import javax.persistence.*;
import javax.validation.constraints.NotNull;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.nio.charset.StandardCharsets;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Collectors;

@Slf4j
public class JPAListViewComponent extends ListViewComponent {

    private final Map initialValues;
    private final List aliasedColumnNamesList = new ArrayList<>();
    private final List columnIds;
    private final Map fieldsByAliasedColumnName;
    private final Map aliasedColumnNamesByColId = new HashMap<>();
    private final Map fieldsByColId = new HashMap<>();
    private final Consumer callback;
    private final String queryFilters;
    private ExtraFilters extraFilters;

    private final Class entityClass;

    private Object filters;

    private List sums;

    private int frozenColumnCount = -1;

    private final String useColumns;

    private final String useFilters;

    private final String useFields;
    private String readOnlyFields;

    public Map getFieldsByColId() {
        return fieldsByColId;
    }

    private List columnNames = new ArrayList<>();
    private Map fieldsByColumnName = new HashMap<>();
    private List filterNames = new ArrayList<>();
    private Map fieldsByFilterName = new HashMap<>();
    private List columnFields;
    private List filterFields;

    Map alias = new HashMap<>();
    Map aliasedColumnNames = new HashMap<>();

    String selectColumnsForCount;
    String selectColumnsForList;

    private boolean addEnabled;
    private boolean deleteEnabled;
    private boolean readOnly;

    List sumFields;

    public JPAListViewComponent(Class entityClass, String columns) {
        this(entityClass, null, null, null, columns, null, null, null);
    }

    public JPAListViewComponent(Class entityClass) {
        this(entityClass, null, null);
    }

    public JPAListViewComponent(Class entityClass, ExtraFilters extraFilters) {
        this(entityClass, extraFilters, null);
    }

    public JPAListViewComponent(Class entityClass, ExtraFilters extraFilters, Map initialValues) {
        this(entityClass, extraFilters, initialValues, null, null, null);
    }

    public JPAListViewComponent(Class entityClass, ExtraFilters extraFilters, Map initialValues,
                                String columns, String filters, String fields) {
        this(entityClass, null, extraFilters, initialValues, columns, filters, fields, null);
    }


    
    public JPAListViewComponent(Class entityClass, String queryFilters, ExtraFilters extraFilters,
                                Map initialValues, String columns, String filters, String fields,
                                Consumer callback) {
        this.entityClass = entityClass;
        this.queryFilters = queryFilters;
        this.extraFilters = extraFilters;
        this.initialValues = initialValues;
        this.useColumns = columns;
        this.useFilters = filters;
        this.useFields = fields;
        this.callback = callback;

        addEnabled = !entityClass.isAnnotationPresent(NewNotAllowed.class);
        deleteEnabled = callback == null && !entityClass.isAnnotationPresent(Indelible.class);
        readOnly = entityClass.isAnnotationPresent(ReadOnly.class) || entityClass.isAnnotationPresent(Output.class);

        addListener(new ListViewComponentListener() {
            @Override
            public void onEdit(Object id) {
                MDDUIAccessor.goTo(getUrl() + "/" + Base64.getUrlEncoder().encodeToString(id.toString().getBytes(StandardCharsets.UTF_8)));
            }

            @Override
            public void onSelect(Object id) {
                if (callback != null) {
                    if (id != null && id instanceof Optional) {
                        id = ((Optional) id).get();
                    }
                    Object o = null;
                    if (id != null) {
                        try {
                            o = JPAHelper.find(entityClass, toId(id));
                        } catch (Throwable throwable) {
                            Notifier.alert(throwable);
                        }
                    }
                    callback.accept(o);
                    MDDUIAccessor.goBack();
                }
            }
        });


        columnFields = getSelectFields(getColumnType(), useColumns, columnNames, fieldsByColumnName);
        filterFields = getFilterFields();

        createAliases(getColumnType(), columnNames, fieldsByColumnName, alias, aliasedColumnNames, aliasedColumnNamesList);

        sumFields = fieldsByColumnName.values().stream().filter(f -> f.isAnnotationPresent(Sum.class)).collect(Collectors.toList());

        {
            String ql = "count(x)";

            for (FieldInterfaced f : sumFields) {
                if (!"".equals(ql)) ql += ", ";
                ql += " sum(x." + f.getName() + ") ";
            }

            selectColumnsForCount = ql;
        }

        selectColumnsForList = buildFieldsPart(columnFields);
        
        columnIds = new ArrayList<>();
        int pos = 0;
        for (String n : columnNames) {
            String colId = "col" + pos++;
            columnIds.add(colId);
            aliasedColumnNamesByColId.put(colId, aliasedColumnNames.get(n));
            if (pos > 1) fieldsByColId.put(colId, fieldsByColumnName.get(n));
        }
        fieldsByAliasedColumnName = new HashMap<>();
        columnNames.stream().forEach(n -> {
            fieldsByAliasedColumnName.put(aliasedColumnNames.get(n), fieldsByColumnName.get(n));
        });

    }

    @Override
    public int getFrozenColumnCount() {
        if (frozenColumnCount < 0) {
            int pos = 1;
            for (FieldInterfaced columnField : getColumnFields(entityClass)) {
                if (columnField.isAnnotationPresent(FrozenColumn.class)) {
                    frozenColumnCount = pos;
                }
                pos++;
            }
            if (frozenColumnCount < 0) frozenColumnCount = 0;
        }
        return frozenColumnCount;
    }

    @Override
    public void buildColumns(Grid grid) {
        buildColumns(resultsComponent, grid, columnIds, fieldsByColId, true, false, null, null, useColumnsToColIds(useColumns));
    }

    private String useColumnsToColIds(String useColumns) {
        if (useColumns == null) return null;
        String r = "";
        int pos = 0;
        for (String t : useColumns.split(",")) {
            String k = t.trim();
            if (k.contains("(")) k = k.substring(0, k.indexOf("("));
            if (k.contains(" ")) k = k.substring(0, k.indexOf(" "));
            if (!"".equals(r)) r += ",";
            r += t.replaceAll(k.replaceAll("\\.", "\\\\."), "col_" + pos++);
        }
        return r;
    }

    @Override
    public String toString() {
        if (callback != null) {
            String n = Helper.capitalize(entityClass.getSimpleName(), false);
            return "Select a" + (n.matches("^[aeiou].*")?"n":"") + " " + n;
        } else return !Strings.isNullOrEmpty(getCaption())?getCaption():Helper.pluralize(Helper.capitalize(entityClass.getSimpleName()));
    }


    @Override
    public Object getModelForSearchFilters() throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        return createNewInstance();
    }

    @Override
    public void setModelForSearchFilters(Object filters) {
        if (filtersComponent != null) filtersComponent.getBinder().setBean(filters);
        this.filters = filters;
    }

    @Override
    public String getFieldsFilter() {
        return selectColumnsForList;
    }

    @Override
    public String getEditableFieldsFilter() {
        return useFields;
    }

    public String getReadOnlyFields() {
        return readOnlyFields;
    }

    public void setReadOnlyFields(String readOnlyFields) {
        this.readOnlyFields = readOnlyFields;
    }

    @Override
    public List getFilterFields(Class filtersType) {
        if (Strings.isNullOrEmpty(useFilters)) {

            List filterFields = ReflectionHelper.getAllFields(filtersType).stream().filter(
                    (f) -> !f.isAnnotationPresent(Password.class) && !f.isAnnotationPresent(Version.class)
                            && !f.isAnnotationPresent(Ignored.class)
                            && (f.isAnnotationPresent(SearchFilter.class)
                            || f.isAnnotationPresent(MainSearchFilter.class))
            ).collect(Collectors.toList());
            if (filterFields.size() == 0) {
                filterFields = ReflectionHelper.getAllFields(filtersType).stream().filter(
                        (f) ->  !f.isAnnotationPresent(Password.class) && !f.isAnnotationPresent(Version.class)
                                && !f.isAnnotationPresent(Ignored.class) && !f.isAnnotationPresent(Output.class)
                                &&  !IResource.class.isAssignableFrom(f.getType())
                                && (String.class.equals(f.getType())
                                || LocalDate.class.equals(f.getType())
                                || LocalDateTime.class.equals(f.getType())
                                || LocalTime.class.equals(f.getType())
                                || Date.class.equals(f.getType())
                                || boolean.class.equals(f.getType())
                                || Boolean.class.equals(f.getType())
                                || f.getType().isEnum()
                                || f.isAnnotationPresent(ManyToOne.class)
                                || f.getType().isAnnotationPresent(Entity.class))
                ).collect(Collectors.toList());
            }

            filterFields.forEach(f -> {
                filterNames.add(f.getName());
                fieldsByFilterName.put(f.getName(), f);
            });

            return filterFields;

        } else {

            List fns = Lists.newArrayList(useFilters.replaceAll(" ", "").split(","));

            List filterFields = fns.stream().map(n -> {
                FieldInterfaced f = ReflectionHelper.getFieldByName(filtersType, n);
                if (f != null) {
                    filterNames.add(n);
                    fieldsByFilterName.put(n, f);
                }
                return f;
            }).filter(f -> f != null).collect(Collectors.toList());

            return filterFields;
        }

    }

    @Override
    public boolean isAddEnabled() {
        return addEnabled;
    }

    @Override
    public boolean isDeleteEnabled() {
        return deleteEnabled;
    }

    public void setAddEnabled(boolean addEnabled) {
        this.addEnabled = addEnabled;
    }

    public void setDeleteEnabled(boolean deleteEnabled) {
        this.deleteEnabled = deleteEnabled;
    }

    @Override
    public boolean isReadOnlyListView() {
        return readOnly;
    }

    @Override
    public void setReadOnly(boolean readOnly) {
        this.readOnly = readOnly;
    }

    @Override
    public List getActions() {
        List l = new ArrayList<>();

        List ms = new ArrayList<>();

        if (callback == null) for (Method m : ReflectionHelper.getAllMethods(entityClass)) {
            if (Modifier.isStatic(m.getModifiers()) && m.isAnnotationPresent(Action.class)) {
                ms.add(m);
            }
        }

        ms.sort((a, b) -> {
            return a.getAnnotation(Action.class).order() - b.getAnnotation(Action.class).order();
        });

        ms.forEach(m -> l.add(ViewComponentHelper.createAction(m, this)));


        return l;
    }


    public Object createNewInstance() throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        if (filters == null) {
            /*
            if (Modifier.isAbstract(entityClass.getModifiers())) {
                for (Class s : ReflectionHelper.getSubclasses(entityClass)) {
                    if (!Modifier.isAbstract(s.getModifiers())) {
                        filters = s.newInstance();
                        break;
                    };
                }
            } else {
                filters = entityClass.newInstance();
            }
            for (FieldInterfaced f : getFilterFields()) {
                try {
                    ReflectionHelper.setValue(f, filters, null);
                } catch (Exception e) {
                    Notifier.alert(e);
                }
            }
            */
            filters = getFiltersType().newInstance();
            if (initialValues != null) {
                for (String k : initialValues.keySet()) {
                    try {
                        ReflectionHelper.setValue(k, filters, initialValues.get(k));
                    } catch (Exception e) {
                        Notifier.alert(e);
                    }
                }
            }
        }
        return filters;
    }

    @Override
    public Class getFiltersType() {
        try {
            return ReflectionHelper.createClass(MDD.getClassPool(), MDDBinder.class, MDD.getClassPool().getClassLoader(),
                    entityClass.getName() + "000Filters" + getFilterFieldsSerialized(), "", null, null, null, List.of(),
                    getFilterFields(entityClass), true);
        } catch (Exception e) {
            Notifier.alert(e);
        }
        return null;
    }

    private String getFilterFieldsSerialized() {
        String s = "";
        if (!Strings.isNullOrEmpty(useFilters)) s += useFilters.replaceAll("[,\\.]", "");
        return s;
    }

    public Object getFilters() {
        return filters;
    }

    @Override
    public Collection findAll(Object filters, List sortOrders, int offset, int limit) {
        List l = new ArrayList();

        try {
            JPAHelper.notransact(new JPATransaction() {
                @Override
                public void run(EntityManager em) throws Throwable {

                    ArrayList mappedSortOrders = new ArrayList<>();
                    for (QuerySortOrder sortOrder : sortOrders) {
                        mappedSortOrders.add(
                                new QuerySortOrder(aliasedColumnNamesByColId.get(sortOrder.getSorted()),
                                        sortOrder.getDirection()));
                    }

                    Query q = buildQuery(em, selectColumnsForList, getFilters(), mappedSortOrders, null,
                            offset, limit, true);

                    l.addAll(q.getResultList());

                }
            });
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }

        return l;
    }

    private Query buildQuery(EntityManager em, String selectedColumns, Object filters,
                             List sortOrders, int offset, int limit)
            throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        return buildQuery(em, selectedColumns, filters, sortOrders, null, offset, limit, false);
    }

    private Query buildQuery(EntityManager em, String selectedColumns, Object filters, List sortOrders,
                             String groupClause, int offset, int limit, boolean addOrderClause)
            throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {


        Map parameterValues = new HashMap<>();
        String w = "";
        w = buildWhereClause(filters, entityClass, parameterValues);

        // SELECT f from Student f LEFT JOIN f.classTbls s WHERE s.ClassName = 'abc' <=== ejemplo left outer join

        String jpql = "select " + selectedColumns + " from " + entityClass.getName() + " x ";

        for (String c : alias.keySet()) {
            jpql += " left join " + c + " " + alias.get(c);
        }

        if (!"".equals(w)) jpql += " where " + w;

        if (!Strings.isNullOrEmpty(groupClause)) jpql += " " + groupClause + " ";

        if (addOrderClause) {
            String oc = "";
            if (sortOrders != null) for (QuerySortOrder qso : sortOrders) {
                if (!"".equals(oc)) oc += ", ";
                oc += "col" + getColumnIndex(aliasedColumnNamesByColId.entrySet().stream()
                        .filter(e -> e.getValue().equals(qso.getSorted()))
                        .map(e -> e.getKey())
                        .findFirst()
                        .get()) + " " + ((SortDirection.DESCENDING.equals(qso.getDirection()))?"desc":"asc");
            }
            List orderCols = new ArrayList<>();
            for (FieldInterfaced f : ReflectionHelper.getAllFields(getColumnType())) {
                if (f.isAnnotationPresent(Order.class)) orderCols.add(f);
            }
            Collections.sort(orderCols, (f1, f2) -> f1.getAnnotation(Order.class).priority() - f2.getAnnotation(Order.class).priority());
            for (FieldInterfaced f : orderCols) {
                if (!"".equals(oc)) oc += ", ";
                oc += aliasedColumnNames.get(f.getName()) + " " + (f.getAnnotation(Order.class).desc()?"desc":"asc");
            }
            if ("".equals(oc) && ReflectionHelper.getFieldByName(entityClass, "audit") != null
                    && AuditRecord.class.isAssignableFrom(
                            ReflectionHelper.getFieldByName(entityClass, "audit").getType()))
                oc += "x" + ".audit.modified desc";
            if ("".equals(oc) && columnNames.size() > 1) oc += aliasedColumnNames.get(columnNames.get(1)) + " desc";
            if (!"".equals(oc)) jpql += " order by " + oc;
        }

        Query q = em.createQuery(jpql).setFirstResult(offset).setMaxResults(limit);
        for (String k : parameterValues.keySet()) q.setParameter(k, parameterValues.get(k));
        log.info(jpql);
        log.info(q.toString());
        return q;
    }

    private int getColumnIndex(String columnId) {
        int i = aliasedColumnNamesList.indexOf(columnId);
        if (i < 0 && columnId.startsWith("col")) i = Integer.parseInt(columnId.substring("col".length()));
        return i;
    }

    private void createAliases(Class sourceType, List paths, Map fieldsByPath,
                               Map alias, Map aliasedColumnNames,
                               List aliasedColumnNamesList) {
        for (String path: paths) {
            FieldInterfaced f = fieldsByPath.get(path);
            String p = path;
            FieldInterfaced fx = f;
            Class type = sourceType;
            FieldInterfaced f0 = null;
            String pathAcumulado = "x";
            while (!Strings.isNullOrEmpty(p)) {
                String s = p;
                if (p.contains(".")) {
                    p = p.substring(p.indexOf(".") + 1);
                    s = s.substring(0, s.indexOf("."));
                } else p = null;
                if (!"".equals(pathAcumulado)) pathAcumulado += ".";
                pathAcumulado += s;
                fx = ReflectionHelper.getFieldByName(type, s);
                if (fx != null) {
                    type = fx.getType();
                    if (type.isAnnotationPresent(Entity.class) && !fx.isAnnotationPresent(NotNull.class)) {
                        // referencia y no obligatorio --> left outer join
                        if (!fx.isAnnotationPresent(NotNull.class)) {
                            if (!alias.containsKey(pathAcumulado)) {
                                alias.put(pathAcumulado, "x" + alias.size());
                            }
                            pathAcumulado = alias.get(pathAcumulado);
                            f0 = fx;
                        }
                    }
                }
            }
            aliasedColumnNames.put(path, pathAcumulado);
            aliasedColumnNamesList.add(pathAcumulado);
        }

    }

    public static List getSelectFields(Class targetType, String useColumns, List columnNames,
                                               Map fieldsByColumnName) {
        List cols = getColumnFields(targetType, false, useColumns, columnNames, fieldsByColumnName);

        FieldInterfaced idField = null;

        for (FieldInterfaced f : ReflectionHelper.getAllFields(targetType)) if (f.isAnnotationPresent(Id.class)) idField = f;

        if (idField != null) columnNames.add(0, idField.getName());

        return columnNames;
    }




    private String buildWhereClause(Object filters, Class entityClass, Map parameterValues)
            throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {

        String ql = "";

        try {
            updateExtraFilters();
        } catch (Exception e) {
            Notifier.alert(e);
        }

        if (!Strings.isNullOrEmpty(queryFilters)) {

            if (!"".equals(ql)) ql += " and ";

            ql += queryFilters;

        }

        if (extraFilters != null && !Strings.isNullOrEmpty(extraFilters.getQl())) {

            if (!"".equals(ql)) ql += " and ";

            ql += extraFilters.getQl();

            if (extraFilters.getParameters() != null) parameterValues.putAll(extraFilters.getParameters());

        }

        if (filters == null) return ql;

        List allFields = getFilterFields();

        allFields = allFields.stream().filter((f) ->
                !(f.isAnnotationPresent(Version.class) || f.isAnnotationPresent(Ignored.class)
                        || (f.isAnnotationPresent(Id.class) && f.isAnnotationPresent(GeneratedValue.class)
                        && !f.isAnnotationPresent(MainSearchFilter.class)
                        && !f.isAnnotationPresent(SearchFilter.class)))
        ).collect(Collectors.toList());

        //todo: contemplar caso varias anotaciones @SearchFilter para un mismo campo


        for (FieldInterfaced f : allFields) {

            Object v = ReflectionHelper.getValue(f, filters);

            if (v != null) {

                FieldInterfaced ef = ReflectionHelper.getFieldByName(entityClass, f.getName());

                if (ef != null && ef.getType().isAnnotationPresent(UseIdToSelect.class)) {

                    boolean anadir = !String.class.equals(v.getClass()) || !Strings.isNullOrEmpty((String) v);

                    if (anadir) {
                        FieldInterfaced idf = ReflectionHelper.getIdField(entityClass);

                        if (!"".equals(ql)) ql += " and ";
                        ql += " x." + f.getName() + "." + idf.getName() + " = :" + f.getName() + " ";
                        parameterValues.put(f.getName(), v);
                    }

                } else if (String.class.equals(v.getClass())) {

                    String s = (String) v;

                    if (!Strings.isNullOrEmpty(s)) {
                        if (!"".equals(ql)) ql += " and ";
                        ql += " lower(x." + f.getName() + (f.isAnnotationPresent(LiteralSearchFilter.class)?".es":"")
                                + ") like :" + f.getName() + " ";
                        parameterValues.put(f.getName(), "%" + ((String) v).toLowerCase() + "%");
                    }
                } else if (Boolean.class.equals(v.getClass()) || boolean.class.equals(v.getClass())) {

                    boolean b = (Boolean) v;

                    if (!"".equals(ql)) ql += " and ";

                    if (!b) ql += " not ";

                    ql += " x." + f.getName() + " ";

                    if (b) ql += " = true ";

                } else if (Integer.class.equals(v.getClass()) || int.class.equals(v.getClass())
                || Long.class.equals(v.getClass()) || long.class.equals(v.getClass())
                || Double.class.equals(v.getClass()) || double.class.equals(v.getClass())) {

                    String fname = f.getName();
                    if (fname.endsWith("From")) fname = fname.substring(0, fname.lastIndexOf("From"));
                    if (fname.endsWith("To")) fname = fname.substring(0, fname.lastIndexOf("To"));
                    if (fname.endsWith("Value")) fname = fname.substring(0, fname.lastIndexOf("Value"));

                    if (!"".equals(ql)) ql += " and ";
                    ql += " x." + fname + " " +
                            (f.getName().endsWith("From")?">=":(f.getName().endsWith("To")?"<=":"="))
                            + " :" + f.getName() + " ";
                    parameterValues.put(f.getName(), v);

                } else if (LocalDate.class.equals(v.getClass()) || LocalDateTime.class.equals(v.getClass())
                        || Date.class.equals(v.getClass())) {

                    String fname = f.getName();
                    if (fname.endsWith("From")) fname = fname.substring(0, fname.lastIndexOf("From"));
                    if (fname.endsWith("To")) fname = fname.substring(0, fname.lastIndexOf("To"));

                    if (!"".equals(ql)) ql += " and ";
                    ql += " x." + fname + " " + ((f.getName().endsWith("From"))?">=":"<=") + " :" + f.getName() + " ";
                    parameterValues.put(f.getName(), v);

                } else {

                    if (!"".equals(ql)) ql += " and ";
                    ql += " x." + f.getName() + " = :" + f.getName() + " ";
                    parameterValues.put(f.getName(), v);

                }

            }


        }

        return ql;
    }

    public void updateExtraFilters() throws Exception {
    }

    private String buildFieldsPart(List columnFieldNames) {
        String s = "";
        int pos = 0;
        for (String columnName : columnFieldNames) {
            if (!"".equals(s)) s += ", ";
            String colId = aliasedColumnNames.get(columnName);
            s += colId + " as col" + pos++;
        }

        return s;
    }

    @Override
    public int gatherCount(Object filters) {
        final int[] count = {0};

        try {
            JPAHelper.notransact(new JPATransaction() {
                @Override
                public void run(EntityManager em) throws Throwable {

                    sums = new ArrayList<>();

                    Query q = buildQuery(em, selectColumnsForCount, filters, null, 0, 1000);
                    log.debug(q.toString());

                    Object r = q.getSingleResult();

                    if (r instanceof Long) {
                        count[0] = ((Long) r).intValue();
                    } else if (r instanceof Object[]) {
                        Object[] v = (Object[]) r;

                        count[0] = ((Long) v[0]).intValue();


                        NumberFormat nf = new DecimalFormat("#,###,###,###,###,###,###.00");


                        int pos = 1;
                        for (FieldInterfaced f : sumFields) {
                            String caption = Helper.capitalize(f.getName());
                            if (!caption.startsWith("Total")) caption = "Total " + caption;
                            if (!Strings.isNullOrEmpty(f.getAnnotation(Sum.class).caption()))
                                caption = f.getAnnotation(Sum.class).caption();

                            Object x = v[pos++];
                            if (x != null && x instanceof Double) {
                                x = Math.round(100d * (Double) x) / 100d;
                                x = nf.format(x);
                            }

                            sums.add(new SumData(caption, (x != null)?"" + x:"---", ""));
                        }

                    }

                }
            });
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }

        return count[0];
    }


    @Override
    public List getSums(Object filters) {
        return sums;
    }

    @Override
    protected List getCharts(Object filters) {
        List l = new ArrayList<>();

        getColumnFields(entityClass).stream().filter(f -> !f.isAnnotationPresent(NoChart.class)
                && !Translated.class.isAssignableFrom(f.getType()) && (f.getType().isEnum()
                || (!IResource.class.isAssignableFrom(f.getType()) && f.isAnnotationPresent(ManyToOne.class))
                || boolean.class.equals(f.getType()) || Boolean.class.equals(f.getType())))
                .forEach(f -> l.add(gatherChartData(filters, f)));

        return l;
    }

    private ChartData gatherChartData(Object filters, FieldInterfaced f) {

        List vs = new ArrayList<>();
        String caption = "By " + Helper.capitalize(f.getName()).toLowerCase();

        try {
            JPAHelper.notransact(new JPATransaction() {
                @Override
                public void run(EntityManager em) throws Throwable {

                    Query q = buildQuery(em, "x." + f.getName() + ", count(x)" , filters, null,
                            "group by x." + f.getName(), 0, 1000, false);
                    log.debug(q.toString());

                    List r = q.getResultList();

                    for (Object[] l : r) {
                        String s = "";

                        if (l[0] instanceof StyledEnum) s = ((StyledEnum)l[0]).getStyle();

                        vs.add(new ChartValue(l[0],"" + l[0], new Double("" + l[1]), s));
                    }


                }
            });
        } catch (Throwable throwable) {
            Notifier.alert(throwable);
        }

        ChartData d = new ChartData(f, caption, vs);

        return d;
    }

    @Override
    public Object deserializeId(String sid) {

        FieldInterfaced idField = null;
        for (FieldInterfaced f : ReflectionHelper.getAllFields(entityClass)) {
            if (f.isAnnotationPresent(Id.class)) {
                idField = f;
                break;
            }
        }

        Object id = sid;
        if (idField != null) {

            if (Long.class.equals(idField.getType()) || long.class.equals(idField.getType())) id = Long.parseLong(sid);
            else if (Integer.class.equals(idField.getType()) || int.class.equals(idField.getType())) id = Integer.parseInt(sid);
            else if (Boolean.class.equals(idField.getType()) || boolean.class.equals(idField.getType())) id = Boolean.parseBoolean(sid);
            else if (Double.class.equals(idField.getType()) || double.class.equals(idField.getType())) id = Double.parseDouble(sid);

        }
        return id;
    }

    @Override
    public Class getModelType() {
        return entityClass;
    }

    @Override
    public Class getColumnType() {
        return entityClass;
    }


    @Override
    protected void delete(Set selection) {
        try {
            JPAHelper.transact( new JPATransaction() {
                @Override
                public void run(EntityManager em) throws Throwable {

                    //selection.forEach(o -> em.remove(em.find(entityClass, toId(o))));
                    for (Object o : selection) ReflectionHelper.delete(em, em.find(entityClass, toId(o)));

                }
            });
        } catch (Throwable throwable) {
            Notifier.alert(throwable);
        }
    }

    @Override
    public void decorateGrid(Grid grid) {
        GridDecorator d = null;
        try {
            Method m = ReflectionHelper.getMethod(entityClass, "getGridDecorator");
            if (m != null) {
                if (Modifier.isStatic(m.getModifiers())) {
                    d = (GridDecorator) m.invoke(null);
                } else {
                    d = (GridDecorator) m.invoke(ReflectionHelper.newInstance(entityClass));
                }
            } else {
                if (GridDecorator.class.isAssignableFrom(entityClass)) {
                    d = (GridDecorator)ReflectionHelper.newInstance(entityClass);
                }
            }
            if (d != null) {
                d.decorateGrid(grid);
            }
        } catch (Exception e) {
            Notifier.alert(e);
        }
    }

    public ExtraFilters getExtraFilters() {
        return extraFilters;
    }

    public void setExtraFilters(ExtraFilters extraFilters) {
        this.extraFilters = extraFilters;
    }


    @Override
    public Set getSelection() {
        Set sel = new HashSet();

        try {
            JPAHelper.notransact(em -> super.getSelection().forEach(o -> {
                Object v = em.find(entityClass, toId(o));
                sel.add(v != null?v:o);
            }));
        } catch (Throwable throwable) {
            Notifier.alert(throwable);
        }

        return sel;
    }

    @Override
    public List getColumnFields() {
        return super.getColumnFields().stream()
                .filter(f -> Strings.isNullOrEmpty(useColumns) || useColumns.contains(f.getId()))
                .collect(Collectors.toList());
    }
}