/*
* 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.impl;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.directwebremoting.annotations.RemoteMethod;
import org.directwebremoting.annotations.RemoteProxy;
import org.directwebremoting.io.FileTransfer;
import org.internna.iwebmvc.core.context.ContextHolder;
import org.internna.iwebmvc.core.crypto.Cipher;
import org.internna.iwebmvc.core.crypto.Decipherer;
import org.internna.iwebmvc.core.dao.DAO;
import org.internna.iwebmvc.core.i18n.Translate;
import org.internna.iwebmvc.core.reports.ReportEngine;
import org.internna.iwebmvc.model.DomainEntity;
import org.internna.iwebmvc.model.Filter;
import org.internna.iwebmvc.model.GridDTO;
import org.internna.iwebmvc.model.Selectable;
import org.internna.iwebmvc.model.UUID;
import org.internna.iwebmvc.model.UpdateDataDTO;
import org.internna.iwebmvc.model.ValuePair;
import org.internna.iwebmvc.core.i18n.Translator;
import org.internna.iwebmvc.model.core.hierarchy.AbstractHierarchy;
import org.internna.iwebmvc.model.core.hierarchy.AbstractHierarchyNode;
import org.internna.iwebmvc.spring.services.dwr.RemoteEntityManager;
import org.internna.iwebmvc.utils.Assert;
import org.internna.iwebmvc.utils.ClassUtils;
import org.internna.iwebmvc.utils.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.FileCopyUtils;
/**
* Default implementation based on a DAO.
* @author Jose Noheda
* @since 1.0
*/
@RemoteProxy(name = "EntityManager")
public class RemoteEntityManagerImpl implements RemoteEntityManager {
protected static Log log = LogFactory.getLog(RemoteEntityManagerImpl.class);
private Random random = new Random();
private String allowedChars = "abcdfghijklmnopqrstuvwxyz";
@Autowired protected ContextHolder context;
@Autowired protected ReportEngine reportEngine;
@Autowired @Qualifier("persistenceFacade") protected DAO dao;
@Autowired protected Decipherer decipherer;
@Autowired protected Translator translator;
protected Map<String, Object> mapFilters(Class<? extends DomainEntity> clazz, List<Filter> filters) {
Assert.notNull(filters);
Map<String, Object> parameters = new HashMap<String, Object>(filters.size());
for (Filter filter : filters) {
if (StringUtils.hasText(filter.getMinValue())) parameters.put(filter.getMin().replaceAll("\\.", "_"), filter.get(BeanUtils.getPropertyDescriptor(clazz, filter.getPath()), filter.getMinValue()));
if (StringUtils.hasText(filter.getPathValue())) {
String path = filter.getPath().replaceAll("\\.", "_");
if (filter.isEntity()) parameters.put(path, new UUID(decipherer.decrypt(filter.getPathValue())));
else parameters.put(filter.getPath().replaceAll("\\.", "_"), filter.get(BeanUtils.getPropertyDescriptor(clazz, filter.getPath()), filter.getPathValue()));
}
}
return parameters;
}
protected StringBuilder isLower(Filter filter) {
StringBuilder builder = new StringBuilder();
if("text".equals(filter.getType())) builder.append("LOWER(");
builder.append(filter.getPath());
if("text".equals(filter.getType())) builder.append(")");
return builder;
}
protected StringBuilder isI18n(Class<? extends DomainEntity> clazz, Filter filter, String operator) {
StringBuilder builder = new StringBuilder();
builder.append("(");
StringBuilder entityId = new StringBuilder(allowedChars.charAt(random.nextInt(25))).append(allowedChars.charAt(random.nextInt(25))).append(allowedChars.charAt(random.nextInt(25)));
for (int locale = 0; locale < context.getAvailableLocales().size(); locale++) {
if (locale != 0) builder.append(" OR ");
builder.append("(e IN (SELECT ");
entityId.append(allowedChars.charAt(random.nextInt(25)));
builder.append(entityId).append(" FROM ").append(clazz.getSimpleName()).append(" ").append(entityId).append(" WHERE ")
.append("LOWER(").append(entityId).append(".").append(filter.getPath()).append(".values[").append(locale)
.append("].localizedValue) ").append(operator).append(" :")
.append((">=".equals(operator) ? filter.getMin() : filter.getPath()).replaceAll("\\.", "_")).append("))");
}
builder.append(") AND ");
return builder;
}
protected StringBuilder isRangeFilter(Class<? extends DomainEntity> clazz, Filter filter) {
StringBuilder builder = new StringBuilder();
if (StringUtils.hasText(filter.getMinValue())) {
if (filter.isI18n()) builder.append(isI18n(clazz, filter, ">="));
else builder.append(isLower(filter)).append(" >= :").append(filter.getMin().replaceAll("\\.", "_")).append(" AND ");
}
if (StringUtils.hasText(filter.getPathValue())) {
if (filter.isI18n()) builder.append(isI18n(clazz, filter, "<="));
else builder.append(isLower(filter)).append(" <= :").append(filter.getPath().replaceAll("\\.", "_")).append(" AND ");
}
return builder;
}
protected StringBuilder isEntity(Filter filter) {
StringBuilder builder = new StringBuilder();
if (StringUtils.hasText(filter.getPathValue())) builder.append(":").append(filter.getPath().replaceAll("\\.", "_")).append(" = ").append(filter.getPath().replaceAll("\\.", "_")).append(".id AND ");
return builder;
}
protected StringBuilder filterToQuery(Class<? extends DomainEntity> clazz, Filter filter) {
Assert.notNull(filter);
if (StringUtils.hasText(filter.getMin())) {
return isRangeFilter(clazz, filter);
} else if (filter.isI18n()) {
return isI18n(clazz, filter, "LIKE");
} else if (filter.isEntity()) {
return isEntity(filter);
} else if (StringUtils.hasText(filter.getPathValue())) {
return new StringBuilder(isLower(filter)).append("text".equals(filter.getType()) ? " LIKE :" : " = :").append(filter.getPath().replaceAll("\\.", "_")).append(" AND ");
}
return new StringBuilder();
}
protected String getQuery(Class<? extends DomainEntity> clazz, List<Filter> filters) {
StringBuilder query = new StringBuilder("SELECT e FROM ")
.append(clazz.getSimpleName())
.append(" e WHERE ");
for (Filter filter : filters) query.append(filterToQuery(clazz, filter));
query.replace(query.lastIndexOf("AND"), query.length(), "");
return query.toString().trim();
}
@Cipher
@Override
@Translate
@RemoteMethod
@SuppressWarnings("unchecked")
public List<? extends DomainEntity> fetch(Class<? extends DomainEntity> entityClass, int offset, int number, Filter[] filterArray) throws Exception {
Assert.notNull(entityClass);
List<Filter> filters = filterArray == null ? new ArrayList<Filter>() : Arrays.asList(filterArray); // Pending DWR-225 (http://getahead.org/bugs/browse/DWR-225)
Map<String, Object> mappedFilters = mapFilters(entityClass, filters);
return mappedFilters == null || mappedFilters.size() == 0
? dao.find(entityClass, offset, number)
: dao.findByQuery(getQuery(entityClass, filters), offset, number, mappedFilters);
}
@Cipher
@Override
@Translate
@RemoteMethod
@SuppressWarnings("unchecked")
public List<DomainEntity> search(Class entityClass, String search, String fields, int count, int number) throws Exception {
Assert.hasText(search);
Assert.notNull(entityClass);
return dao.search(entityClass, fields, search, count, number);
}
@Cipher
@Override
@RemoteMethod
@SuppressWarnings("unchecked")
public List<ValuePair<String, String>> fetchReferences(Class<?> clazz, String path, String query, int count, int number) throws Exception {
Assert.isEncrypted(decipherer, path);
DomainEntity parent = (DomainEntity) ClassUtils.instantiateClass(clazz);
Selectable entity = (Selectable) ClassUtils.instantiateClass(new BeanWrapperImpl(parent).getPropertyType(decipherer.decrypt(path)));
if (entity == null) entity = (Selectable) parent;
Map<String, Object> parameters = new HashMap<String, Object>();
parameters.put("query", query + "%");
List<ValuePair<String, String>> result = new ArrayList<ValuePair<String, String>>(number);
for (Object[] o : dao.executeQuery(entity.selectableQuery(), count, number, parameters))
result.add(new ValuePair<String, String>(o[0].toString(), translator.translate(o[1].toString())));
return result;
}
@Override
@RemoteMethod
@Transactional
@SuppressWarnings("unchecked")
public void removeEntity(Class entityClass, UUID id) throws Exception {
Assert.notNull(id);
Assert.notNull(entityClass);
dao.remove(dao.getReference(entityClass, id));
}
@Override
@RemoteMethod
@SuppressWarnings("unchecked")
public boolean update(Class entityClass, UpdateDataDTO[] data) throws Exception {
Assert.notNull(entityClass);
for (UpdateDataDTO dto : data) {
Assert.isEncrypted(decipherer, dto.getPrimaryKey());
DomainEntity o = dao.find(entityClass, new UUID(decipherer.decrypt(dto.getPrimaryKey())));
BeanWrapper w = new BeanWrapperImpl(o);
w.setPropertyValue(dto.getPath(), dto.getValue());
dao.update(o);
}
return true;
}
@Override
@RemoteMethod
@Transactional
@SuppressWarnings("unchecked")
public boolean removeNode(Class<? extends AbstractHierarchyNode> nodeClass, UUID id) throws Exception {
Assert.notNull(id);
Assert.notNull(nodeClass);
AbstractHierarchyNode node = dao.find(nodeClass, id);
dao.remove(node.getHierarchy());
return true;
}
@Override
@RemoteMethod
@Transactional
public boolean addNode(Class<? extends AbstractHierarchyNode> nodeClass, UUID id, UUID parent) {
Assert.notNull(id);
Assert.notNull(nodeClass);
AbstractHierarchy hierarchy = dao.find(nodeClass, id).getHierarchy();
if (parent != null) {
AbstractHierarchy parentHierarchy = dao.find(nodeClass, parent).getHierarchy();
hierarchy.setParent(parentHierarchy);
parentHierarchy.addChild(hierarchy);
}
dao.update(hierarchy);
return true;
}
@Override
@RemoteMethod
public FileTransfer printGrid(String docType, GridDTO gridData) throws Exception {
return new FileTransfer("grid." + ("PDF".equals(docType) ? "pdf" : "xls"), "application/" + docType.toLowerCase(), FileCopyUtils.copyToByteArray(reportEngine.generateGridReport(docType, gridData)));
}
}