Package org.internna.iwebmvc.spring.services.dwr

Source Code of org.internna.iwebmvc.spring.services.dwr.RemoteEntityManagerImpl

/*
* Copyright 2002-2007 the original author or authors.
*
* Licensed under the Apache license, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.internna.iwebmvc.spring.services.dwr;

import static org.internna.iwebmvc.spring.util.SecurityUtils.isUserInRole;
import static org.internna.iwebmvc.utils.JPAUtils.getReverseSetter;
import static org.springframework.util.StringUtils.hasText;

import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;

import javax.annotation.security.RolesAllowed;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.directwebremoting.annotations.RemoteMethod;
import org.directwebremoting.annotations.RemoteProxy;
import org.hibernate.validator.ClassValidator;
import org.hibernate.validator.InvalidValue;
import org.internna.iwebmvc.RollbackException;
import org.internna.iwebmvc.crypto.Decipherer;
import org.internna.iwebmvc.dao.DAO;
import org.internna.iwebmvc.javascript.EntityParser;
import org.internna.iwebmvc.metadata.EntitySecurity;
import org.internna.iwebmvc.metadata.EntitySecurityRule;
import org.internna.iwebmvc.model.AbstractOwnedDomainEntity;
import org.internna.iwebmvc.model.Captcha;
import org.internna.iwebmvc.model.Displayable;
import org.internna.iwebmvc.model.DomainEntity;
import org.internna.iwebmvc.model.Poll;
import org.internna.iwebmvc.model.PollOption;
import org.internna.iwebmvc.model.PollVote;
import org.internna.iwebmvc.model.UUID;
import org.internna.iwebmvc.model.User;
import org.internna.iwebmvc.model.security.UserImpl;
import org.internna.iwebmvc.model.ui.Filter;
import org.internna.iwebmvc.model.ui.Sort;
import org.internna.iwebmvc.security.AJAXManageableImageCaptchaService;
import org.internna.iwebmvc.security.UserManager;
import org.internna.iwebmvc.spring.i18n.WebContextLocale;
import org.internna.iwebmvc.utils.Assert;
import org.internna.iwebmvc.utils.ClassUtils;
import org.internna.iwebmvc.utils.CollectionUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.context.MessageSource;
import org.springframework.transaction.annotation.Transactional;

/**
* Default implementation based on a DAO.
*
* @author Jose Noheda
* @since 1.0
*/
@RemoteProxy(name = "EntityManager")
public class RemoteEntityManagerImpl implements RemoteEntityManager {

    protected enum OPERATOR {

        LESS_THAN("<"), LESS_OR_EQUAL_THAN("<="), EQUAL("="), LIKE ("LIKE"), GREATER_THAN (">"), GREATER_OR_EQUAL_THAN(">="), NOT_EQUAL("!=");

        private String op;

        OPERATOR(String operator) {
            this.op = operator;
        }

        @Override public String toString() {
            return op;
        }

    }

    protected final String MAX = "_max";

    protected static Log log = LogFactory.getLog(RemoteEntityManagerImpl.class);

    protected DAO dao;
    protected Decipherer decipherer;
    protected UserManager userManager;
    protected MessageSource messageResolver;
    protected List<EntityParser<?>> parsers;
    protected AJAXManageableImageCaptchaService captchaService;

    @Required public void setDao(DAO dao) {
        this.dao = dao;
    }

    public void setDecipherer(Decipherer decipherer) {
        this.decipherer = decipherer;
    }

    public void setMessageResolver(MessageSource messageResolver) {
        this.messageResolver = messageResolver;
    }

    public void setUserManager(UserManager userManager) {
      this.userManager = userManager;
    }

    public void setCaptchaService(AJAXManageableImageCaptchaService captchaService) {
      this.captchaService = captchaService;
    }

    public void setParsers(List<EntityParser<?>> parsers) {
        this.parsers = parsers;
    }

    protected Object getEnum(Class<? extends DomainEntity> clazz, Filter filter, Object val) {
      return "ENUM".equals(filter.getType()) & (val != null) ? ClassUtils.getEnumValue(clazz, filter.getPath(), Integer.parseInt(val.toString())) : val;
    }

    protected Map<String, Object> mapFilters(Class<? extends DomainEntity> clazz, List<Filter> filters, HttpServletRequest request) throws ClassNotFoundException {
        Map<String, Object> parameters = null;
        if (filters != null) {
            parameters = new HashMap<String, Object>(filters.size());
            for (Filter filter : filters) {
                if (hasText(filter.getPath()) && (!filter.isNullValue())) {
                    if (hasText(filter.getFrom())) {
                        if ("COLLECTION".equals(filter.getType())) parameters.put(filter.getSanitizedPath(), dao.getReference(ClassUtils.forName(decipherer.decrypt(filter.getEntityClass())), new UUID(decipherer.decrypt(filter.get("from").toString()))));
                        else if (("ENTITY".equals(filter.getType())) || ("SCRIPTABLE".equals(filter.getType()))) parameters.put(filter.getSanitizedPath(), new UUID(decipherer.decrypt(filter.get("from").toString())));
                        else parameters.put(filter.getSanitizedPath(), getEnum(clazz, filter, filter.get("from")));
                    }
                    if (hasText(filter.getTo())) parameters.put(filter.getSanitizedPath() + MAX, filter.get("to"));
                }
            }
        }
        if ((clazz != null) && AbstractOwnedDomainEntity.class.isAssignableFrom(clazz)) {
            if (parameters == null) parameters = new HashMap<String, Object>(1);
            User user = userManager.getActiveUser(request);
            parameters.put("viewer", user.isAnonymous() ? null : user);
        }
        return parameters == null ? Collections.EMPTY_MAP : Collections.unmodifiableMap(parameters);
    }

    protected StringBuilder queryForBasicFilter(Filter filter, OPERATOR operator) {
        StringBuilder clause = new StringBuilder();
        if (hasText(filter.getFrom())) {
            clause.append("( LOWER(e.");
            if (hasText(filter.getEmbedded())) clause.append(filter.getEmbedded()).append(".");
            clause.append(filter.getPath()).append(") ").append(filter.isRange() ? OPERATOR.GREATER_OR_EQUAL_THAN : operator).append(" :").append(filter.getSanitizedPath()).append(" )");
        }
        if (hasText(filter.getTo())) {
            if (clause.length() > 0) clause.append(" AND ");
            clause.append("( LOWER(e.");
            if (hasText(filter.getEmbedded())) clause.append(filter.getEmbedded()).append(".");
            clause.append(filter.getPath()).append(") ").append(OPERATOR.LESS_OR_EQUAL_THAN).append(" :").append(filter.getSanitizedPath() + MAX).append(" )");
        }
        return clause;
    }

    protected StringBuilder queryForI18nFilter(Filter filter) {
        if (hasText(filter.getFrom()) & filter.isRange() & hasText(filter.getTo())) {
            return new StringBuilder("( LOWER(").append(filter.getPath()).append(") BETWEEN :")
                    .append(filter.getSanitizedPath()).append(" AND :").append(filter.getSanitizedPath() + MAX).append(" ) ");
        } else return queryForBasicFilter(filter, OPERATOR.LIKE);
    }

    protected StringBuilder queryForTextFilter(Filter filter) {
        return queryForBasicFilter(filter, OPERATOR.LIKE);
    }

    protected StringBuilder queryForNumberFilter(Filter filter) {
      StringBuilder clause = new StringBuilder();
        if (hasText(filter.getFrom()))
            clause.append("( e.").append(filter.getPath()).append(" ").append(filter.isRange() ? OPERATOR.GREATER_OR_EQUAL_THAN : (filter.isInvert() ? OPERATOR.NOT_EQUAL : OPERATOR.EQUAL)).append(" :").append(filter.getSanitizedPath()).append(" )");
        if (hasText(filter.getTo())) {
            if (clause.length() > 0) clause.append(" AND ");
            clause.append("( e.").append(filter.getPath()).append(" ").append(OPERATOR.LESS_OR_EQUAL_THAN).append(" :").append(filter.getSanitizedPath() + MAX).append(" )");
        }
        return clause;
    }

    protected StringBuilder queryForRatingFilter(Filter filter) {
        return queryForNumberFilter(filter);
    }

    protected StringBuilder queryForAmountFilter(Filter filter) {
        return queryForBasicFilter(filter, OPERATOR.GREATER_OR_EQUAL_THAN);
    }

    protected StringBuilder queryForDateFilter(Filter filter) {
        return queryForBasicFilter(filter, OPERATOR.GREATER_OR_EQUAL_THAN);
    }

    protected StringBuilder queryForBooleanFilter(Filter filter) {
        return hasText(filter.getFrom()) ? new StringBuilder("( e.").append(filter.getPath()).append(" = :").append(filter.getSanitizedPath()).append(" )") : new StringBuilder();
    }

    protected StringBuilder queryForEntityFilter(Filter filter) {
        return hasText(filter.getFrom()) ? new StringBuilder("( e.").append(filter.getPath()).append(filter.isInvert() ? " != :" : " = :").append(filter.getSanitizedPath()).append(" )") : new StringBuilder();
    }

    protected StringBuilder queryForCollectionFilter(Filter filter) {
        return hasText(filter.getFrom()) ? new StringBuilder("( :").append(filter.getSanitizedPath()).append((filter.isInvert() ? " NOT IN" :" IN") +" ELEMENTS (e.").append(filter.getPath()).append(") )") : new StringBuilder();
    }

    protected StringBuilder filterToQuery(Class<? extends DomainEntity> clazz, Filter filter) {
        if (filter.isNullValue()) {
            return new StringBuilder("( e.").append(filter.getPath()).append(filter.isInvert() ? " != NULL )" : " = NULL )");
        } else {
            String type = filter.getType();
            if ("I18N".equals(type)) return queryForI18nFilter(filter);
            if ("TEXT".equals(type)) return queryForTextFilter(filter);
            if ("DATE".equals(type)) return queryForDateFilter(filter);
            if ("ENUM".equals(type) | "DOUBLE".equals(type) | "NUMBER".equals(type)) return queryForNumberFilter(filter);
            if ("RATING".equals(type)) return queryForRatingFilter(filter);
            if ("AMOUNT".equals(type)) return queryForAmountFilter(filter);
            if ("BOOLEAN".equals(type)) return queryForBooleanFilter(filter);
            if ("ENTITY".equals(type) | "SCRIPTABLE".equals(type)) return queryForEntityFilter(filter);
            if ("COLLECTION".equals(type)) return queryForCollectionFilter(filter);
            return new StringBuilder();
        }
    }

    protected String getQuery(Class<? extends DomainEntity> clazz, List<Filter> filters, Sort order) {
        StringBuilder query = new StringBuilder("SELECT e FROM ")
            .append(clazz.getSimpleName())
            .append(" e WHERE ");
        if (AbstractOwnedDomainEntity.class.isAssignableFrom(clazz)) query.append("( ( e.publicView = true ) OR ( :viewer IN ELEMENTS(e.viewers) ) ) AND ");
        if (filters != null) {
            for (Filter filter : filters) {
                StringBuilder clause = filterToQuery(clazz, filter).append(" AND ");
                if (clause.length() > 5) query.append(clause);
            }
        }
        query.delete(query.lastIndexOf(query.indexOf("AND") > 0 ? " AND " : " WHERE "), query.length());
        if (order != null) {
            query.append(" ORDER BY e.").append(order.getAttribute());
            if (order.isDescending()) query.append(" DESC");
        }
        String q = query.toString();
        if (log.isDebugEnabled()) log.debug("Filtering using query [" + q + "]");
        return q;
    }

    @Override
    @RemoteMethod
    public List<? extends DomainEntity> fetch(Class<? extends DomainEntity> entityClass, int offset, int number, List<Filter> filters, Sort order, HttpServletRequest request) throws Exception {
      try {
            Assert.notNull(entityClass);
            Map<String, Object> parameters = mapFilters(entityClass, filters, request);
            if (CollectionUtils.isNotEmpty(parameters) && log.isDebugEnabled()) log.debug("Filtering query with " + parameters);
            return CollectionUtils.isEmpty(parameters) && (order == null) ? dao.find(entityClass, offset, number) : dao.findByQuery(getQuery(entityClass, filters, order), offset, number, parameters);
      } catch (Exception ex) {
            return new ArrayList<DomainEntity>();
      }
    }

    @SuppressWarnings("unchecked")
    protected Map<String, InvalidValue> validate(DomainEntity entity) {
      Map<String, InvalidValue> fieldErrors = null;
      ClassValidator<DomainEntity> validator = new ClassValidator(ClassUtils.getActualClass(entity), ResourceBundle.getBundle("org.hibernate.validator.resources.DefaultValidatorMessages", WebContextLocale.getActiveLocale()));
      InvalidValue[] errors = filterErrors(validator.getInvalidValues(entity));
      if ((errors != null) && (errors.length > 0)) {
            fieldErrors = new HashMap<String, InvalidValue>(errors.length);
            for (InvalidValue error : errors)
                fieldErrors.put(messageResolver.getMessage(ClassUtils.getActualClass(entity).getName() + "." + error.getPropertyPath(), null, WebContextLocale.getActiveLocale()), error);
      }
      return fieldErrors;
    }

    /**
     * Removes from the error list those errors that will be resolved during operational flow, for
     * example, security constraints.
     *
     * @param errors any
     * @return another array with some of the errors provided (zero or more)
     */
    protected InvalidValue[] filterErrors(InvalidValue[] errors) {
        List<InvalidValue> filteredErrors = new ArrayList<InvalidValue>();
        if ((errors != null) && (errors.length > 0)) {
            for (InvalidValue error : errors)
                if ((error != null) && (!"owners".equals(error.getPropertyName())))
                    filteredErrors.add(error);
        }
        return filteredErrors.toArray(new InvalidValue[0]);
    }

    /**
     * Retrieves a valid @{@link EntityParser} for this type.
     */
    protected EntityParser<?> getParser(Class<?> clazz, Object received) {
        if (!CollectionUtils.isEmpty(parsers)) {
            for (EntityParser<?> parser : parsers)
                if (parser.accept(clazz, received))
                    return parser;
        }
        return null;
    }

    /**
     * Takes a DomainEntity converted by DWR and transforms it to
     * a valid JPA object.
     */
    protected DomainEntity parse(DomainEntity entity) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
      Assert.notNull(entity);
      for (PropertyDescriptor property : BeanUtils.getPropertyDescriptors(ClassUtils.getActualClass(entity))) {
            Object received = property.getReadMethod().invoke(entity);
            EntityParser parser = getParser(property.getPropertyType(), received);
            if (parser != null) {
                Displayable parsed = parser.parse((Displayable) received);
                property.getWriteMethod().invoke(entity, parsed);
            }
      }
      return entity;
    }

    /**
     * Check the validity of the entity and persist the changes if no complains.
     */
    @RemoteMethod
    @Transactional
    @RolesAllowed("ROLE_ENTITY_MANAGER")
    @Override public Map<String, InvalidValue> saveEntity(Captcha captcha, DomainEntity entity, HttpServletRequest request) throws Exception {
        if (entity.isCaptchaRequired()) {
            if ((captcha == null) || (!hasText(captcha.getInput()))) throw new SecurityException("Captcha validation required");
            else {
                String captchaId = request.getSession().getId();
                if (hasText(captcha.getId())) captchaId += captcha.getId();
                if (!captchaService.validateResponse(captchaId, captcha.getInput())) {
                    throw new SecurityException("Captcha validation failed! This should be investigated further");
                }
            }
        }
        DomainEntity binded = parse(entity);
        Map<String, InvalidValue> errors = validate(binded);
        if (CollectionUtils.isEmpty(errors))
            errors = entity.getId() == null ? proceedWithNewEntity(entity): proceedWithExistingEntity(entity);
        if (!CollectionUtils.isEmpty(errors)) throw new RollbackException(errors);
        return errors;
    }

    private Map<String, InvalidValue> checkPermissions(EntitySecurity constraints, EntitySecurity.CRUD operation) {
        Map<String, InvalidValue> errors = new HashMap<String, InvalidValue>();
        if (constraints != null) {
            for (EntitySecurityRule rule : constraints.value()) {
                if (operation.isCompatibleOperation(rule.operation())) {
                    for (String role : rule.ifAllGranted())
                        if (!isUserInRole(role))
                            errors.put(messageResolver.getMessage("iwebmvc.security.missingRequiredRole", null, WebContextLocale.getActiveLocale()), new InvalidValue(role, null, null, null, null));
                    for (String role : rule.ifNotGranted())
                        if (isUserInRole(role))
                            errors.put(messageResolver.getMessage("iwebmvc.security.memberBannedRole", null, WebContextLocale.getActiveLocale()), new InvalidValue(role, null, null, null, null));
                    boolean any = false;
                    for (String role : rule.ifAnyGranted()) any |= isUserInRole(role);
                    if (!any) errors.put(messageResolver.getMessage("iwebmvc.security.missingRequiredRole", null, WebContextLocale.getActiveLocale()), new InvalidValue(Arrays.toString(rule.ifAnyGranted()), null, null, null, null));
                }
            }
        }
        return CollectionUtils.isEmpty(errors) ? null : errors;
    }

    @SuppressWarnings("unchecked")
    protected Map<String, InvalidValue> proceedWithNewEntity(DomainEntity entity) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
        Map<String, InvalidValue> errors = checkPermissions(entity.getClass().getAnnotation(EntitySecurity.class), EntitySecurity.CRUD.CREATE);
        if (CollectionUtils.isEmpty(errors)) {
            Map<String, Collection<DomainEntity>> added = new HashMap<String, Collection<DomainEntity>>();
            for (PropertyDescriptor property : BeanUtils.getPropertyDescriptors(ClassUtils.getActualClass(entity))) {
                if (Collection.class.isAssignableFrom(property.getPropertyType())) {
                    Collection<DomainEntity> loaded = new ArrayList<DomainEntity>();
                    for (DomainEntity element : (Collection<DomainEntity>) property.getReadMethod().invoke(entity))
                        loaded.add(dao.find(ClassUtils.getActualClass(element), element.getId()));
                    added.put(property.getName(), loaded);
                }
            }
            dao.create(entity);
            for (String property : added.keySet()) {
                Method reverseSetter = null;
                for (DomainEntity element : added.get(property)) {
                    if (reverseSetter == null) reverseSetter = getReverseSetter(ClassUtils.getActualClass(entity), ClassUtils.getActualClass(element), property);
                    if (reverseSetter != null) {
                        reverseSetter.invoke(element, entity);
                        dao.update(element);
                    }
                }
            }
        }
        return errors;
    }

    @SuppressWarnings("unchecked")
    protected Map<String, InvalidValue> proceedWithExistingEntity(DomainEntity entity) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
        Map<String, InvalidValue> errors = checkPermissions(entity.getClass().getAnnotation(EntitySecurity.class), EntitySecurity.CRUD.EDIT);
        if (CollectionUtils.isEmpty(errors)) {
            for (PropertyDescriptor property : BeanUtils.getPropertyDescriptors(ClassUtils.getActualClass(entity))) {
                if (Collection.class.isAssignableFrom(property.getPropertyType())) {
                    Collection<DomainEntity> oldCollection = null;
                    if (entity.getId() != null) oldCollection = (Collection<DomainEntity>) property.getReadMethod().invoke(dao.find(ClassUtils.getActualClass(entity), entity.getId()));
                    if (oldCollection == null) oldCollection = new ArrayList<DomainEntity>();
                    Collection<DomainEntity> removedElements = new ArrayList<DomainEntity>();
                    Collection<DomainEntity> newCollection = (Collection<DomainEntity>) property.getReadMethod().invoke(entity);
                    if (newCollection == null) newCollection = new ArrayList<DomainEntity>();
                    for (DomainEntity element : oldCollection) {
                        if (newCollection.contains(element)) newCollection.remove(element);
                        else removedElements.add(element);
                    }
                    Method reverseSetter = null;
                    for (DomainEntity element : removedElements) {
                        oldCollection.remove(element);
                        if (reverseSetter == null) reverseSetter = getReverseSetter(ClassUtils.getActualClass(entity), ClassUtils.getActualClass(element), property.getName());
                        if (reverseSetter != null) {
                            reverseSetter.invoke(element, new Object[] {null});
                            dao.update(element);
                        }
                    }
                    reverseSetter = null;
                    for (DomainEntity element : newCollection) {
                        DomainEntity newElement = dao.find(ClassUtils.getActualClass(element), element.getId());
                        oldCollection.add(newElement);
                        if (reverseSetter == null) reverseSetter = getReverseSetter(ClassUtils.getActualClass(entity), ClassUtils.getActualClass(newElement), property.getName());
                        if (reverseSetter != null) {
                            reverseSetter.invoke(newElement, entity);
                            dao.update(newElement);
                        }
                    }
                    property.getWriteMethod().invoke(entity, oldCollection);
                }
            }
            dao.update(entity);
        }
        return errors;
    }

    /**
     * Deletes (if able) a collection of entities from persistent storage.
     */
    @RemoteMethod
    @RolesAllowed("ROLE_ENTITY_MANAGER")
    @Override public boolean deleteEntities(Class<? extends DomainEntity> entityClass, List<UUID> ids) {
        dao.remove(entityClass, ids);
        return true;
    }

    @RemoteMethod
    @Override public List<? extends DomainEntity> search(Class<? extends DomainEntity> entityClass, String query, int offset, int number) throws Exception {
        Assert.hasText(query);
        Assert.notNull(entityClass);
        return dao.search(entityClass, query, offset, number);
    }

    @RemoteMethod
    @Override public boolean validateCaptcha(Captcha captcha, HttpServletRequest request) {
        boolean validation = false;
        if ((captcha != null) && hasText(captcha.getInput())) {
            String captchaId = request.getSession().getId();
            if (hasText(captcha.getId())) captchaId += captcha.getId();
            validation = captchaService.validateResponse(captchaId, captcha.getInput());
        }
        return validation;
    }

    @RemoteMethod
    @Transactional
    @Override public synchronized DomainEntity vote(UUID pollId, UUID pollOptionId, HttpServletRequest request) {
        Assert.notNull(pollId);
        Assert.notNull(pollOptionId);
        Poll poll = dao.find(Poll.class, pollId);
        PollOption option = dao.find(PollOption.class, pollOptionId);
        User user = userManager.getActiveUser(request);
        String IPAddress = request.getRemoteAddr();
        if (poll.getUserVote(user, IPAddress, dao) == null) {
            if (poll.isAllowAnonymousVotes() | !user.isAnonymous()) {
                option.vote();
                dao.update(option);
                if (poll.isStoreVotes()) {
                    PollVote pollVote = new PollVote();
                    pollVote.setVoteTime(new Date());
                    pollVote.setIP(IPAddress);
                    if (!user.isAnonymous()) pollVote.setAuthor((UserImpl) user);
                    pollVote.setOption(option);
                    dao.create(pollVote);
                }
            }
        }
        return poll;
    }

}
TOP

Related Classes of org.internna.iwebmvc.spring.services.dwr.RemoteEntityManagerImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.