1 | 6 | package controllers; |
2 | |
|
3 | |
import play.Logger; |
4 | |
import play.Play; |
5 | |
import play.data.validation.MaxSize; |
6 | |
import play.data.validation.Password; |
7 | |
import play.data.validation.Required; |
8 | |
import play.db.jpa.FileAttachment; |
9 | |
import play.db.jpa.JPA; |
10 | |
import play.db.jpa.JPASupport; |
11 | |
import play.exceptions.TemplateNotFoundException; |
12 | |
import play.i18n.Messages; |
13 | |
import play.mvc.Before; |
14 | |
import play.mvc.Controller; |
15 | |
import play.mvc.Router; |
16 | |
|
17 | |
import javax.persistence.*; |
18 | |
import java.lang.annotation.ElementType; |
19 | |
import java.lang.annotation.Retention; |
20 | |
import java.lang.annotation.RetentionPolicy; |
21 | |
import java.lang.annotation.Target; |
22 | |
import java.lang.reflect.Field; |
23 | |
import java.lang.reflect.Modifier; |
24 | |
import java.lang.reflect.ParameterizedType; |
25 | |
import java.util.*; |
26 | |
|
27 | 0 | public abstract class CRUD extends Controller { |
28 | |
|
29 | |
@Before |
30 | |
static void addType() { |
31 | 0 | ObjectType type = ObjectType.get(getControllerClass()); |
32 | 0 | renderArgs.put("type", type); |
33 | 0 | } |
34 | |
|
35 | |
public static void index() { |
36 | |
try { |
37 | 0 | render(); |
38 | 0 | } catch (TemplateNotFoundException e) { |
39 | 0 | render("CRUD/index.html"); |
40 | |
} |
41 | 0 | } |
42 | |
|
43 | |
public static void list(int page, String search, String searchFields, String orderBy, String order) { |
44 | 0 | ObjectType type = ObjectType.get(getControllerClass()); |
45 | 0 | notFoundIfNull(type); |
46 | 0 | if (page < 1) { |
47 | 0 | page = 1; |
48 | |
} |
49 | 0 | List<JPASupport> objects = type.findPage(page, search, searchFields, orderBy, order, (String) request.args.get("where")); |
50 | 0 | Long count = type.count(search, searchFields, (String) request.args.get("where")); |
51 | 0 | Long totalCount = type.count(null, null, (String) request.args.get("where")); |
52 | |
try { |
53 | 0 | render(type, objects, count, totalCount, page, orderBy, order); |
54 | 0 | } catch (TemplateNotFoundException e) { |
55 | 0 | render("CRUD/list.html", type, objects, count, totalCount, page, orderBy, order); |
56 | |
} |
57 | 0 | } |
58 | |
|
59 | |
public static void show(String id) { |
60 | 0 | ObjectType type = ObjectType.get(getControllerClass()); |
61 | 0 | notFoundIfNull(type); |
62 | 0 | JPASupport object = type.findById(id); |
63 | |
try { |
64 | 0 | render(type, object); |
65 | 0 | } catch (TemplateNotFoundException e) { |
66 | 0 | render("CRUD/show.html", type, object); |
67 | |
} |
68 | 0 | } |
69 | |
|
70 | |
public static void attachment(String id, String field) throws Exception { |
71 | 0 | ObjectType type = ObjectType.get(getControllerClass()); |
72 | 0 | notFoundIfNull(type); |
73 | 0 | JPASupport object = type.findById(id); |
74 | 0 | FileAttachment attachment = (FileAttachment) object.getClass().getField(field).get(object); |
75 | 0 | if (attachment == null) { |
76 | 0 | notFound(); |
77 | |
} |
78 | 0 | renderBinary(attachment.get(), attachment.filename); |
79 | 0 | } |
80 | |
|
81 | |
public static void save(String id) throws Exception { |
82 | 0 | ObjectType type = ObjectType.get(getControllerClass()); |
83 | 0 | notFoundIfNull(type); |
84 | 0 | JPASupport object = type.findById(id); |
85 | 0 | object = object.edit("object", params); |
86 | |
|
87 | 0 | for (ObjectType.ObjectField field : type.getFields()) { |
88 | 0 | if (field.type.equals("serializedText") && params.get("object." + field.name) != null) { |
89 | 0 | Field f = object.getClass().getDeclaredField(field.name); |
90 | 0 | f.set(object, CRUD.collectionDeserializer(params.get("object." + field.name),(Class)((ParameterizedType) f.getGenericType()).getActualTypeArguments()[0])); |
91 | |
} |
92 | |
} |
93 | |
|
94 | 0 | validation.valid(object); |
95 | 0 | if (validation.hasErrors()) { |
96 | 0 | renderArgs.put("error", Messages.get("crud.hasErrors")); |
97 | |
try { |
98 | 0 | render(request.controller.replace(".", "/") + "/show.html", type, object); |
99 | 0 | } catch (TemplateNotFoundException e) { |
100 | 0 | render("CRUD/show.html", type, object); |
101 | |
} |
102 | |
} |
103 | 0 | object.save(); |
104 | 0 | flash.success(Messages.get("crud.saved", type.modelName, object.getEntityId())); |
105 | 0 | if (params.get("_save") != null) { |
106 | 0 | redirect(request.controller + ".list"); |
107 | |
} |
108 | 0 | redirect(request.controller + ".show", object.getEntityId()); |
109 | 0 | } |
110 | |
|
111 | |
public static void blank() { |
112 | 0 | ObjectType type = ObjectType.get(getControllerClass()); |
113 | 0 | notFoundIfNull(type); |
114 | |
try { |
115 | 0 | render(type); |
116 | 0 | } catch (TemplateNotFoundException e) { |
117 | 0 | render("CRUD/blank.html", type); |
118 | |
} |
119 | 0 | } |
120 | |
|
121 | |
public static void create() throws Exception { |
122 | 0 | ObjectType type = ObjectType.get(getControllerClass()); |
123 | 0 | notFoundIfNull(type); |
124 | 0 | JPASupport object = type.entityClass.newInstance(); |
125 | 0 | validation.valid(object.edit("object", params)); |
126 | 0 | if (validation.hasErrors()) { |
127 | 0 | renderArgs.put("error", Messages.get("crud.hasErrors")); |
128 | |
try { |
129 | 0 | render(request.controller.replace(".", "/") + "/blank.html", type); |
130 | 0 | } catch (TemplateNotFoundException e) { |
131 | 0 | render("CRUD/blank.html", type); |
132 | |
} |
133 | |
} |
134 | 0 | object.save(); |
135 | 0 | flash.success(Messages.get("crud.created", type.modelName, object.getEntityId())); |
136 | 0 | if (params.get("_save") != null) { |
137 | 0 | redirect(request.controller + ".list"); |
138 | |
} |
139 | 0 | if (params.get("_saveAndAddAnother") != null) { |
140 | 0 | redirect(request.controller + ".blank"); |
141 | |
} |
142 | 0 | redirect(request.controller + ".show", object.getEntityId()); |
143 | 0 | } |
144 | |
|
145 | |
public static void delete(String id) { |
146 | 0 | ObjectType type = ObjectType.get(getControllerClass()); |
147 | 0 | notFoundIfNull(type); |
148 | 0 | JPASupport object = type.findById(id); |
149 | |
try { |
150 | 0 | object.delete(); |
151 | 0 | } catch (Exception e) { |
152 | 0 | flash.error(Messages.get("crud.delete.error", type.modelName, object.getEntityId())); |
153 | 0 | redirect(request.controller + ".show", object.getEntityId()); |
154 | |
} |
155 | 0 | flash.success(Messages.get("crud.deleted", type.modelName, object.getEntityId())); |
156 | 0 | redirect(request.controller + ".list"); |
157 | 0 | } |
158 | |
|
159 | |
|
160 | |
@Retention(RetentionPolicy.RUNTIME) |
161 | |
@Target(ElementType.TYPE) |
162 | |
public @interface For { |
163 | |
|
164 | |
Class value(); |
165 | |
} |
166 | |
|
167 | |
|
168 | |
static int getPageSize() { |
169 | 0 | return Integer.parseInt(Play.configuration.getProperty("crud.pageSize", "30")); |
170 | |
} |
171 | |
|
172 | |
public static class ObjectType implements Comparable<ObjectType> { |
173 | |
|
174 | |
public Class<? extends CRUD> controllerClass; |
175 | |
public Class<? extends JPASupport> entityClass; |
176 | |
public String name; |
177 | |
public String modelName; |
178 | |
public String controllerName; |
179 | |
|
180 | 4 | public ObjectType(Class modelClass) { |
181 | 4 | this.modelName = modelClass.getSimpleName(); |
182 | 4 | this.entityClass = modelClass; |
183 | 4 | } |
184 | |
|
185 | |
public ObjectType(String modelClass) throws ClassNotFoundException { |
186 | 0 | this(Play.classloader.loadClass(modelClass)); |
187 | 0 | } |
188 | |
|
189 | |
public static ObjectType forClass(String modelClass) throws ClassNotFoundException { |
190 | 0 | return new ObjectType(modelClass); |
191 | |
} |
192 | |
|
193 | |
public static ObjectType get(Class controllerClass) { |
194 | 4 | Class entityClass = getEntityClassForController(controllerClass); |
195 | 4 | if (entityClass == null || !JPASupport.class.isAssignableFrom(entityClass)) { |
196 | 0 | return null; |
197 | |
} |
198 | 4 | ObjectType type = new ObjectType(entityClass); |
199 | 4 | type.name = controllerClass.getSimpleName(); |
200 | 4 | type.controllerName = controllerClass.getSimpleName().toLowerCase(); |
201 | 4 | type.controllerClass = controllerClass; |
202 | 4 | return type; |
203 | |
} |
204 | |
|
205 | |
public static Class getEntityClassForController(Class controllerClass) { |
206 | 4 | if (controllerClass.isAnnotationPresent(For.class)) { |
207 | 0 | return ((For) (controllerClass.getAnnotation(For.class))).value(); |
208 | |
} |
209 | 4 | String name = controllerClass.getSimpleName(); |
210 | 4 | name = "models." + name.substring(0, name.length() - 1); |
211 | |
try { |
212 | 4 | return Play.classloader.loadClass(name); |
213 | 0 | } catch (ClassNotFoundException e) { |
214 | 0 | return null; |
215 | |
} |
216 | |
} |
217 | |
|
218 | |
public Object getListAction() { |
219 | 0 | return Router.reverse(controllerClass.getName() + ".list"); |
220 | |
} |
221 | |
|
222 | |
public Object getBlankAction() { |
223 | 0 | return Router.reverse(controllerClass.getName() + ".blank"); |
224 | |
} |
225 | |
|
226 | |
public Long count(String search, String searchFields, String where) { |
227 | 0 | String q = "select count(e) from " + entityClass.getName() + " e"; |
228 | 0 | if (search != null && !search.equals("")) { |
229 | 0 | String searchQuery = getSearchQuery(searchFields); |
230 | 0 | if (!searchQuery.equals("")) { |
231 | 0 | q += " where (" + searchQuery + ")"; |
232 | |
} |
233 | 0 | q += (where != null ? " and " + where : ""); |
234 | |
} else { |
235 | 0 | q += (where != null ? " where " + where : ""); |
236 | |
} |
237 | 0 | Query query = JPA.em().createQuery(q); |
238 | 0 | if (search != null && !search.equals("") && q.indexOf("?1") != -1) { |
239 | 0 | query.setParameter(1, "%" + search.toLowerCase() + "%"); |
240 | |
} |
241 | 0 | return Long.decode(query.getSingleResult().toString()); |
242 | |
} |
243 | |
|
244 | |
public List findPage(int page, String search, String searchFields, String orderBy, String order, String where) { |
245 | 0 | int pageLength = getPageSize(); |
246 | 0 | String q = "from " + entityClass.getName(); |
247 | 0 | if (search != null && !search.equals("")) { |
248 | 0 | String searchQuery = getSearchQuery(searchFields); |
249 | 0 | if (!searchQuery.equals("")) { |
250 | 0 | q += " where (" + searchQuery + ")"; |
251 | |
} |
252 | 0 | q += (where != null ? " and " + where : ""); |
253 | |
} else { |
254 | 0 | q += (where != null ? " where " + where : ""); |
255 | |
} |
256 | 0 | if (orderBy == null && order == null) { |
257 | 0 | orderBy = "id"; |
258 | 0 | order = "ASC"; |
259 | |
} |
260 | 0 | if (orderBy == null && order != null) { |
261 | 0 | orderBy = "id"; |
262 | |
} |
263 | 0 | if (order == null || (!order.equals("ASC") && !order.equals("DESC"))) { |
264 | 0 | order = "ASC"; |
265 | |
} |
266 | 0 | q += " order by " + orderBy + " " + order; |
267 | 0 | Query query = JPA.em().createQuery(q); |
268 | 0 | if (search != null && !search.equals("") && q.indexOf("?1") != -1) { |
269 | 0 | query.setParameter(1, "%" + search.toLowerCase() + "%"); |
270 | |
} |
271 | 0 | query.setFirstResult((page - 1) * pageLength); |
272 | 0 | query.setMaxResults(pageLength); |
273 | 0 | return query.getResultList(); |
274 | |
} |
275 | |
|
276 | |
public String getSearchQuery(String searchFields) { |
277 | 0 | List<String> fields = null; |
278 | 0 | if (searchFields != null && !searchFields.equals("")) { |
279 | 0 | fields = Arrays.asList(searchFields.split("[ ]")); |
280 | |
} |
281 | 0 | String q = ""; |
282 | 0 | for (ObjectField field : getFields()) { |
283 | 0 | if (field.searchable && (fields == null ? true : fields.contains(field.name))) { |
284 | 0 | if (!q.equals("")) { |
285 | 0 | q += " or "; |
286 | |
} |
287 | 0 | q += "lower(" + field.name + ") like ?1"; |
288 | |
} |
289 | |
} |
290 | 0 | return q; |
291 | |
} |
292 | |
|
293 | |
public JPASupport findById(Object id) { |
294 | 0 | Query query = JPA.em().createQuery("from " + entityClass.getName() + " where id = ?"); |
295 | |
try { |
296 | 0 | query.setParameter(1, play.data.binding.Binder.directBind(id + "", play.db.jpa.JPASupport.findKeyType(entityClass))); |
297 | 0 | } catch (Exception e) { |
298 | 0 | throw new RuntimeException("Something bad with id type ?", e); |
299 | |
} |
300 | 0 | return (JPASupport) query.getSingleResult(); |
301 | |
} |
302 | |
|
303 | |
public List<ObjectField> getFields() { |
304 | 0 | List fields = new ArrayList(); |
305 | 0 | for (Field f : entityClass.getFields()) { |
306 | 0 | if (Modifier.isTransient(f.getModifiers()) || Modifier.isFinal(f.getModifiers())) { |
307 | 0 | continue; |
308 | |
} |
309 | 0 | ObjectField of = new ObjectField(f); |
310 | 0 | if (of.type != null) { |
311 | 0 | fields.add(of); |
312 | |
} |
313 | |
} |
314 | 0 | return fields; |
315 | |
} |
316 | |
|
317 | |
public ObjectField getField(String name) { |
318 | 0 | for (ObjectField field : getFields()) { |
319 | 0 | if (field.name.equals(name)) { |
320 | 0 | return field; |
321 | |
} |
322 | |
} |
323 | 0 | return null; |
324 | |
} |
325 | |
|
326 | |
public int compareTo(ObjectType other) { |
327 | 6 | return modelName.compareTo(other.modelName); |
328 | |
} |
329 | |
|
330 | |
public static class ObjectField { |
331 | |
|
332 | 0 | public String type = "unknown"; |
333 | |
public String name; |
334 | |
public String relation; |
335 | |
public boolean multiple; |
336 | |
public boolean searchable; |
337 | |
public Object[] choices; |
338 | |
public boolean required; |
339 | |
public String serializedValue; |
340 | |
|
341 | 0 | public ObjectField(Field field) { |
342 | 0 | if (CharSequence.class.isAssignableFrom(field.getType())) { |
343 | 0 | type = "text"; |
344 | 0 | searchable = true; |
345 | 0 | if (field.isAnnotationPresent(MaxSize.class)) { |
346 | 0 | int maxSize = field.getAnnotation(MaxSize.class).value(); |
347 | 0 | if (maxSize > 100) { |
348 | 0 | type = "longtext"; |
349 | |
} |
350 | |
} |
351 | 0 | if (field.isAnnotationPresent(Password.class)) { |
352 | 0 | type = "password"; |
353 | |
} |
354 | |
} |
355 | 0 | if (Number.class.isAssignableFrom(field.getType()) || field.getType().equals(double.class) || field.getType().equals(int.class) || field.getType().equals(long.class)) { |
356 | 0 | type = "number"; |
357 | |
} |
358 | 0 | if (Boolean.class.isAssignableFrom(field.getType()) || field.getType().equals(boolean.class)) { |
359 | 0 | type = "boolean"; |
360 | |
} |
361 | 0 | if (Date.class.isAssignableFrom(field.getType())) { |
362 | 0 | type = "date"; |
363 | |
} |
364 | 0 | if (FileAttachment.class.isAssignableFrom(field.getType())) { |
365 | 0 | type = "file"; |
366 | |
} |
367 | 0 | if (JPASupport.class.isAssignableFrom(field.getType())) { |
368 | 0 | if (field.isAnnotationPresent(OneToOne.class)) { |
369 | 0 | if (field.getAnnotation(OneToOne.class).mappedBy().equals("")) { |
370 | 0 | type = "relation"; |
371 | 0 | relation = field.getType().getName(); |
372 | |
} |
373 | |
} |
374 | 0 | if (field.isAnnotationPresent(ManyToOne.class)) { |
375 | 0 | type = "relation"; |
376 | 0 | relation = field.getType().getName(); |
377 | |
} |
378 | |
} |
379 | 0 | if (Collection.class.isAssignableFrom(field.getType())) { |
380 | 0 | Class fieldType = (Class) ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0]; |
381 | 0 | if (field.isAnnotationPresent(OneToMany.class)) { |
382 | 0 | if (field.getAnnotation(OneToMany.class).mappedBy().equals("")) { |
383 | 0 | type = "relation"; |
384 | 0 | relation = fieldType.getName(); |
385 | 0 | multiple = true; |
386 | |
} |
387 | |
} |
388 | 0 | if (field.isAnnotationPresent(ManyToMany.class)) { |
389 | 0 | if (field.getAnnotation(ManyToMany.class).mappedBy().equals("")) { |
390 | 0 | type = "relation"; |
391 | 0 | relation = fieldType.getName(); |
392 | 0 | multiple = true; |
393 | |
} |
394 | |
} |
395 | |
} |
396 | 0 | if (Collection.class.isAssignableFrom(field.getType())) { |
397 | 0 | if (!(field.isAnnotationPresent(OneToMany.class) || |
398 | 0 | (field.isAnnotationPresent(ManyToMany.class)))) { |
399 | 0 | type = "serializedText"; |
400 | |
} |
401 | |
} |
402 | 0 | if (field.getType().isEnum()) { |
403 | 0 | type = "enum"; |
404 | 0 | relation = field.getType().getSimpleName(); |
405 | 0 | choices = field.getType().getEnumConstants(); |
406 | |
} |
407 | 0 | if (field.isAnnotationPresent(Id.class)) { |
408 | 0 | type = null; |
409 | |
} |
410 | 0 | if (field.isAnnotationPresent(Transient.class)) { |
411 | 0 | type = null; |
412 | |
} |
413 | 0 | if (field.isAnnotationPresent(Required.class)) { |
414 | 0 | required = true; |
415 | |
} |
416 | 0 | name = field.getName(); |
417 | 0 | } |
418 | |
|
419 | |
public Object[] getChoices() { |
420 | 0 | return choices; |
421 | |
} |
422 | |
} |
423 | |
} |
424 | |
|
425 | |
public static String collectionSerializer(Collection<?> coll) { |
426 | 0 | StringBuffer sb = new StringBuffer(); |
427 | 0 | for (Object obj : coll) { |
428 | 0 | sb.append("\"" + obj.toString() + "\","); |
429 | |
} |
430 | 0 | if (sb.length() > 2) { |
431 | 0 | return sb.substring(0, sb.length() - 1); |
432 | |
} |
433 | 0 | return null; |
434 | |
|
435 | |
} |
436 | |
|
437 | |
public static String arraySerializer(Object[] coll) { |
438 | 0 | return collectionSerializer(Arrays.asList(coll)); |
439 | |
} |
440 | |
|
441 | |
public static Collection<?> collectionDeserializer(String target, Class<?> type) { |
442 | 0 | String[] targets = target.trim().split(","); |
443 | |
Collection results; |
444 | 0 | Logger.info("type [" + type + "]"); |
445 | 0 | if (List.class.isAssignableFrom(type)) { |
446 | 0 | results = new ArrayList(); |
447 | |
} else { |
448 | 0 | results = new TreeSet(); |
449 | |
} |
450 | 0 | for (String targ : targets) { |
451 | 0 | if (targ.length() > 1) { |
452 | 0 | targ = targ.substring(1, targ.length() - 1); |
453 | |
} |
454 | 0 | if (type.isEnum()) { |
455 | 0 | Object[] constants = type.getEnumConstants(); |
456 | 0 | for (Object c : constants) { |
457 | 0 | if (c.toString().equals(targ)) { |
458 | 0 | results.add(c); |
459 | |
} |
460 | |
} |
461 | 0 | } else if (CharSequence.class.isAssignableFrom(type)) { |
462 | 0 | results.add(targ); |
463 | 0 | } else if (Integer.class.isAssignableFrom(type)) { |
464 | 0 | results.add(Integer.valueOf(targ)); |
465 | 0 | } else if (Float.class.isAssignableFrom(type)) { |
466 | 0 | results.add(Float.valueOf(targ)); |
467 | 0 | } else if (Boolean.class.isAssignableFrom(type)) { |
468 | 0 | results.add(Boolean.valueOf(targ)); |
469 | 0 | } else if (Double.class.isAssignableFrom(type)) { |
470 | 0 | results.add(Double.valueOf(targ)); |
471 | 0 | } else if (Long.class.isAssignableFrom(type)) { |
472 | 0 | results.add(Long.valueOf(targ)); |
473 | 0 | } else if (Byte.class.isAssignableFrom(type)) { |
474 | 0 | results.add(Byte.valueOf(targ)); |
475 | |
} |
476 | |
} |
477 | |
|
478 | 0 | return results; |
479 | |
|
480 | |
} |
481 | |
} |
482 | |
|