1    /*
2     * Copyright 2008 :torweg free software group
3     * 
4     * This program is free software: you can redistribute it and/or modify
5     * it under the terms of the GNU General Public License as published by
6     * the Free Software Foundation, either version 3 of the License, or
7     * (at your option) any later version.
8     * 
9     * This program is distributed in the hope that it will be useful,
10    * but WITHOUT ANY WARRANTY; without even the implied warranty of
11    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    * GNU General Public License for more details.
13    * 
14    * You should have received a copy of the GNU General Public License
15    * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16    *
17    */
18   package org.torweg.pulse.accesscontrol.attributes;
19   
20   import java.io.Serializable;
21   import java.util.HashSet;
22   import java.util.Map;
23   import java.util.Set;
24   
25   import javax.persistence.Basic;
26   import javax.persistence.CascadeType;
27   import javax.persistence.Entity;
28   import javax.persistence.Inheritance;
29   import javax.persistence.InheritanceType;
30   import javax.persistence.JoinColumn;
31   import javax.persistence.OneToMany;
32   import javax.xml.bind.annotation.XmlAccessOrder;
33   import javax.xml.bind.annotation.XmlAccessType;
34   import javax.xml.bind.annotation.XmlAccessorOrder;
35   import javax.xml.bind.annotation.XmlAccessorType;
36   import javax.xml.bind.annotation.XmlElement;
37   import javax.xml.bind.annotation.XmlElementWrapper;
38   import javax.xml.bind.annotation.XmlRootElement;
39   import javax.xml.bind.annotation.XmlTransient;
40   
41   import net.sf.json.JSONObject;
42   
43   import org.hibernate.LazyInitializationException;
44   import org.hibernate.Session;
45   import org.jdom.Element;
46   import org.slf4j.Logger;
47   import org.slf4j.LoggerFactory;
48   import org.torweg.pulse.accesscontrol.Role;
49   import org.torweg.pulse.accesscontrol.User;
50   import org.torweg.pulse.bundle.JDOMable;
51   import org.torweg.pulse.invocation.lifecycle.Lifecycle;
52   import org.torweg.pulse.service.request.Command;
53   import org.torweg.pulse.util.INamed;
54   import org.torweg.pulse.util.entity.Node;
55   
56   /**
57    * base class for all attributes.
58    * 
59    * @param <T>
60    *            the type of the attribute's value
61    * @author Thomas Weber, Christian Schatt, Daniel Dietz
62    * @version $Revision: 1809 $
63    */
64   @Entity
65   @Inheritance(strategy = InheritanceType.JOINED)
66   @XmlRootElement(name = "abstract-attribute")
67   @XmlAccessorOrder(XmlAccessOrder.UNDEFINED)
68   @XmlAccessorType(XmlAccessType.FIELD)
69   public abstract class AbstractAttribute<T> extends Node implements INamed,
70           JDOMable, Serializable {
71   
72       /**
73        * serialVersionUID.
74        */
75       private static final long serialVersionUID = 5937048602470227822L;
76   
77       /**
78        * the logger.
79        */
80       protected static final Logger LOGGER = LoggerFactory
81               .getLogger(AbstractAttribute.class);
82   
83       /**
84        * the {@code Role}s which allow viewing the attribute for one's own {@code
85        * User}.
86        */
87       @OneToMany(cascade = { CascadeType.MERGE, CascadeType.PERSIST,
88               CascadeType.REFRESH })
89       @JoinColumn(nullable = true)
90       @XmlTransient
91       // getter JAXB_annotated
92       private final Set<Role> selfViewRoles = new HashSet<Role>();
93   
94       /**
95        * the {@code Role}s which allow editing the attribute for one's own {@code
96        * User}.
97        */
98       @OneToMany(cascade = { CascadeType.MERGE, CascadeType.PERSIST,
99               CascadeType.REFRESH })
100      @JoinColumn(nullable = true)
101      @XmlTransient
102      // getter JAXB_annotated
103      private final Set<Role> selfEditRoles = new HashSet<Role>();
104  
105      /**
106       * the {@code Role}s which allow viewing the attribute for arbitrary {@code
107       * User}s in the administration interface.
108       */
109      @OneToMany(cascade = { CascadeType.MERGE, CascadeType.PERSIST,
110              CascadeType.REFRESH })
111      @JoinColumn(nullable = true)
112      @XmlTransient
113      // getter JAXB_annotated
114      private final Set<Role> adminViewRoles = new HashSet<Role>();
115  
116      /**
117       * the {@code Role}s which allow editing the attribute for arbitrary {@code
118       * User}s in the administration interface.
119       */
120      @OneToMany(cascade = { CascadeType.MERGE, CascadeType.PERSIST,
121              CascadeType.REFRESH })
122      @JoinColumn(nullable = true)
123      @XmlTransient
124      // getter JAXB_annotated
125      private final Set<Role> adminEditRoles = new HashSet<Role>();
126  
127      /**
128       * the {@code Role}s which get assigned to {@code User}s, when the attribute
129       * and all its sub-attributes are valid.
130       */
131      @OneToMany(cascade = { CascadeType.MERGE, CascadeType.PERSIST,
132              CascadeType.REFRESH })
133      @JoinColumn(nullable = true)
134      @XmlTransient
135      // getter JAXB_annotated
136      private final Set<Role> triggeredRoles = new HashSet<Role>();
137  
138      /**
139       * flag, indicating whether the attribute is a system attribute.
140       */
141      @Basic
142      @XmlElement(name = "system-attribute")
143      private final boolean systemAttribute;
144  
145      /**
146       * flag, indicating whether the attribute is a required attribute.
147       */
148      @Basic
149      @XmlElement(name = "required")
150      private boolean required;
151  
152      /**
153       * used by JAXB.
154       */
155      @Deprecated
156      protected AbstractAttribute() {
157          super();
158          this.systemAttribute = false;
159      }
160  
161      /**
162       * protected constructor to be called by the constructors of all subclasses.
163       * 
164       * @param isSystemAttribute
165       *            flag, indicating whether the attribute is a system attribute
166       */
167      protected AbstractAttribute(final boolean isSystemAttribute) {
168          super();
169          this.systemAttribute = isSystemAttribute;
170      }
171  
172      /**
173       * sets the {@code Role}s which allow viewing the attribute for one's own
174       * {@code User}.
175       * 
176       * @param r
177       *            the roles to set
178       */
179      public final void setSelfViewRoles(final Set<Role> r) {
180          this.selfViewRoles.clear();
181          this.selfViewRoles.addAll(r);
182      }
183  
184      /**
185       * adds a given {@code Role} to the self-view-roles.
186       * 
187       * @param r
188       *            the role to add
189       * 
190       * @return boolean
191       */
192      public final boolean addSelfViewRole(final Role r) {
193          return this.selfViewRoles.add(r);
194  
195      }
196  
197      /**
198       * removes a given {@code Role} from the self-view-roles.
199       * 
200       * @param r
201       *            the role to remove
202       * 
203       * @return boolean
204       */
205      public final boolean removeSelfViewRole(final Role r) {
206          return this.selfViewRoles.remove(r);
207      }
208  
209      /**
210       * returns the {@code Role}s which allow viewing the attribute for one's own
211       * {@code User}.
212       * 
213       * @return the roles
214       */
215      public final Set<Role> getSelfViewRoles() {
216          return this.selfViewRoles;
217      }
218  
219      /**
220       * For JAXB only.
221       * 
222       * @return this.getSelfViewRoles()
223       */
224      @XmlElementWrapper(name = "self-view-roles")
225      @XmlElement(name = "role")
226      @SuppressWarnings("unused")
227      @Deprecated
228      private HashSet<Role> getSelfViewRolesJAXB() { // NOPMD
229          try {
230              return new HashSet<Role>(getSelfViewRoles());
231          } catch (LazyInitializationException e) {
232              LOGGER.debug("ignored: {}", e.getLocalizedMessage());
233              return null;
234          }
235      }
236  
237      /**
238       * returns the {@code Role}s which allow editing the attribute for one's own
239       * {@code User}.
240       * 
241       * @return the roles
242       */
243      public final Set<Role> getSelfEditRoles() {
244          return this.selfEditRoles;
245      }
246  
247      /**
248       * sets the {@code Role}s which allow editing the attribute for one's own
249       * {@code User}.
250       * 
251       * @param r
252       *            the roles to set
253       */
254      public final void setSelfEditRoles(final Set<Role> r) {
255          this.selfEditRoles.clear();
256          this.selfEditRoles.addAll(r);
257      }
258  
259      /**
260       * adds a given {@code Role} to the self-edit-roles.
261       * 
262       * @param r
263       *            the role to add
264       * 
265       * @return boolean
266       */
267      public final boolean addSelfEditRole(final Role r) {
268          return this.selfEditRoles.add(r);
269  
270      }
271  
272      /**
273       * removes a given {@code Role} from the self-edit-roles.
274       * 
275       * @param r
276       *            the role to remove
277       * 
278       * @return boolean
279       */
280      public final boolean removeSelfEditRole(final Role r) {
281          return this.selfEditRoles.remove(r);
282      }
283  
284      /**
285       * For JAXB only.
286       * 
287       * @return this.getSelfEditRoles()
288       */
289      @XmlElementWrapper(name = "self-edit-roles")
290      @XmlElement(name = "role")
291      @SuppressWarnings("unused")
292      @Deprecated
293      private HashSet<Role> getSelfEditRolesJAXB() { // NOPMD
294          try {
295              return new HashSet<Role>(getSelfEditRoles());
296          } catch (LazyInitializationException e) {
297              LOGGER.debug("ignored: {}", e.getLocalizedMessage());
298              return null;
299          }
300      }
301  
302      /**
303       * sets the {@code Role}s which allow viewing the attribute for arbitrary
304       * {@code User}s in the administration interface.
305       * 
306       * @param r
307       *            the roles to set
308       */
309      public final void setAdminViewRoles(final Set<Role> r) {
310          this.adminViewRoles.clear();
311          this.adminViewRoles.addAll(r);
312      }
313  
314      /**
315       * adds a given {@code Role} to the admin-view-roles.
316       * 
317       * @param r
318       *            the role to add
319       * 
320       * @return boolean
321       */
322      public final boolean addAdminViewRole(final Role r) {
323          return this.adminViewRoles.add(r);
324  
325      }
326  
327      /**
328       * removes a given {@code Role} from the admin-view-roles.
329       * 
330       * @param r
331       *            the role to remove
332       * 
333       * @return boolean
334       */
335      public final boolean removeAdminViewRole(final Role r) {
336          return this.adminViewRoles.remove(r);
337      }
338  
339      /**
340       * returns the {@code Role}s which allow viewing the attribute for arbitrary
341       * {@code User}s in the administration interface.
342       * 
343       * @return the roles
344       */
345      public final Set<Role> getAdminViewRoles() {
346          return this.adminViewRoles;
347      }
348  
349      /**
350       * For JAXB only.
351       * 
352       * @return this.getAdminViewRoles()
353       */
354      @XmlElementWrapper(name = "admin-view-roles")
355      @XmlElement(name = "role")
356      @SuppressWarnings("unused")
357      @Deprecated
358      private HashSet<Role> getAdminViewRolesJAXB() { // NOPMD
359          try {
360              return new HashSet<Role>(getAdminViewRoles());
361          } catch (LazyInitializationException e) {
362              LOGGER.debug("ignored: {}", e.getLocalizedMessage());
363              return null;
364          }
365      }
366  
367      /**
368       * sets the {@code Role}s which allow editing the attribute for arbitrary
369       * {@code User}s in the administration interface.
370       * 
371       * @param r
372       *            the roles to set
373       */
374      public final void setAdminEditRoles(final Set<Role> r) {
375          this.adminEditRoles.clear();
376          this.adminEditRoles.addAll(r);
377      }
378  
379      /**
380       * adds a given {@code Role} to the admin-edit-roles.
381       * 
382       * @param r
383       *            the role to add
384       * 
385       * @return boolean
386       */
387      public final boolean addAdminEditRole(final Role r) {
388          return this.adminEditRoles.add(r);
389  
390      }
391  
392      /**
393       * removes a given {@code Role} from the admin-edit-roles.
394       * 
395       * @param r
396       *            the role to remove
397       * 
398       * @return boolean
399       */
400      public final boolean removeAdminEditRole(final Role r) {
401          return this.adminEditRoles.remove(r);
402      }
403  
404      /**
405       * returns the {@code Role}s which allow editing the attribute for arbitrary
406       * {@code User}s in the administration interface.
407       * 
408       * @return the roles
409       */
410      public final Set<Role> getAdminEditRoles() {
411          return this.adminEditRoles;
412      }
413  
414      /**
415       * For JAXB only.
416       * 
417       * @return this.getAdminEditRoles()
418       */
419      @XmlElementWrapper(name = "admin-edit-roles")
420      @XmlElement(name = "role")
421      @SuppressWarnings("unused")
422      @Deprecated
423      private HashSet<Role> getAdminEditRolesJAXB() { // NOPMD
424          try {
425              return new HashSet<Role>(getAdminEditRoles());
426          } catch (LazyInitializationException e) {
427              LOGGER.debug("ignored: {}", e.getLocalizedMessage());
428              return null;
429          }
430      }
431  
432      /**
433       * sets the {@code Role}s which get assigned to {@code User}s, when the
434       * attribute and all its sub-attributes are valid.
435       * 
436       * @param r
437       *            the roles to set
438       */
439      public final void setTriggeredRoles(final Set<Role> r) {
440          this.triggeredRoles.clear();
441          this.triggeredRoles.addAll(r);
442      }
443  
444      /**
445       * adds a given {@code Role} to the triggered-roles.
446       * 
447       * @param r
448       *            the role to add
449       * 
450       * @return boolean
451       */
452      public final boolean addTriggeredRole(final Role r) {
453          return this.triggeredRoles.add(r);
454  
455      }
456  
457      /**
458       * removes a given {@code Role} from the triggered-roles.
459       * 
460       * @param r
461       *            the role to remove
462       * 
463       * @return boolean
464       */
465      public final boolean removeTriggeredRole(final Role r) {
466          return this.triggeredRoles.remove(r);
467      }
468  
469      /**
470       * returns the {@code Role}s which get assigned to {@code User}s, when the
471       * attribute and all its sub-attributes are valid.
472       * 
473       * @return the triggered roles
474       */
475      public final Set<Role> getTriggeredRoles() {
476          return this.triggeredRoles;
477      }
478  
479      /**
480       * For JAXB only.
481       * 
482       * @return this.getTriggeredRoles()
483       */
484      @XmlElementWrapper(name = "triggered-roles")
485      @XmlElement(name = "role")
486      @SuppressWarnings("unused")
487      @Deprecated
488      private HashSet<Role> getTriggeredRolesJAXB() { // NOPMD
489          try {
490              return new HashSet<Role>(getTriggeredRoles());
491          } catch (LazyInitializationException e) {
492              LOGGER.debug("ignored: {}", e.getLocalizedMessage());
493              return null;
494          }
495      }
496  
497      /**
498       * returns whether the attribute is a system attribute.
499       * 
500       * @return {@code true}, if the attribute is a system attribute
501       */
502      public final boolean isSystemAttribute() {
503          return this.systemAttribute;
504      }
505  
506      /**
507       * returns whether the attribute is required.
508       * 
509       * @return {@code true}, if the attribute is required.
510       */
511      public final boolean isRequired() {
512          return this.required;
513      }
514  
515      /**
516       * sets, whether the attribute is required.
517       * 
518       * @param r
519       *            {@code true}, if the attribute is required.
520       */
521      public final void setRequired(final boolean r) {
522          this.required = r;
523      }
524  
525      /**
526       * returns whether the value of the user's attribute is valid according to
527       * the attribute's check.
528       * <p>
529       * If the {@code AbstractTypedCheck} is {@code null}, the value is
530       * considered valid. A {@code null} value is considered invalid.
531       * </p>
532       * 
533       * @param user
534       *            the user
535       * @return whether the value of the attribute is valid
536       */
537      public final boolean isValid(final User user) {
538          if (getCheck() != null) {
539              if (getValue(user) == null) {
540                  return false;
541              }
542              if (!getCheck().isValid(getValue(user))) {
543                  return false;
544              }
545          }
546          for (Node n : getChildren()) {
547              if (!((AbstractAttribute<?>) n).isValid(user)) {
548                  return false;
549              }
550          }
551          return true;
552  
553          // if (getCheck() == null) {
554          // return true;
555          // }
556          // if (getValue(user) == null) {
557          // return false;
558          // }
559          // return getCheck().isValid(getValue(user));
560  
561      }
562  
563      /**
564       * returns the name of the attribute.
565       * 
566       * @return the name of the attribute
567       */
568      public abstract String getName();
569  
570      /**
571       * returns the value of the attribute for the given user.
572       * 
573       * @param user
574       *            the user
575       * @return the value
576       */
577      @SuppressWarnings("unchecked")
578      public final AbstractValue<T> getValue(final User user) {
579          return (AbstractValue<T>) user.getAttributeValue(this);
580      }
581  
582      /**
583       * sets the value of the attribute for the given user.
584       * 
585       * @param value
586       *            the value to be set
587       * @param user
588       *            the user
589       * @return the modified user
590       */
591      public final User setValue(final AbstractValue<T> value, final User user) {
592          if (value.getAttribute() == null) {
593              value.setAttribute(this);
594          } else if (!value.getAttribute().equals(this)) {
595              throw new IllegalArgumentException("cannot set attribute.value: "
596                      + "given value is already associated "
597                      + "with another attribute");
598          }
599          user.setAttributeValue(value);
600          return user;
601      }
602  
603      /**
604       * returns a set with the available typed checks for the attribute.
605       * 
606       * @return a set with the available checks
607       */
608      @SuppressWarnings("unchecked")
609      public final Set<Class<AbstractTypedCheck<?>>> getTypedChecks() {
610          return Lifecycle.getAttributeFactory().getTypedChecks(
611                  (Class<AbstractAttribute<?>>) this.getClass());
612      }
613  
614      /**
615       * sets the attrubite's check.
616       * 
617       * @param c
618       *            the check to set
619       */
620      public abstract void setCheck(final AbstractTypedCheck<T> c);
621  
622      /**
623       * returns the attribute's check.
624       * 
625       * @return the attribute's check
626       */
627      public abstract AbstractTypedCheck<T> getCheck();
628  
629      /**
630       * creator-method.
631       * 
632       * <p>
633       * Used by {@code AttributeFactory}.
634       * </p>
635       * 
636       * @param name
637       *            the name of the new attribute
638       * @param isSystem
639       *            indicates if new attribute is system-attribute
640       * 
641       * @return a <strong>new</strong> attribute
642       */
643      public abstract AbstractAttribute<T> getAttributeInstance(String name,
644              boolean isSystem);
645  
646      /**
647       * initialises a value of this attribute from the given command.
648       * 
649       * @param c
650       *            the command
651       * @return a value of this attribute initialised from the given command
652       */
653      public abstract AbstractValue<T> valueFromCommand(Command c);
654  
655      /**
656       * updates the attribute's settings from the given command.
657       * <p>
658       * This method assumes that it is part of a larger transaction, thus the
659       * changes to the session are neither encapsulated in a separate transaction
660       * nor commited.
661       * </p>
662       * 
663       * @param command
664       *            the command
665       * @param session
666       *            the session for persisting the updates
667       * @return the modified attribute
668       */
669      public abstract AbstractAttribute<T> updateSettingsFromCommand(
670              Command command, Session session);
671  
672      /**
673       * returns a JDOM representation of the attribute.
674       * <p>
675       * Classes extending {@code AbstractAttribute} are recommended, but not
676       * required to call this method via {@code super.deserializeToJDOM()} and
677       * just add their result to the returned element in a separate
678       * value-element.
679       * </p>
680       * <p>
681       * If {@code <T>} is an instance of {@code JDOMable}, the value is also
682       * added by this method.
683       * </p>
684       * 
685       * @return a JDOM representation of the attribute
686       * @see org.torweg.pulse.bundle.JDOMable#deserializeToJDOM()
687       */
688      public Element deserializeToJDOM() {
689          Element attr = getRootElement();
690          /* add self-view-roles */
691          Element svr = new Element("self-view-roles");
692          addRoleSet(svr, this.selfViewRoles);
693          attr.addContent(svr);
694          /* add self-edit-roles */
695          Element ser = new Element("self-edit-roles");
696          addRoleSet(ser, this.selfEditRoles);
697          attr.addContent(ser);
698          /* add admin-view-roles */
699          Element avr = new Element("admin-view-roles");
700          addRoleSet(avr, this.adminViewRoles);
701          attr.addContent(avr);
702          /* add admin-edit-roles */
703          Element aer = new Element("admin-edit-roles");
704          addRoleSet(aer, this.adminEditRoles);
705          attr.addContent(aer);
706          /* add triggered roles */
707          Element tr = new Element("triggered-roles");
708          addRoleSet(tr, this.triggeredRoles);
709          attr.addContent(tr);
710          /* add check */
711          if (getCheck() != null) {
712              attr.addContent(getCheck().deserializeToJDOM());
713          }
714          return attr;
715      }
716  
717      /**
718       * returns a {@code Element}-representation of the attribute without roles.
719       * 
720       * @return a {@code Element}-representation of the attribute without roles
721       */
722      private Element getRootElement() {
723          Element attr = new Element("Attribute").setAttribute("class",
724                  getClass().getCanonicalName()).setAttribute("name", getName())
725                  .setAttribute("system", Boolean.toString(this.systemAttribute))
726                  .setAttribute("required", Boolean.toString(this.required))
727                  .setAttribute("id", getId().toString());
728          return attr;
729      }
730  
731      /**
732       * used during deserialization of a {@code User}.
733       * 
734       * <p>
735       * Does not add the roles!
736       * </p>
737       * 
738       * @param valueMap
739       *            maps attribute-ids a {@code User}s attribute-values
740       * 
741       * @return a representation of the attribute which is required for the User.
742       */
743      public Element deserializeToJDOM(final Map<Long, AbstractValue<?>> valueMap) {
744          Element attr = getRootElement();
745          // adds the value for this attribute if in valueMap
746          if (valueMap.get(getId()) != null) {
747              attr.addContent(valueMap.get(getId()).deserializeToJDOM());
748          }
749  
750          for (Node n : getChildren()) {
751              // adds child-atributes
752              AbstractAttribute<?> cAttr = (AbstractAttribute<?>) n;
753              attr.addContent(cAttr.deserializeToJDOM(valueMap));
754          }
755  
756          return attr;
757      }
758  
759      /**
760       * returns a representation of the attribute for the attribute-tree in the
761       * web-site-administration.
762       * 
763       * @return a {@code JSONObject}
764       */
765      @Override
766      public JSONObject toJSON() {
767          JSONObject nodeObject = super.toJSON();
768          // nodeObject.put("id", getId());
769          nodeObject.put("clazz", getClass().getCanonicalName());
770          nodeObject.put("text", getName());
771          nodeObject.put("uiProvider", "RegistryTreeNodeUI");
772          nodeObject.put("isSystem", isSystemAttribute());
773          nodeObject.put("isRequired", isRequired());
774          return nodeObject;
775      }
776  
777      /**
778       * adds the given set of roles to the given element.
779       * 
780       * @param el
781       *            the element
782       * @param rs
783       *            the set of roles
784       */
785      private void addRoleSet(final Element el, final Set<Role> rs) {
786          try {
787              for (Role r : rs) {
788                  el.addContent(r.deserializeToJDOM());
789              }
790          } catch (LazyInitializationException e) {
791              if (LOGGER.isTraceEnabled()) {
792                  LOGGER.trace(e.getLocalizedMessage(), e);
793              }
794          }
795      }
796  
797  }
798