1    package org.torweg.pulse.component.core.accesscontrol.admin;
2    
3    import java.util.ArrayList;
4    import java.util.List;
5    
6    import net.sf.json.JSONArray;
7    import net.sf.json.JSONObject;
8    
9    import org.hibernate.Criteria;
10   import org.hibernate.Session;
11   import org.hibernate.Transaction;
12   import org.hibernate.criterion.Criterion;
13   import org.hibernate.criterion.DetachedCriteria;
14   import org.hibernate.criterion.MatchMode;
15   import org.hibernate.criterion.Order;
16   import org.hibernate.criterion.Projections;
17   import org.hibernate.criterion.Property;
18   import org.hibernate.criterion.Restrictions;
19   import org.hibernate.criterion.Subqueries;
20   import org.slf4j.Logger;
21   import org.slf4j.LoggerFactory;
22   import org.torweg.pulse.accesscontrol.AbstractAccessControlObject;
23   import org.torweg.pulse.accesscontrol.User;
24   import org.torweg.pulse.accesscontrol.User.State;
25   import org.torweg.pulse.bundle.Controller;
26   import org.torweg.pulse.invocation.lifecycle.Lifecycle;
27   import org.torweg.pulse.service.PulseException;
28   import org.torweg.pulse.service.request.ServiceRequest;
29   import org.torweg.pulse.util.entity.AbstractBasicEntity;
30   
31   /**
32    * Base-class to derive access-control-editors from. Provides utility-methods.
33    * 
34    * @author Daniel Dietz
35    * @version $Revision: 2013 $
36    */
37   public abstract class AbstractAccessControlEditor extends Controller {
38   
39       /**
40        * the Logger.
41        */
42       protected static final Logger LOGGER = LoggerFactory
43               .getLogger(AccessControlEditor.class);
44   
45       /**
46        * Loads the assorter-data for the <b>contained</b> entities.
47        * 
48        * @param request
49        *            the {@code ServiceRequest}
50        * @param clazz
51        *            the {@code Class&lt;? extends AbstractBasicEntity&gt;}
52        * @param alias
53        *            the alias to be used
54        * 
55        * @return a {@code JSONArray} with the data
56        */
57       protected final JSONArray loadAssorterData(final ServiceRequest request,
58               final Class<? extends AbstractBasicEntity> clazz, final String alias) {
59   
60           // retrieve values from request
61           Long id = Long.parseLong(request.getCommand().getParameter("id")
62                   .getFirstValue());
63   
64           boolean isAsoc = false;
65           if (request.getCommand().getParameter("unasoc") != null) {
66               isAsoc = Boolean.parseBoolean(request.getCommand()
67                       .getParameter("unasoc").getFirstValue());
68           }
69   
70           // setup
71           Session s = Lifecycle.getHibernateDataSource().createNewSession();
72           Transaction tx = s.beginTransaction();
73           JSONArray data = null;
74   
75           try {
76   
77               // build criteria
78               Criteria criteria = buildLoadAssorterCriteria(request, isAsoc, s,
79                       id, clazz, alias);
80   
81               // total for paging
82               long total = processCriteriaForPaging(request, criteria);
83   
84               // load clazz
85               @SuppressWarnings("unchecked")
86               List<AbstractAccessControlObject> loadedObjects = (List<AbstractAccessControlObject>) criteria
87                       .addOrder(Order.asc("name").ignoreCase()).list();
88   
89               // build data-array
90               data = buildDataJSONArray(isAsoc, total, loadedObjects);
91   
92               tx.commit();
93           } catch (Exception e) {
94               tx.rollback();
95               throw new PulseException(getClass().getSimpleName() + ".load"
96                       + clazz.getSimpleName() + "s.failed: "
97                       + e.getLocalizedMessage(), e);
98           } finally {
99               s.close();
100          }
101          return data;
102      }
103  
104      /**
105       * Loads the assorter-data for the <b>joined</b> entities.
106       * 
107       * @param request
108       *            the {@code ServiceRequest}
109       * @param s
110       *            the hibernate<sup>TM</sup>-{@code Session}
111       * @param clazz
112       *            the {@code Class&lt;? extends AbstractAccessControlObject&gt;}
113       * @param ids
114       *            the ids
115       * 
116       * @return the data-{@code JSONArray}
117       */
118      @SuppressWarnings("unchecked")
119      protected final JSONArray loadAssorterData(final ServiceRequest request,
120              final Session s,
121              final Class<? extends AbstractAccessControlObject> clazz,
122              final List<Long> ids) {
123  
124          boolean isAsoc = false;
125          if (request.getCommand().getParameter("unasoc") != null) {
126              isAsoc = Boolean.parseBoolean(request.getCommand()
127                      .getParameter("unasoc").getFirstValue());
128          }
129  
130          Criteria criteria = buildLoadAssorterCriteria(request, isAsoc, s, ids,
131                  clazz);
132  
133          long total = 0;
134          List<AbstractAccessControlObject> loadedObjects;
135          if (criteria != null) {
136              total = processCriteriaForPaging(request, criteria);
137              // load roles
138              loadedObjects = (List<AbstractAccessControlObject>) criteria
139                      .addOrder(Order.asc("name").ignoreCase()).list();
140          } else {
141              loadedObjects = new ArrayList<AbstractAccessControlObject>();
142          }
143  
144          // build data-array
145          return buildDataJSONArray(isAsoc, total, loadedObjects);
146      }
147  
148      /**
149       * builds the data-{@code JSONArray} from the given
150       * {@code AbstractAccessControlObject}s with the given total and the given
151       * unasoc.
152       * 
153       * @param isAsoc
154       *            the flag which determines the association
155       * @param total
156       *            the total
157       * @param loadedObjects
158       *            the {@code AbstractAccessControlObject}s
159       * 
160       * @return the data-{@code JSONArray}
161       */
162      private JSONArray buildDataJSONArray(final boolean isAsoc,
163              final long total,
164              final List<AbstractAccessControlObject> loadedObjects) {
165          JSONArray data = new JSONArray();
166          boolean totalSet = false;
167          for (AbstractAccessControlObject acObj : loadedObjects) {
168              JSONObject json = acObj.toJSON();
169              if (!totalSet) {
170                  json.put("total", total);
171                  totalSet = true;
172              }
173              json.put("initialAsoc", !isAsoc);
174              data.add(json);
175          }
176          return data;
177      }
178  
179      /**
180       * builds the {@code Criteria} for the assorter to load the <b>contained</b>
181       * entities.
182       * 
183       * @param request
184       *            the current {@code ServiceRequest}
185       * @param unasoc
186       *            {@code true} to retrieve criteria for loading of
187       *            {@code Class&lt;? extends AbstractBasicEntity&gt;}s,
188       *            {@code false} for criteria for loading of UN-associated
189       *            {@code Class&lt;?
190       *            extends AbstractBasicEntity&gt;}s
191       * @param s
192       *            the current {@code Session}
193       * @param id
194       *            the id
195       * @param clazz
196       *            the {@code Class&lt;? extends AbstractBasicEntity&gt;}
197       * @param alias
198       *            the field in the clazz
199       * 
200       * @return the built {@code Criteria}
201       */
202      protected final Criteria buildLoadAssorterCriteria(
203              final ServiceRequest request, final boolean unasoc,
204              final Session s, final Long id,
205              final Class<? extends AbstractBasicEntity> clazz, final String alias) {
206  
207          Criterion nameCriterion = buildCriterionFromRequest(request, "filter",
208                  "name");
209  
210          Criteria criteria;
211          if (unasoc) {
212              // return !associated
213              DetachedCriteria dc = DetachedCriteria.forClass(clazz)
214                      .createAlias(alias, "r").add(Restrictions.eq("r.id", id))
215                      .setProjection(Property.forName("id"));
216  
217              criteria = s.createCriteria(clazz).add(
218                      Restrictions.not(Subqueries.propertyIn("id", dc)));
219          } else {
220              // return associated
221              criteria = s.createCriteria(clazz).createAlias(alias, "r")
222                      .add(Restrictions.eq("r.id", id));
223          }
224  
225          // take care of expunged Users
226          if (clazz.getCanonicalName().equals(User.class.getCanonicalName())) {
227              criteria.add(Restrictions.ne("state", State.EXPUNGED));
228          }
229  
230          // add name criteria
231          if (nameCriterion != null) {
232              criteria.add(nameCriterion);
233          }
234          return criteria;
235      }
236  
237      /**
238       * builds the {@code Criteria} for the assorter to load the <b>joined</b>
239       * entities.
240       * 
241       * @param request
242       *            the current {@code ServiceRequest}
243       * @param unasoc
244       *            {@code true} to retrieve criteria for loading of associated
245       *            {@code Class&lt;? extends AbstractBasicEntity&gt;}s,
246       *            {@code false} for criteria for loading of UN-associated
247       *            {@code Class&lt;? extends AbstractBasicEntity&gt;}s
248       * @param s
249       *            the current {@code Session}
250       * @param ids
251       *            the ids
252       * @param clazz
253       *            the {@code Class&lt;? extends AbstractBasicEntity&gt;}
254       * 
255       * @return the built {@code Criteria}
256       */
257      protected final Criteria buildLoadAssorterCriteria(
258              final ServiceRequest request, final boolean unasoc,
259              final Session s, final List<Long> ids,
260              final Class<? extends AbstractBasicEntity> clazz) {
261  
262          // create a Criterion for the User's name
263          Criterion filterCriterion = buildCriterionFromRequest(request,
264                  "filter", "name");
265  
266          Criteria criteria;
267  
268          if (unasoc) {
269              criteria = s.createCriteria(clazz);
270              if (!ids.isEmpty()) {
271                  criteria.add(Restrictions.not(Restrictions.in("id", ids)));
272              }
273          } else {
274              if (!ids.isEmpty()) {
275                  criteria = s.createCriteria(clazz).add(
276                          Restrictions.in("id", ids));
277  
278              } else {
279                  return null;
280              }
281          }
282  
283          // add name criteria
284          if (filterCriterion != null) {
285              criteria.add(filterCriterion);
286          }
287  
288          return criteria;// .addOrder(Order.asc("name"));
289      }
290  
291      /**
292       * @param identifier
293       *            further identifies the request-parameter to look for
294       * @param request
295       *            the current {@code ServiceRequest}
296       * @param clazz
297       *            the class to load
298       * @param s
299       *            the current {@code Session}
300       * 
301       * @return the list of the loaded objects defined by clazz
302       */
303      protected final List<Object> retrieveAssorterObjects(
304              final String identifier, final ServiceRequest request,
305              final String clazz, final Session s) {
306  
307          List<Object> list = new ArrayList<Object>();
308  
309          // check request
310          if (request.getCommand().getParameter(identifier) != null
311                  && request.getCommand().getParameter(identifier).getValues()
312                          .size() > 0) {
313  
314              // retrieve ids
315              List<String> ids = request.getCommand().getParameter(identifier)
316                      .getValues();
317  
318              // add
319              for (String id : ids) {
320                  try {
321                      list.add(s.createQuery("from " + clazz + " c where c.id=?")
322                              .setLong(0, Long.parseLong(id)).uniqueResult());
323                  } catch (NumberFormatException e) {
324                      LOGGER.debug(
325                              "EXCEPTION.ignored: AccessControlEditor.retrieveAssorterObjects(id:{}, clazz:{}, identifier:{}) \"{}\"",
326                              new Object[] { id, clazz, identifier,
327                                      e.getLocalizedMessage() });
328                  }
329              }
330  
331          }
332  
333          return list;
334      }
335  
336      /**
337       * builds {@code Restrictions.ilike} from request-parameter for given
338       * property-name with {@code MatchMode.ANYWHERE}.
339       * 
340       * @param request
341       *            the current {@code ServiceRequest}
342       * @param parameterName
343       *            the name of the request-parameter
344       * @param propertyName
345       *            the name of the property
346       * 
347       * @return a {@code Criterion} if request-parameter name != '' or null, null
348       *         otherwise
349       */
350      protected final Criterion buildCriterionFromRequest(
351              final ServiceRequest request, final String parameterName,
352              final String propertyName) {
353          if (request.getCommand().getParameter(parameterName) != null
354                  && !request.getCommand().getParameter(parameterName)
355                          .getFirstValue().trim().equals("")) {
356              return Restrictions.ilike(propertyName, request.getCommand()
357                      .getParameter(parameterName).getFirstValue().trim(),
358                      MatchMode.ANYWHERE);
359          }
360          return null;
361      }
362  
363      /**
364       * builds {@code Restrictions.idEq} from request.
365       * 
366       * @param request
367       *            the current {@code ServiceRequest}
368       * 
369       * @return a {@code Criterion} if request-parameter id != '' or null, null
370       *         otherwise
371       */
372      protected final Criterion buildIdCriterionFromRequest(
373              final ServiceRequest request) {
374          if (request.getCommand().getParameter("id") != null
375                  && !request.getCommand().getParameter("id").getFirstValue()
376                          .trim().equals("")) {
377              try {
378                  return Restrictions.idEq(Long.parseLong(request.getCommand()
379                          .getParameter("id").getFirstValue().trim()));
380              } catch (NumberFormatException e) {
381                  return Restrictions.idEq(0L);
382              }
383  
384          }
385          return null;
386      }
387  
388      /**
389       * returns the total number of results for the current criteria as integer
390       * and applies paging-start/limit to the criteria.
391       * 
392       * @param request
393       *            the current {@code ServiceRequest}
394       * @param criteria
395       *            the criteria
396       * 
397       * @return the total number of results for the current criteria as integer
398       */
399      protected final long processCriteriaForPaging(final ServiceRequest request,
400              final Criteria criteria) {
401  
402          int start = Integer.parseInt(request.getCommand().getParameter("start")
403                  .getFirstValue());
404          int limit = Integer.parseInt(request.getCommand().getParameter("limit")
405                  .getFirstValue());
406  
407          long total = (Long) criteria.setProjection(Projections.rowCount())
408                  .uniqueResult();
409  
410          // unset projection
411          criteria.setProjection(null);
412          criteria.setResultTransformer(Criteria.ROOT_ENTITY);
413  
414          // paging
415          if (total > start) {
416              criteria.setFirstResult(start).setMaxResults(limit);
417          } else {
418              criteria.setFirstResult(0).setMaxResults(limit);
419          }
420  
421          return total;
422  
423      }
424  
425      /**
426       * returns the parameter-value; {@code null} if request-parameter
427       * parameterName is null or "".
428       * 
429       * @param request
430       *            the current {@code ServiceRequest}
431       * @param parameterName
432       *            the name of the request-parameter
433       * 
434       * @return the parameter-value, {@code null} if request-parameter
435       *         parameterName is null or ""
436       */
437      protected final String getReqParamOrNullIfValueEmpty(
438              final ServiceRequest request, final String parameterName) {
439          if (request.getCommand().getParameter(parameterName) != null
440                  && !request.getCommand().getParameter(parameterName)
441                          .getFirstValue().trim().equals("")) {
442              return request.getCommand().getParameter(parameterName)
443                      .getFirstValue().trim();
444          }
445          return null;
446      }
447  
448      /**
449       * returns the parameter-value or "", {@code null} if request-parameter
450       * parameterName is null.
451       * 
452       * @param request
453       *            the current {@code ServiceRequest}
454       * @param parameterName
455       *            the name of the request-parameter
456       * 
457       * @return the parameter-value or "", {@code null} if request-parameter
458       *         parameterName is null
459       */
460      protected final String getReqParamIgnoreEmptyValue(
461              final ServiceRequest request, final String parameterName) {
462          if (request.getCommand().getParameter(parameterName) != null) {
463              return request.getCommand().getParameter(parameterName)
464                      .getFirstValue().trim();
465          }
466          return null;
467      }
468  
469  }
470