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.site.content.admin;
19   
20   import java.util.ArrayList;
21   import java.util.List;
22   import java.util.Map;
23   import java.util.TreeMap;
24   
25   import net.sf.json.JSONArray;
26   import net.sf.json.JSONObject;
27   
28   import org.hibernate.LockOptions;
29   import org.hibernate.Session;
30   import org.hibernate.Transaction;
31   import org.jdom.Element;
32   import org.torweg.pulse.annotations.Action;
33   import org.torweg.pulse.annotations.Groups;
34   import org.torweg.pulse.annotations.Permission;
35   import org.torweg.pulse.annotations.RequireToken;
36   import org.torweg.pulse.bundle.Bundle;
37   import org.torweg.pulse.bundle.Controller;
38   import org.torweg.pulse.configuration.ConfigBean;
39   import org.torweg.pulse.configuration.DeprecatedConfigurable;
40   import org.torweg.pulse.invocation.lifecycle.Lifecycle;
41   import org.torweg.pulse.service.PulseException;
42   import org.torweg.pulse.service.event.JSONOutputEvent;
43   import org.torweg.pulse.service.event.XSLTOutputEvent;
44   import org.torweg.pulse.service.request.Command;
45   import org.torweg.pulse.service.request.ServiceRequest;
46   import org.torweg.pulse.site.ViewTypes;
47   import org.torweg.pulse.site.content.AbstractContentGroup;
48   import org.torweg.pulse.site.content.AbstractRegistryNode;
49   import org.torweg.pulse.site.content.Content;
50   import org.torweg.pulse.site.content.ContentFolderNode;
51   import org.torweg.pulse.site.content.ContentGroup;
52   import org.torweg.pulse.site.content.ContentLocalizationMap;
53   import org.torweg.pulse.site.content.ContentNode;
54   import org.torweg.pulse.site.content.ContentRegistry;
55   import org.torweg.pulse.site.content.IAdministerSitemap;
56   import org.torweg.pulse.site.content.RegistryBundleNode;
57   import org.torweg.pulse.site.content.RegistryLocaleNode;
58   import org.torweg.pulse.util.adminui.JSONCommunicationUtils;
59   import org.torweg.pulse.util.adminui.RegistryEditorResult;
60   import org.torweg.pulse.util.adminui.RightsCheckUtils;
61   import org.torweg.pulse.util.entity.Node;
62   
63   /**
64    * The main, abstract {@code ContentRegistryEditor} to derive the {@code
65    * ContentRegistryEditor}s for the {@code Bundle}s from.
66    * <p>
67    * This controller provides the functions for the usage of the content-registry
68    * as shown in the west-panel of the website-administration:
69    * <ul>
70    * <li>provides general browsing functions</li>
71    * <li>provides general editing functions</li>
72    * </ul>
73    * </p>
74    * 
75    * @author Daniel Dietz
76    * @version $Revision: 1914 $
77    */
78   public abstract class AbstractContentRegistryEditor extends Controller
79           implements DeprecatedConfigurable {
80   
81       /**
82        * the {@code ConfigBean} of this {@code Controller}.
83        */
84       private ContentRegistryEditorConfig config;
85   
86       /**
87        * Initialises the west-panel with the {@code ContentRegistryEditor} -tree.
88        * 
89        * @param bundle
90        *            the current {@code Bundle}
91        * @param request
92        *            the current {@code ServiceRequest}
93        * 
94        * @return the initialization {@code Result} for the {@code
95        *         ContentRegistryEditor}
96        */
97       @RequireToken
98       @Action(value = "contentRegistryTreeInit", generate = true)
99       @Permission("useContentRegistry")
100      @Groups(values = { "Admin-UI" })
101      public final RegistryEditorResult initEditor(final Bundle bundle,
102              final ServiceRequest request) {
103          RegistryEditorResult result = new RegistryEditorResult();
104          XSLTOutputEvent event = new XSLTOutputEvent(this.config.getAjaxXSL());
105          event.setStopEvent(true);
106          request.getEventManager().addEvent(event);
107          return result;
108      }
109  
110      /**
111       * TODO: add a clear description!
112       * 
113       * @param bundle
114       *            the {@code Bundle} we belong to
115       * @param request
116       *            the current {@code ServiceRequest}
117       * @return {@code null}
118       */
119      @RequireToken
120      @Action(value = "browseContentRegistryEdit", generate = true)
121      @Permission("useContentRegistry")
122      @Groups(values = { "Admin-UI" })
123      public final Object browseContentRegistryEdit(final Bundle bundle,
124              final ServiceRequest request) {
125  
126          // retrieve node-id from request
127          String nodeid = request.getCommand().getParameter("node")
128                  .getFirstValue();
129  
130          // setup
131          Session s = Lifecycle.getHibernateDataSource().createNewSession();
132          Transaction tx = s.beginTransaction();
133          JSONArray contentRegistryNodes = new JSONArray();
134  
135          try {
136  
137              if (nodeid.length() > 5 && nodeid.substring(0, 6).equals("source")) {
138  
139                  // process root
140                  contentRegistryNodes = processContentRegistryRoot(request, s,
141                          true);
142  
143              } else {
144  
145                  // load the registry-node which is being browsed
146                  AbstractRegistryNode registryNode = (AbstractRegistryNode) s
147                          .get(AbstractRegistryNode.class, Long.parseLong(nodeid));
148  
149                  List<? extends Node> nodeList = (List<? extends Node>) registryNode
150                          .getChildren();
151  
152                  Map<String, JSONObject> nodeMap = buildEditNodeMap(request,
153                          nodeList);
154  
155                  contentRegistryNodes.addAll(nodeMap.values());
156  
157              }
158  
159              tx.rollback();
160  
161          } catch (Exception e) {
162              tx.rollback();
163              throw new PulseException(getClass().getSimpleName()
164                      + ".browseContentRegistryEdit.failed: "
165                      + e.getLocalizedMessage(), e);
166          } finally {
167              s.close();
168          }
169  
170          // output
171          request.getEventManager().addEvent(
172                  new JSONOutputEvent(contentRegistryNodes));
173  
174          return null;
175  
176      }
177  
178      /**
179       * TODO: add a clear description!
180       * 
181       * @param bundle
182       *            the {@code Bundle} we belong to
183       * @param request
184       *            the current {@code ServiceRequest}
185       * @return {@code null}
186       */
187      @RequireToken
188      @Action(value = "browseContentRegistrySelect", generate = true)
189      @Permission("useContentRegistry")
190      @Groups(values = { "Admin-UI" })
191      public final Object browseContentRegistrySelect(final Bundle bundle,
192              final ServiceRequest request) {
193  
194          // retrieve node-id from request
195          String nodeid = request.getCommand().getParameter("node")
196                  .getFirstValue();
197  
198          // mode:
199          // if mode=null : contents will be made selectable
200          // if mode="folder" : folders will be made selectable
201          String mode = null;
202          if (request.getCommand().getParameter("mode") != null) {
203              mode = request.getCommand().getParameter("mode").getFirstValue();
204          }
205  
206          // setup
207          Session s = Lifecycle.getHibernateDataSource().createNewSession();
208          Transaction tx = s.beginTransaction();
209          JSONArray contentRegistryNodes = new JSONArray();
210  
211          try {
212  
213              if (nodeid.length() > 5 && nodeid.substring(0, 6).equals("source")) {
214  
215                  // process root
216                  contentRegistryNodes = processContentRegistryRoot(request, s,
217                          false);
218  
219              } else {
220  
221                  // load the registry-node which is being browsed
222                  AbstractRegistryNode registryNode = (AbstractRegistryNode) s
223                          .get(AbstractRegistryNode.class, Long.parseLong(nodeid));
224  
225                  List<? extends Node> nodeList = (List<? extends Node>) registryNode
226                          .getChildren();
227  
228                  // retrieve node-map : according to mode
229                  Map<String, JSONObject> nodeMap;
230                  if (mode != null && mode.equals("folder")) {
231                      nodeMap = buildSelectFolderMap(request, nodeList);
232                  } else {
233                      nodeMap = buildSelectNodeMap(request, nodeList);
234                  }
235                  contentRegistryNodes.addAll(nodeMap.values());
236  
237              }
238  
239              tx.rollback();
240  
241          } catch (Exception e) {
242              tx.rollback();
243              throw new PulseException(getClass().getSimpleName()
244                      + ".browseContentRegistrySelect.failed: "
245                      + e.getLocalizedMessage(), e);
246          } finally {
247              s.close();
248          }
249  
250          // output
251          request.getEventManager().addEvent(
252                  new JSONOutputEvent(contentRegistryNodes));
253  
254          return null;
255      }
256  
257      /**
258       * convenience.
259       * 
260       * @param request
261       *            the current {@code ServiceRequest}
262       * @param s
263       *            the current {@code Session}
264       * @param edit
265       *            {@code true} if called by browseEdit, {@code false} otherwise
266       * 
267       * @return {@code JSONArray} of contentRegistryNodes
268       */
269      private JSONArray processContentRegistryRoot(final ServiceRequest request,
270              final Session s, final boolean edit) {
271  
272          // load ContentRegistry.rootNodes (RegistryBundleNode)
273          ContentRegistry contentRegistry = (ContentRegistry) s.get(
274                  ContentRegistry.class, 1L);
275  
276          List<AbstractRegistryNode> nodeList = new ArrayList<AbstractRegistryNode>(
277                  contentRegistry.getRootNodes());
278  
279          Map<String, JSONObject> nodeMap = new TreeMap<String, JSONObject>();
280  
281          // given parameter bundle ->
282          // returns content-registry for requested bundle
283          // and the bundles the content-types of which contain the content-types
284          // of the requested bundle
285          Bundle bundle = null;
286          if (request.getCommand().getParameter("bundle") != null
287                  && !request.getCommand().getParameter("bundle").getFirstValue()
288                          .equals("")) {
289              bundle = Lifecycle.getBundle(request.getCommand().getParameter(
290                      "bundle").getFirstValue());
291          }
292  
293          // sort bundle-nodes
294          for (AbstractRegistryNode r : nodeList) {
295  
296              JSONObject nodeJSONObject = r.toJSON();
297              nodeJSONObject.put("draggable", false);
298              nodeJSONObject.put("allowDrop", false);
299  
300              if (edit) {
301                  // retrieve edit-URLs
302                  Map<String, String> editURLs = this.config.getEditURLs(r,
303                          request);
304  
305                  // add edit-URLs
306                  for (Map.Entry<String, String> e : editURLs.entrySet()) {
307                      nodeJSONObject.put(e.getKey() + "URL", e.getValue());
308                  }
309              }
310  
311              // bundle-check
312              if (bundle == null) {
313                  nodeMap.put(r.getBundle().getName(), nodeJSONObject);
314              } else {
315                  // check node-bundle vs bundle for matching content-types
316                  if (r.getBundle().getContentTypes().containsAll(
317                          bundle.getContentTypes())) {
318                      nodeMap.put(r.getBundle().getName(), nodeJSONObject);
319                  }
320  
321              }
322          }
323  
324          JSONArray rootNodes = new JSONArray();
325          rootNodes.addAll(nodeMap.values());
326          return rootNodes;
327      }
328  
329      /**
330       * convenience.
331       * 
332       * @param request
333       *            the current {@code ServiceRequest}
334       * @param nodeList
335       *            a list of nodes to build the node-map for
336       * 
337       * @return {@code Map&lt;String, JSONObject&gt;}
338       */
339      private Map<String, JSONObject> buildSelectNodeMap(
340              final ServiceRequest request, final List<? extends Node> nodeList) {
341  
342          Map<String, JSONObject> nodeMap = new TreeMap<String, JSONObject>();
343  
344          for (Node n : nodeList) {
345  
346              if (n.getClass().equals(ContentNode.class)
347                      && request.getCommand().getParameter("excludeGroups") != null
348                      && ((ContentNode) n).getContent() instanceof AbstractContentGroup) {
349                  continue;
350              }
351  
352              JSONObject nodeJSONObject = n.toJSON();
353  
354              if (hideOtherLocalesCheck(request, nodeJSONObject)) {
355                  continue;
356              }
357  
358              nodeJSONObject = applyContentRegistrySettingsToSelectNodeOrFolderMap(
359                      request, n, nodeJSONObject);
360  
361              // sort nodes
362              nodeMap.put(n.toJSON().get("type") + "." + n.getClass() + "."
363                      + ((AbstractRegistryNode) n).getName() + "." + n.getId(),
364                      nodeJSONObject);
365          }
366  
367          return nodeMap;
368      }
369  
370      /**
371       * convenience.
372       * 
373       * @param request
374       *            the current {@code ServiceRequest}
375       * @param nodeList
376       *            a list of nodes to build the node-map for
377       * 
378       * @return {@code Map&lt;String, JSONObject&gt;}
379       */
380      private Map<String, JSONObject> buildSelectFolderMap(
381              final ServiceRequest request, final List<? extends Node> nodeList) {
382  
383          Map<String, JSONObject> nodeMap = new TreeMap<String, JSONObject>();
384  
385          for (Node n : nodeList) {
386  
387              JSONObject nodeJSONObject = n.toJSON();
388  
389              if (hideOtherLocalesCheck(request, nodeJSONObject)) {
390                  continue;
391              }
392  
393              nodeJSONObject = applyContentRegistrySettingsToSelectNodeOrFolderMap(
394                      request, n, nodeJSONObject);
395  
396              if (n instanceof ContentNode) {
397                  nodeJSONObject.remove("mode");
398                  nodeJSONObject.put("disabled", true);
399              }
400  
401              // sort nodes
402              nodeMap.put(n.toJSON().get("type") + "." + n.getClass() + "."
403                      + ((AbstractRegistryNode) n).getName() + "." + n.getId(),
404                      nodeJSONObject);
405  
406          }
407  
408          return nodeMap;
409      }
410  
411      /**
412       * Applies the {@code ContentRegistry}-specific settings (e.g.
413       * mode='select') to the node.
414       * 
415       * @param request
416       *            the current {@code ServiceRequest}
417       * @param node
418       *            the current {@code Node}
419       * @param nodeJSONObject
420       *            the node-JSON to be modified
421       * 
422       * @return the modified node-JSON
423       */
424      protected JSONObject applyContentRegistrySettingsToSelectNodeOrFolderMap(
425              final ServiceRequest request, final Node node,
426              final JSONObject nodeJSONObject) {
427  
428          nodeJSONObject.put("draggable", false);
429  
430          // show select mode of tree-node in admin ui
431          if (node.getClass().equals(ContentNode.class)
432                  || node.getClass().equals(ContentFolderNode.class)) {
433              // select-mode-locale-check
434              selectModeCheck(request, nodeJSONObject, node);
435          }
436  
437          ViewTypes viewTypes = ((AbstractRegistryNode) node).getBundle()
438                  .getViewTypes((AbstractRegistryNode) node);
439  
440          if (viewTypes != null && viewTypes.getViewByType("expand") != null) {
441              Command c = viewTypes.getViewByType("expand").getCommand(
442                      request.getCommand().createCopy(false).clearParameters())
443                      .addHttpParameter("id", node.getId().toString());
444              if (request.getCommand().getParameter("mode") != null) {
445                  c.addHttpParameter("mode", request.getCommand().getParameter(
446                          "mode").getFirstValue());
447              }
448              nodeJSONObject.put("expandURL", c.toCommandURL(request));
449          }
450  
451          return nodeJSONObject;
452      }
453  
454      /**
455       * convenience.
456       * 
457       * @param request
458       *            the current {@code ServiceRequest}
459       * @param nodeJSONObject
460       *            the {@code JSONObject}-representation of the current {@code
461       *            Node} to set the select-mode for
462       * @param node
463       *            the current {@code Node}
464       */
465      private void selectModeCheck(final ServiceRequest request,
466              final JSONObject nodeJSONObject, final Node node) {
467          if (request.getCommand().getParameter("allowSelectContentOnly") != null
468                  && request.getCommand().getParameter("allowSelectContentOnly")
469                          .getFirstValue().equals("true")
470                  && !node.getClass().equals(ContentNode.class)) {
471              return;
472          }
473          if (request.getCommand().getParameter("locale") != null) {
474              // check locale
475              if (nodeJSONObject.getString("locale") != null
476                      && nodeJSONObject.getString("locale").equalsIgnoreCase(
477                              request.getCommand().getParameter("locale")
478                                      .getFirstValue())) {
479                  nodeJSONObject.put("mode", "select");
480              }
481          } else {
482              nodeJSONObject.put("mode", "select");
483          }
484      }
485  
486      /**
487       * convenience.
488       * 
489       * @param request
490       *            the current {@code ServiceRequest}
491       * @param nodeJSONObject
492       *            the node to check as {@code JSONObject}
493       * @return {@code true}, if (request.locale != nodeJSONObject.locale) &&
494       *         (request.hideOtherLocales == true)
495       */
496      private boolean hideOtherLocalesCheck(final ServiceRequest request,
497              final JSONObject nodeJSONObject) {
498  
499          // given parameter locale -> set mode select
500          // only for ContentNode with matching locale
501          if (request.getCommand().getParameter("locale") != null) {
502  
503              // given parameter hideOtherLocales -> returns
504              // only nodes with matching locale
505              boolean hideOtherLocales = false;
506              if (request.getCommand().getParameter("hideOtherLocales") != null) {
507                  hideOtherLocales = Boolean.parseBoolean(request.getCommand()
508                          .getParameter("hideOtherLocales").getFirstValue());
509              }
510  
511              if (hideOtherLocales
512                      && nodeJSONObject.getString("locale") != null
513                      && !(nodeJSONObject.getString("locale")
514                              .equalsIgnoreCase(request.getCommand()
515                                      .getParameter("locale").getFirstValue()))) {
516                  return true;
517              }
518  
519          }
520  
521          return false;
522      }
523  
524      /**
525       * convenience.
526       * 
527       * @param request
528       *            the current {@code ServiceRequest}
529       * @param nodeList
530       *            a list of nodes to build the node-map for
531       * 
532       * @return {@code Map&lt;String, JSONObject&gt;}
533       */
534      private Map<String, JSONObject> buildEditNodeMap(
535              final ServiceRequest request, final List<? extends Node> nodeList) {
536  
537          // given parameter locale -> generate edit urls
538          // only for ContentNode with matching locale
539          String localeString = null;
540          if (request.getCommand().getParameter("locale") != null) {
541              localeString = request.getCommand().getParameter("locale")
542                      .getFirstValue();
543          }
544  
545          Map<String, JSONObject> nodeMap = new TreeMap<String, JSONObject>();
546  
547          for (Node n : nodeList) {
548              if (n != null) {
549                  JSONObject nodeJSONObject = n.toJSON();
550  
551                  // locale-check
552                  if (localeString == null) {
553  
554                      // applies contentregistry-specific node-settings
555                      nodeJSONObject = applyContentRegistrySettingsToEditNodeMap(
556                              request, n, nodeJSONObject);
557  
558                      // sort nodes
559                      nodeMap.put(n.toJSON().get("type") + "." + n.getClass()
560                              + "." + ((AbstractRegistryNode) n).getName() + "."
561                              + n.getId(), nodeJSONObject);
562  
563                  } else {
564                      // check locale
565                      if (nodeJSONObject.getString("locale") != null
566                              && nodeJSONObject.getString("locale")
567                                      .equalsIgnoreCase(localeString)) {
568  
569                          // applies contentregistry-specific node-settings
570                          nodeJSONObject = applyContentRegistrySettingsToEditNodeMap(
571                                  request, n, nodeJSONObject);
572  
573                          // sort nodes
574                          nodeMap.put(n.toJSON().get("type") + "." + n.getClass()
575                                  + "." + ((AbstractRegistryNode) n).getName()
576                                  + "." + n.getId(), nodeJSONObject);
577  
578                      }
579                  }
580              }
581          }
582  
583          return nodeMap;
584      }
585  
586      /**
587       * Applies the {@code ContentRegistry}-edit-specific settings (e.g. URLs) to
588       * the node.
589       * 
590       * @param request
591       *            the current {@code ServiceRequest}
592       * @param n
593       *            the current {@code Node}
594       * @param nodeJSONObject
595       *            the node-JSON to be modified
596       * 
597       * @return the modified node
598       */
599      protected JSONObject applyContentRegistrySettingsToEditNodeMap(
600              final ServiceRequest request, final Node n,
601              final JSONObject nodeJSONObject) {
602  
603          if (n.getClass().equals(RegistryLocaleNode.class)) {
604              nodeJSONObject.put("draggable", false);
605          }
606  
607          // remove attribute leaf from all nodes that are not
608          // content-nodes
609          if (!n.getClass().equals(ContentNode.class)) {
610              nodeJSONObject.remove("leaf");
611          }
612  
613          // retrieve edit-URLs
614          Map<String, String> editURLs = this.config.getEditURLs(
615                  (AbstractRegistryNode) n, request);
616  
617          // add edit-URLs
618          for (Map.Entry<String, String> e : editURLs.entrySet()) {
619              nodeJSONObject.put(e.getKey() + "URL", e.getValue());
620          }
621  
622          return nodeJSONObject;
623  
624      }
625  
626      /**
627       * moves a {@code ? extends RegistryLocaleNode}. By default a {@code
628       * RegistryLocaleNode} itself is not movable.
629       * 
630       * @param bundle
631       *            the current {@code Bundle}
632       * @param request
633       *            the current {@code ServiceRequest}
634       */
635      @RequireToken
636      @Action(value = "moveRegistryLocaleNode", generate = true)
637      @Permission("useContentRegistry")
638      @Groups(values = { "Admin-UI" })
639      public final void moveRegistryLocaleNode(final Bundle bundle,
640              final ServiceRequest request) {
641  
642          // setup
643          Session s = Lifecycle.getHibernateDataSource().createNewSession();
644          Transaction tx = s.beginTransaction();
645  
646          int index = -1;
647          // error : cannotMoveNodeToRootLevel (if occurs)
648          // error : userHasNoEditRightsForLocale (if occurs)
649          JSONObject error = null;
650          boolean refreshSitemap = false;
651  
652          try {
653  
654              // load node that's to be moved
655              RegistryLocaleNode moveNode = (RegistryLocaleNode) s.get(
656                      RegistryLocaleNode.class, Long.parseLong(request
657                              .getCommand().getParameter("movenodeid")
658                              .getFirstValue()), LockOptions.UPGRADE);
659  
660              // user check
661              error = RightsCheckUtils.checkUserAgainstLocale(error, bundle,
662                      request, moveNode.getLocale());
663  
664              RegistryLocaleNode newParentNode = null;
665  
666              if (error == null) {
667  
668                  // determine new parent
669                  newParentNode = determineNewParent(request, s);
670  
671                  // general ContentRegistry-move-checks
672                  // also checks for (new parent == null)
673                  error = performMoveChecks(newParentNode, moveNode);
674              }
675  
676              // perform move
677              if (error == null) {
678  
679                  // determine index
680                  if (!request.getCommand().getParameter("insertpoint")
681                          .getFirstValue().equals("append")) {
682                      // determine add-(reference-node.)position-index
683                      index = newParentNode.getChildIndex((RegistryLocaleNode) s
684                              .get(RegistryLocaleNode.class, Long
685                                      .parseLong(request.getCommand()
686                                              .getParameter("targetnodeid")
687                                              .getFirstValue())));
688                      if (request.getCommand().getParameter("insertpoint")
689                              .getFirstValue().equals("below")) {
690                          index += 1;
691                      }
692                  }
693  
694                  // load old parent
695                  RegistryLocaleNode oldParentNode = (RegistryLocaleNode) moveNode
696                          .getParent();
697  
698                  // move
699                  oldParentNode.removeChild(moveNode);
700                  if (index == -1) {
701                      newParentNode.addChild(moveNode);
702                  } else {
703                      try {
704                          newParentNode.addChild(index, moveNode);
705                      } catch (IndexOutOfBoundsException e) {
706                          newParentNode.addChild(moveNode);
707                      }
708                  }
709  
710                  // IAdministerSitemap
711                  refreshSitemap = processIAdministerSitemapOnMove(s, moveNode,
712                          newParentNode, oldParentNode);
713  
714                  // persist
715                  s.saveOrUpdate(oldParentNode);
716                  s.saveOrUpdate(newParentNode);
717  
718                  tx.commit();
719              }
720  
721          } catch (Exception e) {
722              if (tx != null) {
723                  tx.rollback();
724              }
725              throw new PulseException(getClass().getSimpleName()
726                      + ".moveRegistryLocaleNode.failed: "
727                      + e.getLocalizedMessage(), e);
728          } finally {
729              s.close();
730          }
731  
732          // output
733          if (error == null) {
734              JSONObject responseObject = new JSONObject();
735              responseObject.put("refreshSitemap", refreshSitemap);
736              JSONCommunicationUtils.jsonSuccessMessage(request, responseObject);
737          } else {
738              JSONCommunicationUtils.jsonErrorMessage(request, error);
739          }
740      }
741  
742      /**
743       * Calls sub-routines if any {@code IAdminsterSitemap}- {@code Content} is
744       * affected by the move.
745       * 
746       * @param s
747       *            a hibernate-<sup>TM</sup>-{@code Session}
748       * @param moveNode
749       *            the node to be moved
750       * @param newParentNode
751       *            the new parent of the moveNode
752       * @param oldParentNode
753       *            the old parent of the moveNode
754       * 
755       * @return {@code true} if the sitemap in website-administration-ui is to be
756       *         refreshed, {@code false} otherwise
757       */
758      private boolean processIAdministerSitemapOnMove(final Session s,
759              final RegistryLocaleNode moveNode,
760              final RegistryLocaleNode newParentNode,
761              final RegistryLocaleNode oldParentNode) {
762          boolean refreshSitemap = false;
763          if (moveNode instanceof ContentNode) {
764              // process IAdministerSitemap
765              if (oldParentNode instanceof ContentNode
766                      && ((ContentNode) oldParentNode).getContent() instanceof IAdministerSitemap) {
767                  ((IAdministerSitemap) ((ContentNode) oldParentNode)
768                          .getContent()).onChildContentRemove(
769                          ((ContentNode) moveNode).getContent(), s);
770                  refreshSitemap = true;
771              }
772  
773              // process IAdministerSitemap
774              if (newParentNode instanceof ContentNode
775                      && ((ContentNode) newParentNode).getContent() instanceof IAdministerSitemap) {
776                  ((IAdministerSitemap) ((ContentNode) newParentNode)
777                          .getContent()).onChildContentAdd(
778                          ((ContentNode) moveNode).getContent(), s);
779                  refreshSitemap = true;
780              }
781          }
782          return refreshSitemap;
783      }
784  
785      /**
786       * convenience (for move).
787       * 
788       * @param request
789       *            the current {@code ServiceRequest}
790       * @param s
791       *            the current {@code Session}
792       * 
793       * @return a {@code RegistryLocaleNode} which could be possible new parent
794       *         during move operation
795       */
796      private RegistryLocaleNode determineNewParent(final ServiceRequest request,
797              final Session s) {
798          try {
799              if (request.getCommand().getParameter("insertpoint")
800                      .getFirstValue().equals("append")) {
801                  // (reference-node = target-node)
802                  // load the potentially new parent-node
803                  return (RegistryLocaleNode) s.get(RegistryLocaleNode.class,
804                          Long.parseLong(request.getCommand().getParameter(
805                                  "targetnodeid").getFirstValue()),
806                          LockOptions.UPGRADE);
807              } else {
808                  // (reference-node = target-node.parent)
809                  // load the potentially new parent-node
810                  return (RegistryLocaleNode) ((RegistryLocaleNode) s.get(
811                          RegistryLocaleNode.class, Long.parseLong(request
812                                  .getCommand().getParameter("targetnodeid")
813                                  .getFirstValue()), LockOptions.UPGRADE))
814                          .getParent();
815              }
816          } catch (Exception e) {
817              return null;
818          }
819      }
820  
821      /**
822       * convenience.
823       * 
824       * @param newParentNode
825       *            the potential new parent {@code RegistryLocaleNode}
826       * @param moveNode
827       *            the {@code RegistryLocaleNode} which is being moved
828       * 
829       * @return a {@code JSONObject} on errors, null otherwise
830       */
831      private JSONObject performMoveChecks(
832              final RegistryLocaleNode newParentNode,
833              final RegistryLocaleNode moveNode) {
834          if (newParentNode == null) {
835              JSONObject error = new JSONObject();
836              error.put("e", "cannotMoveToUndefinedLevel");
837              return error;
838          }
839          if (newParentNode.getClass().equals(RegistryBundleNode.class)) {
840              JSONObject error = new JSONObject();
841              error.put("e", "cannotMoveToBundleLevel");
842              return error;
843          }
844          if (!newParentNode.getBundle().getName().equals(
845                  moveNode.getBundle().getName())) {
846              JSONObject error = new JSONObject();
847              error.put("e", "cannotMoveToDifferentBundle");
848              return error;
849          }
850          if (!newParentNode.getLocale().equals(moveNode.getLocale())) {
851              JSONObject error = new JSONObject();
852              error.put("e", "cannotMoveToDifferentLocale");
853              return error;
854          }
855          if (newParentNode.getClass().equals(RegistryLocaleNode.class)
856                  && !moveNode.getClass().equals(ContentFolderNode.class)) {
857              JSONObject error = new JSONObject();
858              error.put("e", "canOnlyMoveFolderToLocaleLevel");
859              return error;
860          }
861  
862          return canMoveContentOnlyToFolderCheck(newParentNode, moveNode);
863      }
864  
865      /**
866       * The check for moving contents.
867       * 
868       * @param newParentNode
869       *            the new parent
870       * @param moveNode
871       *            the node to be moved
872       * 
873       * @return an error-{@code JSONObject} if occurs, {@code null} otherwise
874       */
875      protected JSONObject canMoveContentOnlyToFolderCheck(
876              final RegistryLocaleNode newParentNode,
877              final RegistryLocaleNode moveNode) {
878          if (moveNode.getClass().equals(ContentNode.class)
879                  && !newParentNode.getClass().equals(ContentFolderNode.class)) {
880              JSONObject error = new JSONObject();
881              error.put("e", "canMoveContentOnlyToFolder");
882              return error;
883          }
884          return null;
885      }
886  
887      /**
888       * returns an array of registry-node-ids which are the ids of the
889       * registry-nodes from content-registry-root down to the content-node that
890       * holds the content the id of which has been passed with the request.
891       * 
892       * @param bundle
893       *            the {@code Bundle} we belong to
894       * @param request
895       *            the current {@code ServiceRequest}
896       */
897      @RequireToken
898      @Action(value = "getContentRegistryIdPathContent", generate = true)
899      @Permission("useContentRegistry")
900      @Groups(values = { "Admin-UI" })
901      public final void getContentRegistryIdPath(final Bundle bundle,
902              final ServiceRequest request) {
903  
904          // retrieve content-id from request
905          Long id = Long.parseLong(request.getCommand().getParameter("id")
906                  .getFirstValue());
907  
908          // setup
909          Session s = Lifecycle.getHibernateDataSource().createNewSession();
910          Transaction tx = s.beginTransaction();
911          JSONArray contentRegistryNodeIds = new JSONArray();
912  
913          try {
914  
915              // load the content-node that holds the content
916              ContentNode node = (ContentNode) s.createQuery(
917                      "from ContentNode n where n.content.id=?").setLong(0, id)
918                      .uniqueResult();
919  
920              // retrieve ids
921              Node n = node;
922              contentRegistryNodeIds.add(n.getId());
923              while (n.getParent() != null) {
924                  n = n.getParent();
925                  contentRegistryNodeIds.add(0, n.getId());
926              }
927  
928              tx.rollback();
929  
930          } catch (Exception e) {
931              tx.rollback();
932              throw new PulseException(getClass().getSimpleName()
933                      + ".getContentRegistryIdPathContent.failed: "
934                      + e.getLocalizedMessage(), e);
935          } finally {
936              s.close();
937          }
938  
939          JSONCommunicationUtils.jsonSuccessMessage(request, "ids",
940                  contentRegistryNodeIds);
941      }
942  
943      /**
944       * deletes a {@code ContentNode}.
945       * 
946       * @param bundle
947       *            the current {@code Bundle}
948       * @param request
949       *            the current {@code ServiceRequest}
950       */
951      @RequireToken
952      @Action(value = "deleteAbstractContentNode", generate = true)
953      @Permission("useContentRegistry")
954      @Groups(values = { "Admin-UI" })
955      public final void deleteAbstractContentNode(final Bundle bundle,
956              final ServiceRequest request) {
957  
958          // retrieve id from request
959          Long id = Long.parseLong(request.getCommand().getParameter("id")
960                  .getFirstValue());
961  
962          // setup
963          Session s = Lifecycle.getHibernateDataSource().createNewSession();
964          Transaction tx = s.beginTransaction();
965          // error : userHasNoEditRightsForLocale (if occurs)
966          JSONObject error = null;
967          boolean refreshSitemap = false;
968  
969          try {
970  
971              // load the content-node that is to be deleted
972              ContentNode deleteNode = (ContentNode) s.get(ContentNode.class, id,
973                      LockOptions.UPGRADE);
974  
975              // user check
976              error = RightsCheckUtils.checkUserAgainstLocale(bundle, request,
977                      deleteNode.getLocale());
978  
979              // perform deletion
980              if (error == null) {
981  
982                  // retrieve content that is to be deleted
983                  Content deleteContent = deleteNode.getContent();
984  
985                  // retrieve contents' localization-map
986                  ContentLocalizationMap locMap = deleteContent
987                          .getLocalizationMap();
988  
989                  // remove content from localization map
990                  locMap.remove(deleteContent.getLocale());
991  
992                  // load deleteNodes' parent
993                  Node parent = deleteNode.getParent();
994  
995                  // delete
996                  parent.removeChild(deleteNode);
997  
998                  // process IAdministerSitemap
999                  if (parent instanceof ContentNode
1000                         && ((ContentNode) parent).getContent() instanceof IAdministerSitemap) {
1001                     ((IAdministerSitemap) ((ContentNode) parent).getContent())
1002                             .onChildContentRemove(deleteContent, s);
1003                     refreshSitemap = true;
1004                 }
1005 
1006                 s.delete(deleteNode);
1007                 s.delete(deleteContent);
1008                 if (locMap.isEmpty()) {
1009                     s.delete(locMap);
1010                 } else {
1011                     s.saveOrUpdate(locMap);
1012                 }
1013 
1014                 // persist parent
1015                 s.saveOrUpdate(parent);
1016 
1017                 tx.commit();
1018             }
1019 
1020         } catch (Exception e) {
1021             tx.rollback();
1022             throw new PulseException(getClass().getSimpleName()
1023                     + ".deleteAbstractContentNode.failed: "
1024                     + e.getLocalizedMessage(), e);
1025         } finally {
1026             s.close();
1027         }
1028 
1029         // output
1030         if (error == null) {
1031             JSONObject responseObject = new JSONObject();
1032             responseObject.put("refreshSitemap", refreshSitemap);
1033             JSONCommunicationUtils.jsonSuccessMessage(request, responseObject);
1034         } else {
1035             JSONCommunicationUtils.jsonErrorMessage(request, error);
1036         }
1037     }
1038 
1039     /**
1040      * renames a {@code RegistryLocaleNode}.
1041      * 
1042      * @param bundle
1043      *            the current {@code Bundle}
1044      * @param request
1045      *            the current {@code ServiceRequest}
1046      */
1047     @RequireToken
1048     @Action(value = "renameRegistryLocaleNode", generate = true)
1049     @Permission("useContentRegistry")
1050     @Groups(values = { "Admin-UI" })
1051     public final void renameRegistryLocaleNode(final Bundle bundle,
1052             final ServiceRequest request) {
1053 
1054         // retrieve id from request
1055         Long id = Long.parseLong(request.getCommand().getParameter("id")
1056                 .getFirstValue());
1057 
1058         // setup
1059         Session s = Lifecycle.getHibernateDataSource().createNewSession();
1060         Transaction tx = s.beginTransaction();
1061         // error : userHasNoEditRightsForLocale (if occurs)
1062         JSONObject error = null;
1063 
1064         try {
1065 
1066             // load the node that is to be renamed
1067             RegistryLocaleNode renameNode = (RegistryLocaleNode) s.get(
1068                     RegistryLocaleNode.class, id);
1069 
1070             // user check
1071             error = RightsCheckUtils.checkUserAgainstLocale(bundle, request,
1072                     renameNode.getLocale());
1073 
1074             // perform rename
1075             if (error == null
1076                     && request.getCommand().getParameter("name") != null
1077                     && request.getCommand().getParameter("name")
1078                             .getFirstValue().trim().length() != 0
1079                     && !(request.getCommand().getParameter("name")
1080                             .getFirstValue().equals(renameNode.getName()))) {
1081 
1082                 // set new name
1083                 renameNode.setName(request.getCommand().getParameter("name")
1084                         .getFirstValue().trim());
1085 
1086                 // persist
1087                 s.saveOrUpdate(renameNode);
1088 
1089                 tx.commit();
1090 
1091             }
1092 
1093         } catch (Exception e) {
1094             tx.rollback();
1095             throw new PulseException(getClass().getSimpleName()
1096                     + ".renameRegistryLocaleNode.failed: "
1097                     + e.getLocalizedMessage(), e);
1098         } finally {
1099             s.close();
1100         }
1101 
1102         // output
1103         if (error == null) {
1104             JSONCommunicationUtils.jsonSuccessMessage(request);
1105         } else {
1106             JSONCommunicationUtils.jsonErrorMessage(request, error);
1107         }
1108     }
1109 
1110     /**
1111      * deletes a {@code RegistryLocale} (if empty).
1112      * <p>
1113      * Used for deletion of folders within website-administration of {@code
1114      * ContentRegistry}.
1115      * </p>
1116      * 
1117      * @param bundle
1118      *            the current {@code Bundle}
1119      * @param request
1120      *            the current {@code ServiceRequest}
1121      */
1122     @RequireToken
1123     @Action(value = "deleteRegistryLocaleNode", generate = true)
1124     @Permission("useContentRegistry")
1125     @Groups(values = { "Admin-UI" })
1126     public final void deleteRegistryLocaleNode(final Bundle bundle,
1127             final ServiceRequest request) {
1128 
1129         // retrieve id from request
1130         Long id = Long.parseLong(request.getCommand().getParameter("id")
1131                 .getFirstValue());
1132 
1133         // setup
1134         Session s = Lifecycle.getHibernateDataSource().createNewSession();
1135         Transaction tx = s.beginTransaction();
1136         // error : userHasNoEditRightsForLocale (if occurs)
1137         // error : folderIsNotEmpty (if occurs)
1138         JSONObject error = null;
1139 
1140         try {
1141 
1142             // load the node that is to be deleted as RegistryLocaleNode
1143             RegistryLocaleNode deleteNode = (RegistryLocaleNode) s.get(
1144                     RegistryLocaleNode.class, id, LockOptions.UPGRADE);
1145 
1146             // user check
1147             error = RightsCheckUtils.checkUserAgainstLocale(bundle, request,
1148                     deleteNode.getLocale());
1149 
1150             if (deleteNode.getChildren().size() > 0) {
1151                 error = new JSONObject();
1152                 error.put("e", "folderIsNotEmpty");
1153             }
1154 
1155             // perform deletion
1156             if (error == null) {
1157 
1158                 // load deleteNodes' parent
1159                 Node parent = deleteNode.getParent();
1160 
1161                 // delete
1162                 parent.removeChild(deleteNode);
1163                 s.delete(deleteNode);
1164 
1165                 // persist parent
1166                 s.saveOrUpdate(parent);
1167 
1168                 tx.commit();
1169             }
1170 
1171         } catch (Exception e) {
1172             tx.rollback();
1173             throw new PulseException(getClass().getSimpleName()
1174                     + ".deleteRegistryLocaleNode.failed: "
1175                     + e.getLocalizedMessage(), e);
1176         } finally {
1177             s.close();
1178         }
1179 
1180         // output
1181         if (error == null) {
1182             JSONCommunicationUtils.jsonSuccessMessage(request);
1183         } else {
1184             JSONCommunicationUtils.jsonErrorMessage(request, error);
1185         }
1186     }
1187 
1188     /**
1189      * creates and saves a new {@code ContentFolderNode}.
1190      * 
1191      * @param bundle
1192      *            the current {@code Bundle}
1193      * @param request
1194      *            the current {@code ServiceRequest}
1195      */
1196     @RequireToken
1197     @Action(value = "createContentFolderNode", generate = true)
1198     @Permission("useContentRegistry")
1199     @Groups(values = { "Admin-UI" })
1200     public final void createContentFolderNode(final Bundle bundle,
1201             final ServiceRequest request) {
1202 
1203         // retrieve parent-id/name from request
1204         Long id = Long.parseLong(request.getCommand().getParameter("id")
1205                 .getFirstValue());
1206         String name = request.getCommand().getParameter("name").getFirstValue();
1207 
1208         // setup
1209         Session s = Lifecycle.getHibernateDataSource().createNewSession();
1210         Transaction tx = s.beginTransaction();
1211         ContentFolderNode newFolder = null;
1212         // error : userHasNoEditRightsForLocale (if occurs)
1213         JSONObject error = null;
1214 
1215         try {
1216 
1217             // load the parent-content-node that has requested a save as
1218             // RegistryLocaleNode
1219             RegistryLocaleNode parent = (RegistryLocaleNode) s.get(
1220                     RegistryLocaleNode.class, id, LockOptions.UPGRADE);
1221 
1222             // user check
1223             error = RightsCheckUtils.checkUserAgainstLocale(bundle, request,
1224                     parent.getLocale());
1225 
1226             if (error == null) {
1227 
1228                 // create new folder
1229                 newFolder = new ContentFolderNode(name, parent.getBundle());
1230                 newFolder.setLocale(parent.getLocale());
1231 
1232                 // add new folder to parent
1233                 parent.addChild(newFolder);
1234 
1235                 // persist
1236                 s.saveOrUpdate(parent);
1237 
1238                 tx.commit();
1239             }
1240 
1241         } catch (Exception e) {
1242             tx.rollback();
1243             throw new PulseException(getClass().getSimpleName()
1244                     + ".createContentFolderNode.failed: "
1245                     + e.getLocalizedMessage(), e);
1246         } finally {
1247             s.close();
1248         }
1249 
1250         // output
1251         if (error == null) {
1252             JSONCommunicationUtils.jsonSuccessMessage(request, "id", newFolder
1253                     .getId().toString());
1254         } else {
1255             JSONCommunicationUtils.jsonErrorMessage(request, error);
1256         }
1257     }
1258 
1259     /**
1260      * creates and saves a new {@code ContentGroup}.
1261      * 
1262      * @param bundle
1263      *            the current {@code Bundle}
1264      * @param request
1265      *            the current {@code ServiceRequest}
1266      */
1267     @RequireToken
1268     @Action(value = "createContentGroup")
1269     @Permission("useContentRegistry")
1270     @Groups(values = { "CoreAdministrator" })
1271     public final void createContentGroup(final Bundle bundle,
1272             final ServiceRequest request) {
1273 
1274         // retrieve parent-id/name from request
1275         Long id = Long.parseLong(request.getCommand().getParameter("id")
1276                 .getFirstValue());
1277         String name = request.getCommand().getParameter("name").getFirstValue();
1278 
1279         // setup
1280         Session s = Lifecycle.getHibernateDataSource().createNewSession();
1281         Transaction tx = s.beginTransaction();
1282         ContentNode newContentNode = null;
1283         // error : userHasNoEditRightsForLocale (if occurs)
1284         JSONObject error = null;
1285 
1286         try {
1287 
1288             // load the parent-content-node that has requested a save as
1289             // RegistryLocaleNode
1290             RegistryLocaleNode parent = (RegistryLocaleNode) s.get(
1291                     RegistryLocaleNode.class, id, LockOptions.UPGRADE);
1292 
1293             // user check
1294             error = RightsCheckUtils.checkUserAgainstLocale(bundle, request,
1295                     parent.getLocale());
1296 
1297             if (error == null) {
1298 
1299                 // create new content-group
1300                 ContentGroup newContent = new ContentGroup(parent.getLocale(),
1301                         parent.getBundle());
1302 
1303                 // set name
1304                 newContent.setName(name);
1305 
1306                 // set (default-)suffix
1307                 newContent.setSuffix(name);
1308 
1309                 // set empty summary
1310                 newContent.setSummary(new Element("body"));
1311 
1312                 // set empty description
1313                 newContent.setDescription(new Element("body"));
1314 
1315                 // create & add content-localization-map
1316                 ContentLocalizationMap clMap = new ContentLocalizationMap(
1317                         parent.getLocale(), newContent);
1318                 newContent.setLocalizationMap(clMap);
1319 
1320                 // set creator
1321                 newContent.setCreator(request.getUser());
1322 
1323                 // save
1324                 s.saveOrUpdate(newContent);
1325 
1326                 // create content-node
1327                 newContentNode = new ContentNode(newContent, parent.getBundle());
1328                 newContentNode.setLocale(parent.getLocale());
1329 
1330                 // add new content-node to parent
1331                 parent.addChild(newContentNode);
1332 
1333                 // persist
1334                 s.saveOrUpdate(parent);
1335 
1336                 tx.commit();
1337 
1338                 // TODO : check if this necessary
1339                 AbstractBasicContentEditor
1340                         .initHibernateSearchFix(s, newContent);
1341             }
1342 
1343         } catch (Exception e) {
1344             tx.rollback();
1345             throw new PulseException(getClass().getSimpleName()
1346                     + ".createContentGroup.failed: " + e.getLocalizedMessage(),
1347                     e);
1348         } finally {
1349             s.close();
1350         }
1351 
1352         // output
1353         if (error == null) {
1354             JSONCommunicationUtils.jsonSuccessMessage(request, "id",
1355                     newContentNode.getId().toString());
1356         } else {
1357             JSONCommunicationUtils.jsonErrorMessage(request, error);
1358         }
1359     }
1360 
1361     /**
1362      * Returns the {@code ContentRegistryEditorConfig}.
1363      * 
1364      * @return the configuration.
1365      */
1366     protected final ContentRegistryEditorConfig getConfig() {
1367         return this.config;
1368     }
1369 
1370     /**
1371      * initializes the {@code ContentRegistryEditor}.
1372      * 
1373      * @param c
1374      *            the {@code ConfigBean} of this {@code Controller}
1375      * 
1376      */
1377     public final void init(final ConfigBean c) {
1378         this.config = (ContentRegistryEditorConfig) c;
1379     }
1380 
1381     /**
1382      * Starts the "create-editor" for the current {@code Bundle}.
1383      * 
1384      * @param bundle
1385      *            the current {@code Bundle}
1386      * @param request
1387      *            the current {@code ServiceRequest}
1388      * 
1389      * @return an AJAX-result
1390      */
1391     public abstract Object create(Bundle bundle, ServiceRequest request);
1392 
1393 }
1394