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.component.core.site.map;
19   
20   import java.net.URI;
21   import java.net.URISyntaxException;
22   import java.util.List;
23   
24   import net.sf.json.JSONArray;
25   import net.sf.json.JSONObject;
26   
27   import org.hibernate.LockOptions;
28   import org.hibernate.Session;
29   import org.hibernate.Transaction;
30   import org.hibernate.search.FullTextSession;
31   import org.slf4j.Logger;
32   import org.slf4j.LoggerFactory;
33   import org.torweg.pulse.accesscontrol.Role;
34   import org.torweg.pulse.annotations.Action;
35   import org.torweg.pulse.annotations.Groups;
36   import org.torweg.pulse.annotations.Permission;
37   import org.torweg.pulse.annotations.RequireToken;
38   import org.torweg.pulse.bundle.Bundle;
39   import org.torweg.pulse.bundle.Controller;
40   import org.torweg.pulse.configuration.ConfigBean;
41   import org.torweg.pulse.configuration.DeprecatedConfigurable;
42   import org.torweg.pulse.invocation.lifecycle.Lifecycle;
43   import org.torweg.pulse.service.PulseException;
44   import org.torweg.pulse.service.event.JSONOutputEvent;
45   import org.torweg.pulse.service.event.XSLTOutputEvent;
46   import org.torweg.pulse.service.request.ServiceRequest;
47   import org.torweg.pulse.site.View;
48   import org.torweg.pulse.site.content.IAdministerSitemap;
49   import org.torweg.pulse.site.map.Sitemap;
50   import org.torweg.pulse.site.map.SitemapNode;
51   import org.torweg.pulse.util.adminui.JSONCommunicationUtils;
52   import org.torweg.pulse.util.adminui.RightsCheckUtils;
53   import org.torweg.pulse.util.entity.Node;
54   
55   /**
56    * the editor for the {@code Sitemap}. This controller corresponds to the
57    * sitemap shown in the west-panel of the website-administration.
58    * 
59    * @author Daniel Dietz
60    * @version $Revision: 2043 $
61    */
62   // TODO: final refactoring after the right's-system has been refactored
63   public class SitemapEditor extends Controller implements DeprecatedConfigurable {
64   
65       /**
66        * the logger.
67        */
68       private static final Logger LOGGER = LoggerFactory
69               .getLogger(SitemapEditor.class);
70   
71       /**
72        * the {@code ConfigBean} of this {@code Controller}.
73        */
74       private SitemapEditorConfig config;
75   
76       /**
77        * @param bundle
78        *            the current {@code Bundle}
79        * @param request
80        *            the current {@code ServiceRequest}
81        * @return the initialization {@code Result} for the {@code SitemapEditor}
82        * @see Sitemap
83        * @see SitemapNode
84        */
85       @RequireToken
86       @Action(value = "sitemapTreeInit", generate = true)
87       @Permission("browseSitemap")
88       @Groups(values = { "SitemapEditor", "SitemapViewer" })
89       public final SitemapEditorResult initTree(final Bundle bundle,
90               final ServiceRequest request) {
91   
92           Session s = null;
93           Transaction tx = null;
94           SitemapEditorResult result = null;
95   
96           try {
97               s = Lifecycle.getHibernateDataSource().createNewSession();
98               tx = s.beginTransaction();
99   
100              // get available sitemaps for the result
101              @SuppressWarnings("unchecked")
102              List<Sitemap> siteMaps = s.createQuery("from Sitemap as sm").list();
103  
104              for (Sitemap sitemap : siteMaps) {
105                  LOGGER.debug("found sitemap for locale: {}", sitemap
106                          .getRootNode().getLocale());
107              }
108  
109              // roll-back since load only
110              tx.rollback();
111  
112              // set the sitemaps for the result
113              result = new SitemapEditorResult();
114              result.setSitemaps(siteMaps);
115  
116              if (request.getCommand().getParameter("sitemap-locale") != null) {
117                  result.setSitemapLocale(request.getCommand()
118                          .getParameter("sitemap-locale").getFirstValue());
119              }
120  
121          } catch (Exception e) {
122              if (tx != null) {
123                  tx.rollback();
124              }
125              throw new PulseException("SitemapEditor.doSitemapTreeInit.failed: "
126                      + e.getLocalizedMessage(), e);
127          } finally {
128              s.close();
129          }
130  
131          setAjaxResult(request);
132  
133          return result;
134      }
135  
136      /**
137       * @param bundle
138       *            the {@code Bundle} we belong to
139       * @param request
140       *            the current {@code ServiceRequest}
141       * @see SitemapNode
142       */
143      @RequireToken
144      @Action(value = "sitemapTreeLoadNode", generate = true)
145      @Permission("browseSitemap")
146      @Groups(values = { "SitemapEditor", "SitemapViewer" })
147      public final void loadNode(final Bundle bundle, final ServiceRequest request) {
148  
149          // get load-node id
150          Long nodeId = Long.parseLong(request.getCommand().getParameter("id")
151                  .getFirstValue());
152  
153          // setup
154          Session s = Lifecycle.getHibernateDataSource().createNewSession();
155          Transaction tx = s.beginTransaction();
156          SitemapNode sitemapNode = null;
157          // to hold loaded SitemapNode
158          JSONObject loadedNode = null;
159  
160          try {
161              sitemapNode = (SitemapNode) s.get(SitemapNode.class, nodeId);
162              if (sitemapNode != null) {
163                  loadedNode = sitemapNode.toJSON();
164              }
165          } catch (Exception e) {
166              tx.rollback();
167              throw new PulseException(
168                      "SitemapEditor.doSitemapTreeLoadNode.failed: "
169                              + e.getLocalizedMessage(), e);
170          } finally {
171              s.close();
172          }
173  
174          // output
175          if (loadedNode == null) {
176              JSONObject error = new JSONObject();
177              error.put("e", "nodeDoesNotExist");
178              JSONCommunicationUtils.jsonErrorMessage(request, error);
179          } else {
180              JSONCommunicationUtils.jsonSuccessMessage(request, "node",
181                      loadedNode);
182          }
183      }
184  
185      /**
186       * @param bundle
187       *            the {@code Bundle} we belong to
188       * @param request
189       *            the current {@code ServiceRequest} *
190       * @return {@code null}
191       * @see SitemapNode
192       */
193      @RequireToken
194      @Action(value = "sitemapTreeGetChildren", generate = true)
195      @Permission("browseSitemap")
196      @Groups(values = { "SitemapEditor", "SitemapViewer" })
197      public final Object getChildren(final Bundle bundle,
198              final ServiceRequest request) {
199  
200          // retrieve nodeId from request
201          Long nodeId = null;
202          if (!(request.getCommand().getParameter("node").getFirstValue()
203                  .length() > 5 && request.getCommand().getParameter("node")
204                  .getFirstValue().substring(0, 6).equals("source"))) {
205              nodeId = Long.parseLong(request.getCommand().getParameter("node")
206                      .getFirstValue());
207          }
208  
209          // setup
210          Session s = null;
211          Transaction tx = null;
212          SitemapNode node = null;
213          JSONArray children = new JSONArray();
214  
215          try {
216  
217              s = Lifecycle.getHibernateDataSource().createNewSession();
218              tx = s.beginTransaction();
219  
220              if (nodeId == null) {
221                  // load a root-node
222                  node = loadSitemapRootNodeForGivenLocale(request, s);
223                  JSONObject nodeObj = node.toJSON();
224                  // add mode
225                  processRequestForMode(request, node, nodeObj);
226                  children.add(nodeObj);
227              } else {
228                  // load node for given id
229                  node = (SitemapNode) s.get(SitemapNode.class, nodeId);
230                  // add children
231                  if (node.getChildren() != null) {
232                      for (Node childNode : node.getChildren()) {
233                          if (childNode == null) {
234                              continue;
235                          }
236                          JSONObject nodeObj = childNode.toJSON();
237  
238                          // add mode
239                          processRequestForMode(request, (SitemapNode) childNode,
240                                  nodeObj);
241  
242                          // disable move for virtual nodes
243                          if (((SitemapNode) childNode).getType().equals(
244                                  SitemapNode.Type.VIRTUAL)) {
245                              nodeObj.put("allowDrop", false);
246                              nodeObj.put("allowDrag", false);
247                              nodeObj.put("allowChildren", false);
248                          }
249  
250                          children.add(nodeObj);
251                      }
252                  }
253              }
254  
255              tx.commit();
256          } catch (Exception e) {
257              if (tx != null) {
258                  tx.rollback();
259              }
260              throw new PulseException(
261                      "SitemapEditor.doSitemapTreeGetChildren.failed: "
262                              + e.getLocalizedMessage(), e);
263          } finally {
264              s.close();
265          }
266  
267          // output
268          request.getEventManager().addEvent(new JSONOutputEvent(children));
269          return null;
270      }
271  
272      /**
273       * processes the request for parameter mode and modifies JSONResponse
274       * accordingly.
275       * 
276       * @param request
277       *            the current {@code ServiceRequest}
278       * @param node
279       *            the current node
280       * @param nodeObj
281       *            the current JSON-representation of the node
282       */
283      private void processRequestForMode(final ServiceRequest request,
284              final SitemapNode node, final JSONObject nodeObj) {
285          String mode = null;
286          if (request.getCommand().getParameter("mode") != null
287                  && !request.getCommand().getParameter("mode").getFirstValue()
288                          .equals("")) {
289              mode = request.getCommand().getParameter("mode").getFirstValue();
290          }
291          if (mode != null) {
292              nodeObj.put("mode", mode);
293              if (mode.equals("select")) {
294                  addUri(request, node, nodeObj);
295              }
296          }
297      }
298  
299      /**
300       * adds the URI for the sitemap to the JSON-representation of the node.
301       * 
302       * @param request
303       *            the current {@code ServiceRequest}
304       * @param node
305       *            the current sitemap-node
306       * @param nodeObj
307       *            the current JSON-representation of the node
308       */
309      private void addUri(final ServiceRequest request, final SitemapNode node,
310              final JSONObject nodeObj) {
311          View view = node.getView();
312          if (view != null) {
313              try {
314                  view.getContent();
315                  URI httpURI = new URI(view
316                          .getCommand(request.getCommand().createCopy(false))
317                          .setLocale(node.getLocale()).setSitemapID(node.getId())
318                          .toCommandURL(request));
319                  nodeObj.put("uri", shortenHttpURI(httpURI, request));
320              } catch (URISyntaxException e) {
321                  nodeObj.put("uri", "");
322              }
323          }
324      }
325  
326      /**
327       * tries to shorten the HTTP URI to the file by leaving out all parts before
328       * the path, if possible.
329       * 
330       * @param httpURI
331       *            the URI
332       * @param request
333       *            the current request
334       * @return a string representation of the processed URI
335       */
336      private String shortenHttpURI(final URI httpURI,
337              final ServiceRequest request) {
338          if (httpURI.getHost().equals(
339                  request.getHttpServletRequest().getServerName())
340                  && ((httpURI.getPort() == request.getHttpServletRequest()
341                          .getServerPort()) || (httpURI.getPort() == -1))) {
342              StringBuilder shortUri = new StringBuilder(httpURI.getRawPath());
343              if (httpURI.getRawQuery() != null) {
344                  shortUri.insert(0, '?').append(httpURI.getRawQuery());
345              }
346              if (httpURI.getRawFragment() != null) {
347                  shortUri.insert(0, '#').append(httpURI.getRawFragment());
348              }
349              return shortUri.toString();
350          } else {
351              return httpURI.toASCIIString();
352          }
353      }
354  
355      /**
356       * @param bundle
357       *            the {@code Bundle} we belong to
358       * @param request
359       *            the current {@code ServiceRequest}
360       * @see SitemapNode
361       */
362      @RequireToken
363      @Action(value = "sitemapTreeCreateNode", generate = true)
364      @Permission("editSitemap")
365      @Groups(values = { "SitemapEditor" })
366      public final void createNode(final Bundle bundle,
367              final ServiceRequest request) {
368  
369          // setup
370          Session s = null;
371          Transaction tx = null;
372          SitemapNode newSitemapNode = null;
373          // error : userHasNoEditRightsForLocale (if occurs)
374          // error : nodeCreateDisabled (if occurs)
375          JSONObject error = null;
376  
377          try {
378  
379              s = Lifecycle.getHibernateDataSource().createNewSession();
380              tx = s.beginTransaction();
381  
382              // load parent SitemapNode
383              SitemapNode parentNode = (SitemapNode) s.get(
384                      SitemapNode.class,
385                      Long.parseLong(request.getCommand().getParameter("id")
386                              .getFirstValue()), LockOptions.UPGRADE);
387  
388              // check user
389              error = RightsCheckUtils.checkUserAgainstLocale(error, bundle,
390                      request, parentNode.getLocale());
391  
392              if (error == null) {
393  
394                  // create new SitemapNode
395                  newSitemapNode = new SitemapNode(request.getCommand()
396                          .getParameter("name").getFirstValue().trim(),
397                          parentNode.getLocale());
398  
399                  // add parent-section-tag
400                  newSitemapNode.setSectionTag(parentNode.getSectionTag());
401  
402                  // add parent-roles
403                  for (Role role : parentNode.getRoles()) {
404                      newSitemapNode.addRole(role);
405                  }
406  
407                  // add the new SitemapNode to the parent SitemapNode
408                  parentNode.addChild(newSitemapNode);
409  
410                  // persist
411                  s.saveOrUpdate(parentNode);
412              }
413  
414              tx.commit();
415          } catch (Exception e) {
416              if (tx != null) {
417                  tx.rollback();
418              }
419              throw new PulseException(
420                      "SitemapEditor.doSitemapTreeCreateNode.failed: "
421                              + e.getLocalizedMessage(), e);
422          } finally {
423              s.close();
424          }
425  
426          // output
427          if (error == null) {
428              JSONCommunicationUtils.jsonSuccessMessage(request, "id",
429                      newSitemapNode.getId().toString());
430          } else {
431              JSONCommunicationUtils.jsonErrorMessage(request, error);
432          }
433      }
434  
435      /**
436       * renames a {@code SitemapNode}.
437       * 
438       * @param bundle
439       *            the {@code Bundle} we belong to
440       * @param request
441       *            the current {@code ServiceRequest}
442       * @see SitemapNode
443       */
444      @RequireToken
445      @Action(value = "sitemapTreeRenameNode", generate = true)
446      @Permission("editSitemap")
447      @Groups(values = { "SitemapEditor" })
448      public final void renameNode(final Bundle bundle,
449              final ServiceRequest request) {
450  
451          // setup
452          Session s = null;
453          Transaction tx = null;
454          // error : userHasNoEditRightsForLocale (if occurs)
455          // error : nodeRenameDisabled (if occurs)
456          JSONObject error = null;
457  
458          try {
459  
460              s = Lifecycle.getHibernateDataSource().createNewSession();
461              tx = s.beginTransaction();
462  
463              // load SitemapNode
464              SitemapNode sitemapNode = (SitemapNode) s.get(
465                      SitemapNode.class,
466                      Long.parseLong(request.getCommand().getParameter("id")
467                              .getFirstValue()));
468  
469              // check user
470              error = RightsCheckUtils.checkUserAgainstLocale(error, bundle,
471                      request, sitemapNode.getLocale());
472  
473              if (error == null) {
474                  // rename sitemapNode
475                  if (!request.getCommand().getParameter("newname")
476                          .getFirstValue().trim().equals("")) {
477                      sitemapNode.setName(request.getCommand()
478                              .getParameter("newname").getFirstValue());
479  
480                      // logger.debug("\n\n\n"
481                      // + new java.io.InputStreamReader(
482                      // new java.io.ByteArrayInputStream(request
483                      // .getCommand().getParameter(
484                      // "newname").getFirstValue()
485                      // .getBytes())).getEncoding()
486                      // + "\n\n\n");
487  
488                  }
489  
490                  // persist
491                  s.saveOrUpdate(sitemapNode);
492              }
493  
494              tx.commit();
495          } catch (Exception e) {
496              if (tx != null) {
497                  tx.rollback();
498              }
499              throw new PulseException(
500                      "SitemapEditor.doSitemapTreeRenameNode.failed: "
501                              + e.getLocalizedMessage(), e);
502          } finally {
503              s.close();
504          }
505  
506          // output
507          if (error != null) {
508              JSONCommunicationUtils.jsonErrorMessage(request, error);
509          } else {
510              JSONCommunicationUtils.jsonSuccessMessage(request);
511          }
512      }
513  
514      /**
515       * @param bundle
516       *            the {@code Bundle} we belong to
517       * @param request
518       *            the current {@code ServiceRequest}
519       * @see SitemapNode
520       */
521      @RequireToken
522      @Action(value = "sitemapTreeMoveNode", generate = true)
523      @Permission("editSitemap")
524      @Groups(values = { "SitemapEditor" })
525      public final void moveNode(final Bundle bundle, final ServiceRequest request) {
526  
527          // setup
528          Session s = null;
529          Transaction tx = null;
530          SitemapNode newParentNode = null;
531          int index = -1;
532          // error : nodeMoveDisabled (if occurs)
533          // error : cannotMoveNodeToRootLevel (if occurs)
534          // error : userHasNoEditRightsForLocale (if occurs)
535          JSONObject error = null;
536  
537          try {
538              s = Lifecycle.getHibernateDataSource().createNewSession();
539              tx = s.beginTransaction();
540  
541              // load node that's to be moved
542              SitemapNode moveNode = (SitemapNode) s.get(
543                      SitemapNode.class,
544                      Long.parseLong(request.getCommand()
545                              .getParameter("movenodeid").getFirstValue()),
546                      LockOptions.UPGRADE);
547  
548              // determine new parent by insert-point
549              if (request.getCommand().getParameter("insertpoint")
550                      .getFirstValue().equals("append")) {
551                  // load new parent (reference-node)
552                  newParentNode = (SitemapNode) s.get(
553                          SitemapNode.class,
554                          Long.parseLong(request.getCommand()
555                                  .getParameter("targetnodeid").getFirstValue()),
556                          LockOptions.UPGRADE);
557              } else {
558                  // load new parent (reference-node.parent)
559                  newParentNode = (SitemapNode) ((SitemapNode) s.get(
560                          SitemapNode.class,
561                          Long.parseLong(request.getCommand()
562                                  .getParameter("targetnodeid").getFirstValue()),
563                          LockOptions.UPGRADE)).getParent();
564                  if (newParentNode == null) {
565                      error = new JSONObject();
566                      error.put("e", "cannotMoveNodeToRootLevel");
567                  } else {
568                      // determine add-(reference-node.)position-index
569                      index = determineIndex(request, s, newParentNode);
570                  }
571              }
572  
573              // user check
574              error = RightsCheckUtils.checkUserAgainstLocale(error, bundle,
575                      request, moveNode.getLocale());
576  
577              if (error == null) {
578                  // load old parent
579                  SitemapNode oldParentNode = (SitemapNode) moveNode.getParent();
580  
581                  // perform move
582                  performMove(newParentNode, index, moveNode, oldParentNode);
583                  // persist
584                  s.saveOrUpdate(oldParentNode);
585                  s.saveOrUpdate(newParentNode);
586              }
587  
588              tx.commit();
589          } catch (Exception e) {
590              if (tx != null) {
591                  tx.rollback();
592              }
593              throw new PulseException(
594                      "SitemapEditor.doSitemapTreeMoveNode.failed: "
595                              + e.getLocalizedMessage(), e);
596          } finally {
597              s.close();
598          }
599  
600          // output
601          if (error == null) {
602              JSONCommunicationUtils.jsonSuccessMessage(request);
603          } else {
604              JSONCommunicationUtils.jsonErrorMessage(request, error);
605          }
606      }
607  
608      /**
609       * performs the actual move operation of moveNode from oldParentNode to
610       * newParentNode.
611       * 
612       * @param newParentNode
613       *            the new parent
614       * @param index
615       *            the index of the position to add the moveNode to its new
616       *            parent
617       * @param moveNode
618       *            the node which is to be moved
619       * @param oldParentNode
620       *            the old parent
621       */
622      private void performMove(final SitemapNode newParentNode, final int index,
623              final SitemapNode moveNode, final SitemapNode oldParentNode) {
624          oldParentNode.removeChild(moveNode);
625          if (index == -1) {
626              newParentNode.addChild(moveNode);
627          } else {
628              try {
629                  newParentNode.addChild(index, moveNode);
630              } catch (IndexOutOfBoundsException e) {
631                  newParentNode.addChild(moveNode);
632              }
633          }
634      }
635  
636      /**
637       * determines the add-index for the moveNode according to the given request.
638       * 
639       * @param request
640       *            the current {@code ServiceRequest}
641       * @param s
642       *            the current {@code Session}
643       * @param newParentNode
644       *            the new parent
645       * 
646       * @return the index
647       */
648      private int determineIndex(final ServiceRequest request, final Session s,
649              final SitemapNode newParentNode) {
650          int index = newParentNode.getChildIndex((SitemapNode) s.get(
651                  SitemapNode.class,
652                  Long.parseLong(request.getCommand()
653                          .getParameter("targetnodeid").getFirstValue())));
654          if (request.getCommand().getParameter("insertpoint").getFirstValue()
655                  .equals("below")) {
656              index += 1;
657          }
658          return index;
659      }
660  
661      /**
662       * @param bundle
663       *            the {@code Bundle} we belong to
664       * @param request
665       *            the current {@code ServiceRequest}
666       * @see SitemapNode
667       */
668      @RequireToken
669      @Action(value = "sitemapTreeToggleNode", generate = true)
670      @Permission("editSitemap")
671      @Groups(values = { "SitemapEditor" })
672      public final void toggleNodeVisibility(final Bundle bundle,
673              final ServiceRequest request) {
674  
675          // setup
676          Session s = null;
677          Transaction tx = null;
678          SitemapNode toggleNode = null;
679          // error : userHasNoEditRightsForLocale (if occurs)
680          JSONObject error = null;
681          boolean reloadNode = false;
682  
683          try {
684  
685              s = Lifecycle.getHibernateDataSource().createNewSession();
686              tx = s.beginTransaction();
687  
688              // load node to toggle
689              toggleNode = (SitemapNode) s.get(
690                      SitemapNode.class,
691                      Long.parseLong(request.getCommand().getParameter("id")
692                              .getFirstValue()));
693  
694              // check user
695              error = RightsCheckUtils.checkUserAgainstLocale(bundle, request,
696                      toggleNode.getLocale());
697  
698              // toggle
699              if (error == null) {
700                  toggleNode.setVisible(!toggleNode.isVisible());
701  
702                  // process IAdminsterSitemap-content
703                  View v = toggleNode.getView();
704                  if (v != null && v.getContent() instanceof IAdministerSitemap) {
705                      ((IAdministerSitemap) v.getContent()).onSitemapChange(
706                              toggleNode, s);
707                      reloadNode = true;
708                  }
709  
710                  // persist
711                  s.update(toggleNode);
712  
713                  // add/remove from search index!
714                  if (toggleNode.isVisible()) {
715                      ((FullTextSession) s).index(toggleNode);
716                  } else {
717                      ((FullTextSession) s).purge(SitemapNode.class,
718                              toggleNode.getId());
719                  }
720              }
721  
722              tx.commit();
723          } catch (Exception e) {
724              if (tx != null) {
725                  tx.rollback();
726              }
727              throw new PulseException(
728                      "SitemapEditor.doSitemapTreeToggleNode.failed: "
729                              + e.getLocalizedMessage(), e);
730          } finally {
731              s.close();
732          }
733  
734          // output
735          if (error == null) {
736              JSONObject responseObject = new JSONObject();
737              responseObject.put("visible",
738                      Boolean.toString(toggleNode.isVisible()));
739              responseObject.put("reloadNode", reloadNode);
740              JSONCommunicationUtils.jsonSuccessMessage(request, responseObject);
741          } else {
742              JSONCommunicationUtils.jsonErrorMessage(request, error);
743          }
744      }
745  
746      /**
747       * @param bundle
748       *            the {@code Bundle} we belong to
749       * @param request
750       *            the current {@code ServiceRequest}
751       * @see SitemapNode
752       */
753      @RequireToken
754      @Action(value = "sitemapTreeDeleteNode", generate = true)
755      @Permission("editSitemap")
756      @Groups(values = { "SitemapEditor" })
757      public final void deleteNode(final Bundle bundle,
758              final ServiceRequest request) {
759  
760          // setup
761          Session s = null;
762          Transaction tx = null;
763          // error : userHasNoEditRightsForLocale (if occurs)
764          // error : nodeDeleteDisabled (if occurs)
765          // error : cannotDeleteRootNode (if occurs)
766          JSONObject error = null;
767  
768          try {
769  
770              s = Lifecycle.getHibernateDataSource().createNewSession();
771              tx = s.beginTransaction();
772  
773              // load SitemapNode that is to be deleted
774              SitemapNode deleteNode = (SitemapNode) s.get(
775                      SitemapNode.class,
776                      Long.parseLong(request.getCommand().getParameter("id")
777                              .getFirstValue()), LockOptions.UPGRADE);
778  
779              // root node check
780              if (error == null && deleteNode.getParent() == null) {
781                  error = new JSONObject();
782                  error.put("e", "cannotDeleteRootNode");
783              }
784              // user check
785              error = RightsCheckUtils.checkUserAgainstLocale(error, bundle,
786                      request, deleteNode.getLocale());
787  
788              if (error == null) {
789  
790                  // load parent SitemapNode
791                  SitemapNode parentSitemapNode = (SitemapNode) deleteNode
792                          .getParent();
793  
794                  // delete
795                  parentSitemapNode.removeChild(deleteNode);
796                  s.delete(deleteNode);
797  
798                  // persist parent
799                  s.saveOrUpdate(parentSitemapNode);
800  
801              }
802  
803              tx.commit();
804          } catch (Exception e) {
805              if (tx != null) {
806                  tx.rollback();
807              }
808              throw new PulseException(
809                      "SitemapEditor.doSitemapTreeDeleteNode.failed: "
810                              + e.getLocalizedMessage(), e);
811          } finally {
812              s.close();
813          }
814  
815          // output
816          if (error == null) {
817              JSONCommunicationUtils.jsonSuccessMessage(request);
818          } else {
819              JSONCommunicationUtils.jsonErrorMessage(request, error);
820          }
821      }
822  
823      /**
824       * convenience method.
825       * 
826       * @param request
827       *            the current {@code ServiceRequest}; determines {@code Locale}
828       * @param s
829       *            the current {@code Session}
830       * @return {@code Sitemap} root-node for "given" {@code Locale}
831       */
832      private SitemapNode loadSitemapRootNodeForGivenLocale(
833              final ServiceRequest request, final Session s) {
834          // site-map: initialize for locale
835          String localeString = request.getLocale().toString();
836  
837          // if locale in request-parameter it overrides request locale
838          if (request.getCommand().getParameter("sitemap-locale") != null) {
839              localeString = request.getCommand().getParameter("sitemap-locale")
840                      .getFirstValue();
841          }
842  
843          // load the site-map
844          Sitemap sitemap = (Sitemap) s
845                  .createQuery("from Sitemap as sm where sm.locale=?")
846                  .setString(0, localeString).uniqueResult();
847  
848          return sitemap.getRootNode();
849      }
850  
851      /**
852       * configures the request for an AJAX response.
853       * 
854       * @param request
855       *            the current {@code ServiceRequest}
856       */
857      private void setAjaxResult(final ServiceRequest request) {
858          XSLTOutputEvent event = new XSLTOutputEvent(this.config.getAjaxXSL());
859          event.setStopEvent(true);
860          request.getEventManager().addEvent(event);
861      }
862  
863      /**
864       * initializes the {@code SitemapEditor}.
865       * 
866       * @param c
867       *            the {@code ConfigBean} of this {@code Controller}
868       */
869      public void init(final ConfigBean c) {
870          this.config = (SitemapEditorConfig) c;
871      }
872  
873  }
874