package play.modules.cream; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.lang.reflect.ParameterizedType; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import javax.jcr.Node; import javax.jcr.PathNotFoundException; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.persistence.Transient; import org.jcrom.annotations.JcrBaseVersionCreated; import org.jcrom.annotations.JcrBaseVersionName; import org.jcrom.annotations.JcrCheckedout; import org.jcrom.annotations.JcrCreated; import org.jcrom.annotations.JcrPath; import org.jcrom.annotations.JcrReference; import org.jcrom.annotations.JcrUUID; import org.jcrom.annotations.JcrVersionCreated; import org.jcrom.annotations.JcrVersionName; import play.db.Model; import play.db.Model.Factory; import play.db.Model.Property; import play.exceptions.UnexpectedException; import play.modules.cream.ocm.JcrMapper; import play.modules.cream.ocm.JcrQuery; import play.modules.cream.ocm.JcrQueryResult; public class JcrModelLoader implements Factory { private final Class<? extends Model> clazz; public JcrModelLoader(Class<? extends play.db.Model> modelClass) { this.clazz = modelClass; } @Override public Long count(List<String> searchFields, String keywords, String where) { // XXX Duplicate Query for count()... :( JcrQueryResult<? extends Model> queryResult = doQuery(null, null, searchFields, keywords, where); return queryResult.count(); } @Override public void deleteAll() { try { Session session = JCR.getSession(); try { Node node = session.getNode(JcrMapper.getDefaultPath(clazz)); if (node != null) { node.remove(); } } catch (PathNotFoundException e) { // } } catch (RepositoryException e) { throw new UnexpectedException(e); } } @Override @SuppressWarnings("unchecked") public List<Model> fetch(int offset, int size, String orderBy, String order, List<String> searchFields, String keywords, String where) { JcrQueryResult<Model> queryResult = (JcrQueryResult<Model>) doQuery(orderBy, order, searchFields, keywords, where); int page = (offset > size) ? offset / size : 1; return queryResult.fetch(page, size); } @Override public Model findById(Object id) { if (id == null) { return null; } return JcrMapper.loadByUUID(clazz, (String) id); } public String keyName() { return keyField().getName(); } public Class<?> keyType() { return keyField().getType(); } public Object keyValue(Model m) { try { return keyField().get(m); } catch (Exception ex) { throw new UnexpectedException(ex); } } @Override public List<Property> listProperties() { List<Model.Property> properties = new ArrayList<Model.Property>(); Set<Field> fields = new LinkedHashSet<Field>(); Class<?> tclazz = clazz; while (!tclazz.equals(Object.class)) { Collections.addAll(fields, tclazz.getDeclaredFields()); tclazz = tclazz.getSuperclass(); } for (Field f : fields) { if (Modifier.isTransient(f.getModifiers())) { continue; } if (f.isAnnotationPresent(Transient.class)) { continue; } Model.Property mp = buildProperty(f); if (mp != null) { properties.add(mp); } } return properties; } Model.Property buildProperty(final Field field) { Model.Property modelProperty = new Model.Property(); modelProperty.type = field.getType(); modelProperty.field = field; final boolean isReference = field.isAnnotationPresent(JcrReference.class); // Collection if (Collection.class.isAssignableFrom(field.getType())) { final Class<?> fieldType = (Class<?>) ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0]; if (isReference) { modelProperty.isRelation = true; modelProperty.isMultiple = true; modelProperty.relationType = fieldType; modelProperty.choices = new Model.Choices() { @SuppressWarnings("unchecked") public List<Object> list() { String path = JcrMapper.getDefaultPath(fieldType); return (List<Object>) JcrMapper.findAll(fieldType, path).fetch(); } }; } } else if (Model.class.isAssignableFrom(field.getType())) { if (isReference) { // Single node modelProperty.isRelation = true; modelProperty.relationType = field.getType(); modelProperty.choices = new Model.Choices() { @SuppressWarnings("unchecked") public List<Object> list() { String path = JcrMapper.getDefaultPath(field.getType()); return (List<Object>) JcrMapper.findAll(field.getType(), path).fetch(); } }; } } if (field.getType().isEnum()) { modelProperty.choices = new Model.Choices() { @SuppressWarnings("unchecked") public List<Object> list() { return (List<Object>) Arrays.asList(field.getType().getEnumConstants()); } }; } modelProperty.name = field.getName(); if (field.getType().equals(String.class)) { modelProperty.isSearchable = true; } if (field.isAnnotationPresent(JcrUUID.class) || field.isAnnotationPresent(JcrCheckedout.class) || field.isAnnotationPresent(JcrCreated.class) || field.isAnnotationPresent(JcrVersionCreated.class) || field.isAnnotationPresent(JcrBaseVersionCreated.class) || field.isAnnotationPresent(JcrVersionName.class) || field.isAnnotationPresent(JcrPath.class) || field.isAnnotationPresent(JcrBaseVersionName.class) || field.isAnnotationPresent(JcrPath.class)) { modelProperty.isGenerated = true; } return modelProperty; } Field keyField() { Class c = clazz; try { while (!c.equals(Object.class)) { for (Field field : c.getDeclaredFields()) { if (field.isAnnotationPresent(JcrUUID.class)) { field.setAccessible(true); return field; } } c = c.getSuperclass(); } } catch (Exception e) { throw new UnexpectedException("Error while determining the object @Id for an object of type " + clazz); } throw new UnexpectedException("Cannot get the object @JcrUUID for an object of type " + clazz); } private void appendOrderBy(String orderBy, String order, StringBuilder q) { if (order == null || (!order.equals("ASC") && !order.equals("DESC"))) { order = "ASC"; } q.append(" order by "); q.append(orderBy); q.append(" "); q.append(order); } private StringBuilder buildSelect(List<String> searchFields, String keywords, String where) { StringBuilder q = new StringBuilder("select * from [nt:unstructured] where ischildnode(${path})"); if (keywords != null && !keywords.equals("")) { String searchQuery = getSearchQuery(searchFields, keywords); if (!searchQuery.equals("")) { q.append(" and ("); q.append(searchQuery); q.append(')'); } } q.append(where != null ? " and " + where : ""); return q; } private JcrQueryResult<? extends Model> doQuery(String orderBy, String order, List<String> searchFields, String keywords, String where) { StringBuilder q = buildSelect(searchFields, keywords, where); if (orderBy != null) { appendOrderBy(orderBy, order, q); } JcrQuery query = JcrQuery.builder(clazz, q.toString()).setString("path", JcrMapper.getDefaultPath(clazz)) .setString("keywords", keywords).build(); return query.excute(); } private String getSearchQuery(List<String> searchFields, String keywords) { StringBuilder q = new StringBuilder(); if (keywords != null && !keywords.equals("")) { for (Model.Property property : listProperties()) { if (property.isSearchable && (searchFields == null || searchFields.isEmpty() ? true : searchFields .contains(property.name))) { if (q.length() > 1) { q.append(" or "); } q.append("contains("); q.append(property.name); q.append(", ${keywords})"); } } } return q.toString(); } }