1    /*
2     * Copyright 2006 :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   package org.torweg.pulse.site.content;
18   
19   import java.io.IOException;
20   import java.io.StringReader;
21   import java.util.HashSet;
22   import java.util.Locale;
23   import java.util.Map;
24   import java.util.Set;
25   
26   import javax.persistence.Basic;
27   import javax.persistence.Column;
28   import javax.persistence.Lob;
29   import javax.persistence.MappedSuperclass;
30   import javax.persistence.Transient;
31   import javax.xml.bind.JAXBException;
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.XmlRootElement;
38   import javax.xml.bind.annotation.XmlTransient;
39   import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
40   
41   import org.hibernate.LazyInitializationException;
42   import org.jdom.Element;
43   import org.jdom.JDOMException;
44   import org.jdom.input.SAXBuilder;
45   import org.jdom.input.SAXHandler;
46   import org.slf4j.Logger;
47   import org.slf4j.LoggerFactory;
48   import org.torweg.pulse.accesscontrol.User;
49   import org.torweg.pulse.bundle.ExtendedJDOMable;
50   import org.torweg.pulse.configuration.PoorMansCache;
51   import org.torweg.pulse.invocation.lifecycle.Lifecycle;
52   import org.torweg.pulse.service.PulseException;
53   import org.torweg.pulse.service.request.ServiceRequest;
54   import org.torweg.pulse.site.View;
55   import org.torweg.pulse.site.content.util.ILinkCorrectableElement;
56   import org.torweg.pulse.util.xml.XMLConverter;
57   import org.torweg.pulse.util.xml.bind.ElementXmlAdapter;
58   
59   /**
60    * @author Christian Schatt, Daniel Dietz
61    * @version $Revision: 1984 $
62    */
63   @MappedSuperclass
64   @XmlRootElement(name = "abstract-basic-content")
65   @XmlAccessorOrder(XmlAccessOrder.UNDEFINED)
66   @XmlAccessorType(XmlAccessType.FIELD)
67   public abstract class AbstractBasicContent extends Content implements
68           ExtendedJDOMable {
69   
70       /**
71        * serialVersionUID.
72        */
73       private static final long serialVersionUID = 6376391145823134097L;
74   
75       /**
76        * The {@code Logger} of the {@code AbstractBasicContent}.
77        */
78       private static final Logger LOGGER = LoggerFactory
79               .getLogger(AbstractBasicContent.class);
80   
81       /**
82        * The default suffix of the {@code AbstractBasicContent}.
83        */
84       public static final String DEFAULT_SUFFIX = "index.html";
85   
86       /**
87        * The summary of the {@code AbstractBasicContent}.
88        */
89       @Lob
90       @Column(length = 1048576, nullable = false)
91       @XmlTransient
92       private String summary = "<body/>";
93   
94       /**
95        * The summary of the {@code AbstractBasicContent} as a JDOM {@code Element}
96        * .
97        */
98       @Transient
99       // getter JAXB-annotated
100      private transient Element summaryElement = null;
101  
102      /**
103       * The keywords of the {@code AbstractBasicContent}.
104       */
105      @Basic
106      @XmlElement(name = "keywords")
107      private String keywords = "";
108  
109      /**
110       * the title to be used for mark-up languages with a support for document
111       * titles, e.g. HTML.
112       */
113      @Basic
114      @XmlElement(name = "title")
115      private String title = null;
116  
117      /**
118       * the keywords to be used for mark-up languages with a support for document
119       * keywords, e.g. HTML through meta-keywords.
120       */
121      @Basic
122      @XmlElement(name = "meta-keywords")
123      private String metaKeywords = null;
124  
125      /**
126       * the description to be used for mark-up languages with a support for
127       * document descriptions, e.g. HTML through meta-descriptions.
128       */
129      @Basic
130      @XmlElement(name = "meta-description")
131      private String metaDescription = null;
132  
133      /**
134       * The suffix used for {@code Command}s accessing the
135       * {@code AbstractBasicContent}.
136       * 
137       * <p>
138       * The default value is {@code DEFAULT_SUFFIX}.
139       * </p>
140       */
141      @Basic
142      @XmlElement(name = "suffix")
143      private String suffix = DEFAULT_SUFFIX;
144  
145      /**
146       * Returns the summary of the {@code AbstractBasicContent}.
147       * 
148       * @return the summary of the {@code AbstractBasicContent}.
149       */
150      public final String getSummary() {
151          return this.summary;
152      }
153  
154      /**
155       * Sets the summary of the {@code AbstractBasicContent} from a JDOM
156       * {@code Element}.
157       * 
158       * @param pSummaryElement
159       *            the JDOM {@code Element} representing the summary to be set.
160       */
161      public final void setSummary(final Element pSummaryElement) {
162          this.summary = XMLConverter.getRawString(pSummaryElement, true);
163          this.summaryElement = pSummaryElement;
164      }
165  
166      /**
167       * Returns the summary of the {@code AbstractBasicContent} as a JDOM
168       * {@code Element}.
169       * 
170       * @return the summary of the {@code AbstractBasicContent} as a JDOM
171       *         {@code Element}.
172       */
173      public final Element getSummaryElement() {
174          if (getSummary() == null) {
175              return null;
176          }
177          if (this.summaryElement == null) {
178              try {
179                  this.summaryElement = new SAXBuilder().build(
180                          new StringReader(getSummary())).getRootElement();
181              } catch (JDOMException e) {
182                  throw new PulseException("Error parsing XML for summary: "
183                          + e.getLocalizedMessage(), e);
184              } catch (IOException e) {
185                  throw new PulseException("Error reading XML for summary: "
186                          + e.getLocalizedMessage(), e);
187              }
188          }
189          return (Element) this.summaryElement.clone();
190      }
191  
192      /**
193       * For JAXB only.
194       * 
195       * @return this.getSummaryElement()
196       */
197      @XmlElement(name = "summary-element")
198      @XmlJavaTypeAdapter(value = ElementXmlAdapter.class)
199      @SuppressWarnings("unused")
200      @Deprecated
201      private Element getSummaryElementJAXB() {
202          try {
203              return getSummaryElement();
204          } catch (LazyInitializationException e) {
205              LOGGER.debug("ignored: {}", e.getLocalizedMessage());
206              return null;
207          }
208      }
209  
210      /**
211       * returns the title to be used for mark-up languages with a support for
212       * document titles, e.g. HTML.
213       * 
214       * @return the title
215       */
216      public final String getTitle() {
217          return this.title;
218      }
219  
220      /**
221       * sets the title to be used for mark-up languages with a support for
222       * document titles, e.g. HTML.
223       * 
224       * @param t
225       *            the title to set
226       */
227      public final void setTitle(final String t) {
228          this.title = t;
229      }
230  
231      /**
232       * returns the keywords to be used for mark-up languages with a support for
233       * document keywords, e.g. HTML through meta-keywords.
234       * 
235       * @return the meta keywords
236       */
237      public final String getMetaKeywords() {
238          return this.metaKeywords;
239      }
240  
241      /**
242       * sets the keywords to be used for mark-up languages with a support for
243       * document keywords, e.g. HTML through meta-keywords.
244       * 
245       * @param kw
246       *            the meta keywords to set
247       */
248      public final void setMetaKeywords(final String kw) {
249          this.metaKeywords = kw;
250      }
251  
252      /**
253       * returns the description to be used for mark-up languages with a support
254       * for document descriptions, e.g. HTML through meta-descriptions.
255       * 
256       * @return the meta description
257       */
258      public final String getMetaDescription() {
259          return this.metaDescription;
260      }
261  
262      /**
263       * sets the keywords to be used for mark-up languages with a support for
264       * document descriptions, e.g. HTML through meta-descriptions.
265       * 
266       * @param md
267       *            the meta description to set
268       */
269      public final void setMetaDescription(final String md) {
270          this.metaDescription = md;
271      }
272  
273      /**
274       * Returns the suffix of the {@code AbstractBasicContent}.
275       * 
276       * @return the suffix of the {@code AbstractBasicContent}.
277       */
278      public final String getSuffix() {
279          return this.suffix;
280      }
281  
282      /**
283       * Sets the suffix of the {@code AbstractBasicContent}.
284       * 
285       * <p>
286       * If the given {@code String} contains at least one {@code /}, the part
287       * after its last occurrence will be set as suffix. If the given
288       * {@code String} is {@code null}, the suffix will be set to
289       * {@code DEFAULTSUFFIX}.
290       * </p>
291       * 
292       * @param pSuffix
293       *            the suffix to be set.
294       */
295      public final void setSuffix(final String pSuffix) {
296          if (pSuffix == null) {
297              this.suffix = DEFAULT_SUFFIX;
298              return;
299          }
300          AbstractBasicContentConfig config = (AbstractBasicContentConfig) PoorMansCache
301                  .getConfig(AbstractBasicContent.class);
302          this.suffix = config.convert(pSuffix.trim(), getLocale());
303      }
304  
305      /**
306       * Returns the keywords of the {@code AbstractBasicContent}.
307       * 
308       * @return the keywords of the {@code AbstractBasicContent}.
309       */
310      public final String getKeywords() {
311          return this.keywords;
312      }
313  
314      /**
315       * Sets the keywords of the {@code AbstractBasicContent}.
316       * 
317       * <p>
318       * If the given {@code String} is longer than 255 characters, it will be
319       * shortened to a maximum length of 255 characters, ending at the last space
320       * character within this range.
321       * </p>
322       * 
323       * @param pKeywords
324       *            the keywords to be set.
325       */
326      public final void setKeywords(final String pKeywords) {
327          if (pKeywords == null) {
328              this.keywords = "";
329              return;
330          }
331          if (pKeywords.length() > 255) {
332              this.keywords = pKeywords.substring(0, pKeywords.substring(0, 255)
333                      .lastIndexOf(" ") - 1);
334          } else {
335              this.keywords = pKeywords;
336          }
337      }
338  
339      /**
340       * Returns the {@code AbstractBasicContent}'s textual information as it is
341       * supposed to be supplied for the index.
342       * 
343       * <p>
344       * Implementing classes may override the method to supply more information
345       * for the index.
346       * </p>
347       * 
348       * @return the {@code AbstractBasicContent}'s textual information as it is
349       *         supposed to be supplied for the index.
350       */
351      @Override
352      public StringBuilder getFullTextValue() {
353          StringBuilder fullText = new StringBuilder();
354          fullText.append(getName());
355          String part = getKeywords();
356          if (part != null) {
357              fullText.append(' ').append(part);
358          }
359          part = getSuffix();
360          if (part != null) {
361              fullText.append(' ').append(part);
362          }
363          if (this.summaryElement != null) {
364              fullText.append(' ').append(
365                      XMLConverter.getHTMLText(getSummaryElement()));
366          }
367          return fullText;
368      }
369  
370      /**
371       * Returns the state of the {@code AbstractBasicContent} as a JDOM
372       * {@code Element}.
373       * 
374       * @return the state of the {@code AbstractBasicContent} as a JDOM
375       *         {@code Element}.
376       */
377      public Element deserializeToJDOM() {
378          return deserializeToJDOM(null);
379      }
380  
381      /**
382       * Returns the state of the {@code AbstractBasicContent} as a JDOM
383       * {@code Element}.
384       * 
385       * @param pServiceRequest
386       *            the current {@code ServiceRequest} or {@code null}.
387       * 
388       * @return the state of the {@code AbstractBasicContent} as a JDOM
389       *         {@code Element}.
390       */
391      public Element deserializeToJDOM(final ServiceRequest pServiceRequest) {
392          Element element = new Element("Content").setAttribute("class",
393                  getClass().getCanonicalName()).setAttribute("bundle",
394                  getBundle().getName());
395          if (getId() != null) {
396              element.setAttribute("id", getId().toString());
397          }
398          if (getLocale() != null) {
399              element.setAttribute("locale", getLocale().toString());
400          }
401          if (getName() != null) {
402              element.addContent(new Element("name").setText(getName()));
403          }
404          if (getTitle() != null) {
405              element.addContent(new Element("title").setText(getTitle()));
406          }
407          if (getMetaKeywords() != null) {
408              element.addContent(new Element("meta-keywords")
409                      .setText(getMetaKeywords()));
410          }
411          if (getMetaDescription() != null) {
412              element.addContent(new Element("meta-description")
413                      .setText(getMetaDescription()));
414          }
415          addContentLocalizationMap(element);
416          addViews(element);
417          addAttachments(pServiceRequest, element);
418          if (getSummary() != null) {
419              element.addContent(new Element("summary")
420                      .addContent((Element) getSummaryElement().clone()));
421          }
422          if (getKeywords() != null) {
423              element.addContent(new Element("keywords").setText(getKeywords()));
424          }
425          if (getSuffix() != null) {
426              element.addContent(new Element("suffix").setText(getSuffix()));
427          }
428  
429          // add creator/created an lastModifier/lastModified
430          element.addContent(new Element("creator").addContent(getCreator()
431                  .deserializeToJDOMShallow()));
432          element.addContent(new Element("created").addContent(getCreated()
433                  .toString()));
434          element.addContent(new Element("created-millis")
435                  .addContent(getCreatedMillis().toString()));
436          element.addContent(new Element("last-modifier")
437                  .addContent(getLastModifier().deserializeToJDOMShallow()));
438          element.addContent(new Element("last-modified")
439                  .addContent(getLastModified().toString()));
440  
441          // add reference duration
442          if (getReferenceDuration() != null) {
443              // node.addContent(getDuration().deserializeToJDOM());
444              SAXHandler jdomSaxHandler = new SAXHandler();
445              try {
446                  Lifecycle.getJAXBContext().createMarshaller()
447                          .marshal(getReferenceDuration(), jdomSaxHandler);
448              } catch (JAXBException e) {
449                  throw new PulseException("Error: " + e.getLocalizedMessage(), e);
450              }
451              element.addContent(new Element("reference-duration")
452                      .addContent(jdomSaxHandler.getDocument()
453                              .detachRootElement()));
454          }
455  
456          return element;
457      }
458  
459      /**
460       * overrides {@link Content#createCopy(Locale, User)}.
461       * 
462       * @param l
463       *            the locale for the copy
464       * @param u
465       *            the user for the copy
466       * @return the copy of the {@code AbstractBasicContent}
467       */
468      @Override
469      public abstract AbstractBasicContent createCopy(Locale l, User u);
470  
471      /**
472       * @return the set of {@code ILinkCorrectableElement}s
473       * @see org.torweg.pulse.site.content.util.ILinkCorretable#getLinkCorrectables()
474       */
475      public Set<ILinkCorrectableElement> getLinkCorrectables() {
476          Set<ILinkCorrectableElement> correctables = new HashSet<ILinkCorrectableElement>();
477          correctables.add(new SummaryCorrectable(this));
478          return correctables;
479      }
480  
481      /**
482       * Adds the {@code Attachment}s of the {@code Content} to the given JDOM
483       * {@code Element}.
484       * 
485       * @param pServiceRequest
486       *            the current {@code ServiceRequest} or {@code null}.
487       * @param pElement
488       *            the JDOM {@code Element} to be filled with data.
489       */
490      private void addAttachments(final ServiceRequest pServiceRequest,
491              final Element pElement) {
492          /* get user for extended information */
493          User user = null;
494          if (pServiceRequest != null) {
495              user = pServiceRequest.getUser();
496          }
497          Element element = new Element("attachments");
498          try {
499              for (Attachment attachment : getAttachments()) {
500                  Element attElem = attachment.deserializeToJDOM();
501                  if (pServiceRequest == null) {
502                      attElem.setAttribute("canRead", Boolean.toString(attachment
503                              .getVirtualFile().canRead(user)));
504                      attElem.setAttribute("canWrite", Boolean
505                              .toString(attachment.getVirtualFile()
506                                      .canWrite(user)));
507                  }
508                  element.addContent(attElem);
509              }
510          } catch (LazyInitializationException e) {
511              LOGGER.trace("adding attachments: {}", e.getMessage());
512          } finally {
513              pElement.addContent(element);
514          }
515      }
516  
517      /**
518       * Adds the {@code View}s of the {@code Content} to the given JDOM
519       * {@code Element}.
520       * 
521       * @param pElement
522       *            the JDOM {@code Element} to be filled with data.
523       */
524      private void addViews(final Element pElement) {
525          try {
526              for (View view : getAssociatedViews()) {
527                  Element viewElem = new Element(View.class.getSimpleName());
528                  viewElem.setAttribute("class", view.getClass().getSimpleName());
529                  viewElem.setAttribute("id", view.getId().toString());
530                  pElement.addContent(viewElem);
531              }
532          } catch (LazyInitializationException e) {
533              pElement.removeChildren(View.class.getSimpleName());
534          }
535      }
536  
537      /**
538       * Adds the {@code ContentLocalizationMap} of the {@code Content} to the
539       * given JDOM {@code Element}.
540       * 
541       * @param pElement
542       *            the JDOM {@code Element} to be filled with data.
543       */
544      private void addContentLocalizationMap(final Element pElement) {
545          try {
546              if (getLocalizationMap() != null) {
547                  Element element = new Element(getLocalizationMap().getClass()
548                          .getSimpleName());
549                  for (Map.Entry<Locale, Content> entry : getLocalizationMap()
550                          .entrySet()) {
551                      try {
552                          element.addContent(new Element("Content")
553                                  .setAttribute("id",
554                                          entry.getValue().getId().toString())
555                                  .setAttribute("locale",
556                                          entry.getKey().toString())
557                                  .setAttribute("name",
558                                          entry.getValue().getName())
559                                  .setAttribute(
560                                          "class",
561                                          entry.getValue().getClass()
562                                                  .getCanonicalName()));
563                      } catch (LazyInitializationException e) {
564                          continue;
565                      }
566                  }
567                  pElement.addContent(element);
568              }
569          } catch (LazyInitializationException e) {
570              pElement.removeChild(getLocalizationMap().getClass()
571                      .getSimpleName());
572          }
573      }
574  
575      /**
576       * is an {@code ILinkCorrectableElement} for the summary of the
577       * {@code AbstractBasicContent}.
578       * 
579       * @author Thomas Weber
580       */
581      private static final class SummaryCorrectable implements
582              ILinkCorrectableElement {
583  
584          /**
585           * the abstract basic content.
586           */
587          private final AbstractBasicContent content;
588  
589          /**
590           * creates a new {@code SummaryCorrectable} for the given
591           * {@code AbstractBasicContent}.
592           * 
593           * @param c
594           *            the content as a back reference
595           */
596          public SummaryCorrectable(final AbstractBasicContent c) {
597              super();
598              this.content = c;
599          }
600  
601          /**
602           * @return the element
603           * @see org.torweg.pulse.site.content.util.ILinkCorrectableElement#getElement()
604           */
605          public Element getElement() {
606              return this.content.getSummaryElement();
607          }
608  
609          /**
610           * @param e
611           *            the element to set
612           * @see org.torweg.pulse.site.content.util.ILinkCorrectableElement#setElement(org.jdom.Element)
613           */
614          public void setElement(final Element e) {
615              this.content.setSummary(e);
616          }
617  
618      }
619  }
620