View Javadoc

1   /**
2    * Copyright (c) 2011, University of Konstanz, Distributed Systems Group
3    * All rights reserved.
4    * 
5    * Redistribution and use in source and binary forms, with or without
6    * modification, are permitted provided that the following conditions are met:
7    * * Redistributions of source code must retain the above copyright
8    * notice, this list of conditions and the following disclaimer.
9    * * Redistributions in binary form must reproduce the above copyright
10   * notice, this list of conditions and the following disclaimer in the
11   * documentation and/or other materials provided with the distribution.
12   * * Neither the name of the University of Konstanz nor the
13   * names of its contributors may be used to endorse or promote products
14   * derived from this software without specific prior written permission.
15   * 
16   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17   * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19   * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
20   * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26   */
27  
28  package org.treetank.service.jaxrx.implementation;
29  
30  import java.io.IOException;
31  import java.io.InputStream;
32  import java.io.OutputStream;
33  import java.util.Map;
34  
35  import javax.ws.rs.core.StreamingOutput;
36  
37  import org.jaxrx.core.JaxRxException;
38  import org.jaxrx.core.QueryParameter;
39  import org.treetank.access.NodeReadTrx;
40  import org.treetank.access.NodeWriteTrx;
41  import org.treetank.access.NodeWriteTrx.HashKind;
42  import org.treetank.access.conf.SessionConfiguration;
43  import org.treetank.access.conf.StandardSettings;
44  import org.treetank.api.IStorage;
45  import org.treetank.api.INodeReadTrx;
46  import org.treetank.api.INodeWriteTrx;
47  import org.treetank.api.ISession;
48  import org.treetank.exception.TTException;
49  import org.treetank.node.interfaces.IStructNode;
50  import org.treetank.service.jaxrx.enums.EIdAccessType;
51  import org.treetank.service.jaxrx.util.RestXPathProcessor;
52  import org.treetank.service.jaxrx.util.WorkerHelper;
53  import org.treetank.service.xml.serialize.XMLSerializer;
54  import org.treetank.service.xml.serialize.XMLSerializer.XMLSerializerBuilder;
55  import org.treetank.service.xml.serialize.XMLSerializerProperties;
56  import org.treetank.service.xml.shredder.EShredderInsert;
57  
58  /**
59   * This class is responsible to work with database specific XML node id's. It
60   * allows to access a resource by a node id, modify an existing resource by node
61   * id, delete an existing resource by node id and to append a new resource to an
62   * existing XML element identified by a node id.
63   * 
64   * @author Patrick Lang, Lukas Lewandowski, University of Konstanz
65   * 
66   */
67  public class NodeIdRepresentation {
68  
69      /**
70       * This field specifies the begin result element of the request.
71       */
72      private static final transient byte[] BEGINRESULT = "<jaxrx:result xmlns:jaxrx=\"http://jaxrx.org/\">"
73          .getBytes();
74  
75      /**
76       * This field specifies the end result element of the request.
77       */
78      private static final transient byte[] ENDRESULT = "</jaxrx:result>".getBytes();
79  
80      private static final transient String NOTFOUND = "Node id not found";
81  
82      /**
83       * The 'yes' string.
84       */
85      private static final transient String YESSTRING = "yes";
86  
87      /**
88       * Storage path to the data.
89       */
90      private final IStorage mDatabase;
91  
92      /**
93       * 
94       * Constructor.
95       * 
96       * @param pDatabase
97       *            storage to be set
98       */
99      public NodeIdRepresentation(final IStorage pDatabase) {
100         mDatabase = pDatabase;
101     }
102 
103     /**
104      * This method is responsible to deliver the whole XML resource addressed by
105      * a unique node id.
106      * 
107      * @param resourceName
108      *            The name of the database, where the node id belongs.
109      * @param nodeId
110      *            The unique node id of the requested resource.
111      * @param queryParams
112      *            The optional query parameters.
113      * @return The whole XML resource addressed by a unique node id.
114      * @throws JaxRxException
115      *             The exception occurred.
116      */
117     public StreamingOutput getResource(final String resourceName, final long nodeId,
118         final Map<QueryParameter, String> queryParams) throws JaxRxException {
119         final StreamingOutput sOutput = new StreamingOutput() {
120             @Override
121             public void write(final OutputStream output) throws IOException, JaxRxException {
122 
123                 // final String xPath = queryParams.get(QueryParameter.QUERY);
124                 final String revision = queryParams.get(QueryParameter.REVISION);
125                 final String wrap = queryParams.get(QueryParameter.WRAP);
126                 final String doNodeId = queryParams.get(QueryParameter.OUTPUT);
127                 final boolean wrapResult = (wrap == null) ? false : wrap.equalsIgnoreCase(YESSTRING);
128                 final boolean nodeid = (doNodeId == null) ? false : doNodeId.equalsIgnoreCase(YESSTRING);
129                 final Long rev = revision == null ? null : Long.valueOf(revision);
130                 serialize(resourceName, nodeId, rev, nodeid, output, wrapResult);
131             }
132         };
133 
134         return sOutput;
135     }
136 
137     /**
138      * This method is responsible to deliver the whole XML resource addressed by
139      * a unique node id.
140      * 
141      * @param resourceName
142      *            The name of the database, where the node id belongs.
143      * @param nodeId
144      *            The unique node id of the requested resource.
145      * @param queryParams
146      *            The optional query parameters.
147      * @param accessType
148      *            The id access type to access a resource by a relative method
149      *            type defined in {@link EIdAccessType}.
150      * @return The whole XML resource addressed by a unique node id.
151      * @throws JaxRxException
152      *             The exception occurred.
153      */
154     public StreamingOutput getResourceByAT(final String resourceName, final long nodeId,
155         final Map<QueryParameter, String> queryParams, final EIdAccessType accessType) throws JaxRxException {
156         final StreamingOutput sOutput = new StreamingOutput() {
157             @Override
158             public void write(final OutputStream output) throws IOException, JaxRxException {
159 
160                 // final String xPath = queryParams.get(QueryParameter.QUERY);
161                 final String revision = queryParams.get(QueryParameter.REVISION);
162                 final String wrap = queryParams.get(QueryParameter.WRAP);
163                 final String doNodeId = queryParams.get(QueryParameter.OUTPUT);
164                 final boolean wrapResult = (wrap == null) ? false : wrap.equalsIgnoreCase(YESSTRING);
165                 final boolean nodeid = (doNodeId == null) ? false : doNodeId.equalsIgnoreCase(YESSTRING);
166                 final Long rev = revision == null ? null : Long.valueOf(revision);
167                 serializeAT(resourceName, nodeId, rev, nodeid, output, wrapResult, accessType);
168             }
169         };
170 
171         return sOutput;
172     }
173 
174     /**
175      * This method is responsible to perform a XPath query expression on the XML
176      * resource which is addressed through a unique node id.
177      * 
178      * @param resourceName
179      *            The name of the database, the node id belongs to.
180      * @param nodeId
181      *            The node id of the requested resource.
182      * @param query
183      *            The XPath expression.
184      * @param queryParams
185      *            The optional query parameters (output, wrap, revision).
186      * @return The result of the XPath query expression.
187      */
188     public StreamingOutput performQueryOnResource(final String resourceName, final long nodeId,
189         final String query, final Map<QueryParameter, String> queryParams) {
190 
191         final StreamingOutput sOutput = new StreamingOutput() {
192             @Override
193             public void write(final OutputStream output) throws IOException, JaxRxException {
194 
195                 final String revision = queryParams.get(QueryParameter.REVISION);
196                 final String wrap = queryParams.get(QueryParameter.WRAP);
197                 final String doNodeId = queryParams.get(QueryParameter.OUTPUT);
198                 final boolean wrapResult = (wrap == null) ? true : wrap.equalsIgnoreCase(YESSTRING);
199                 final boolean nodeid = (doNodeId == null) ? false : doNodeId.equalsIgnoreCase(YESSTRING);
200                 final Long rev = revision == null ? null : Long.valueOf(revision);
201                 final RestXPathProcessor xpathProcessor = new RestXPathProcessor(mDatabase);
202                 try {
203                     xpathProcessor.getXpathResource(resourceName, nodeId, query, nodeid, rev, output,
204                         wrapResult);
205                 } catch (final TTException exce) {
206                     throw new JaxRxException(exce);
207                 }
208             }
209         };
210 
211         return sOutput;
212     }
213 
214     /**
215      * This method is responsible to delete an XML resource addressed through a
216      * unique node id (except root node id).
217      * 
218      * @param resourceName
219      *            The name of the database, which the node id belongs to.
220      * @param nodeId
221      *            The unique node id.
222      * @throws JaxRxException
223      *             The exception occurred.
224      */
225     public void deleteResource(final String resourceName, final long nodeId) throws JaxRxException {
226         synchronized (resourceName) {
227             ISession session = null;
228             INodeWriteTrx wtx = null;
229             boolean abort = false;
230             if (mDatabase.existsResource(resourceName)) {
231                 try {
232                     // Creating a new session
233                     session =
234                         mDatabase.getSession(new SessionConfiguration(resourceName, StandardSettings.KEY));
235                     // Creating a write transaction
236                     wtx = new NodeWriteTrx(session, session.beginBucketWtx(), HashKind.Rolling);
237                     // move to node with given rest id and deletes it
238                     if (wtx.moveTo(nodeId)) {
239                         wtx.remove();
240                         wtx.commit();
241                     } else {
242                         // workerHelper.closeWTX(abort, wtx, session, database);
243                         throw new JaxRxException(404, NOTFOUND);
244                     }
245                 } catch (final TTException exce) {
246                     abort = true;
247                     throw new JaxRxException(exce);
248                 } finally {
249                     try {
250                         WorkerHelper.closeWTX(abort, wtx, session);
251                     } catch (final TTException exce) {
252                         throw new JaxRxException(exce);
253                     }
254                 }
255             } else {
256                 throw new JaxRxException(404, "DB not found");
257             }
258         }
259     }
260 
261     /**
262      * This method is responsible to modify the XML resource, which is addressed
263      * through a unique node id.
264      * 
265      * @param resourceName
266      *            The name of the database, where the node id belongs to.
267      * @param nodeId
268      *            The node id.
269      * @param newValue
270      *            The new value of the node that has to be replaced.
271      * @throws JaxRxException
272      *             The exception occurred.
273      */
274     public void modifyResource(final String resourceName, final long nodeId, final InputStream newValue)
275         throws JaxRxException {
276         synchronized (resourceName) {
277             ISession session = null;
278             INodeWriteTrx wtx = null;
279             boolean abort = false;
280             if (mDatabase.existsResource(resourceName)) {
281                 try {
282                     // Creating a new session
283                     session =
284                         mDatabase.getSession(new SessionConfiguration(resourceName, StandardSettings.KEY));
285                     // Creating a write transaction
286                     wtx = new NodeWriteTrx(session, session.beginBucketWtx(), HashKind.Rolling);
287 
288                     if (wtx.moveTo(nodeId)) {
289                         final long parentKey = wtx.getNode().getParentKey();
290                         wtx.remove();
291                         wtx.moveTo(parentKey);
292                         WorkerHelper.shredInputStream(wtx, newValue, EShredderInsert.ADDASFIRSTCHILD);
293 
294                     } else {
295                         // workerHelper.closeWTX(abort, wtx, session, database);
296                         throw new JaxRxException(404, NOTFOUND);
297                     }
298 
299                 } catch (final TTException exc) {
300                     abort = true;
301                     throw new JaxRxException(exc);
302                 } finally {
303                     try {
304                         WorkerHelper.closeWTX(abort, wtx, session);
305                     } catch (final TTException exce) {
306                         throw new JaxRxException(exce);
307                     }
308                 }
309             } else {
310                 throw new JaxRxException(404, "Requested resource not found");
311             }
312         }
313     }
314 
315     /**
316      * This method is responsible to perform a POST request to a node id. This
317      * method adds a new XML subtree as first child or as right sibling to the
318      * node which is addressed through a node id.
319      * 
320      * @param resourceName
321      *            The name of the database, the node id belongs to.
322      * @param nodeId
323      *            The node id.
324      * @param input
325      *            The new XML subtree.
326      * @param type
327      *            The type which indicates if the new subtree has to be inserted
328      *            as right sibling or as first child.
329      * @throws JaxRxException
330      *             The exception occurred.
331      */
332     public void addSubResource(final String resourceName, final long nodeId, final InputStream input,
333         final EIdAccessType type) throws JaxRxException {
334         ISession session = null;
335         INodeWriteTrx wtx = null;
336         synchronized (resourceName) {
337             boolean abort;
338             if (mDatabase.existsResource(resourceName)) {
339                 abort = false;
340                 try {
341                     // Creating a new session
342                     session =
343                         mDatabase.getSession(new SessionConfiguration(resourceName, StandardSettings.KEY));
344                     // Creating a write transaction
345                     wtx = new NodeWriteTrx(session, session.beginBucketWtx(), HashKind.Rolling);
346                     final boolean exist = wtx.moveTo(nodeId);
347                     if (exist) {
348                         if (type == EIdAccessType.FIRSTCHILD) {
349                             WorkerHelper.shredInputStream(wtx, input, EShredderInsert.ADDASFIRSTCHILD);
350                         } else if (type == EIdAccessType.RIGHTSIBLING) {
351                             WorkerHelper.shredInputStream(wtx, input, EShredderInsert.ADDASRIGHTSIBLING);
352                         } else if (type == EIdAccessType.LASTCHILD) {
353                             if (wtx.moveTo(((IStructNode)wtx.getNode()).getFirstChildKey())) {
354                                 long last = wtx.getNode().getDataKey();
355                                 while (wtx.moveTo(((IStructNode)wtx.getNode()).getRightSiblingKey())) {
356                                     last = wtx.getNode().getDataKey();
357                                 }
358                                 wtx.moveTo(last);
359                                 WorkerHelper.shredInputStream(wtx, input, EShredderInsert.ADDASRIGHTSIBLING);
360 
361                             } else {
362                                 throw new JaxRxException(404, NOTFOUND);
363                             }
364 
365                         } else if (type == EIdAccessType.LEFTSIBLING
366                             && wtx.moveTo(((IStructNode)wtx.getNode()).getLeftSiblingKey())) {
367 
368                             WorkerHelper.shredInputStream(wtx, input, EShredderInsert.ADDASRIGHTSIBLING);
369 
370                         }
371                     } else {
372                         throw new JaxRxException(404, NOTFOUND);
373                     }
374                 } catch (final JaxRxException exce) { 
375                     abort = true;
376                     throw exce;
377                 } catch (final Exception exce) {
378                     abort = true;
379                     throw new JaxRxException(exce);
380                 } finally {
381                     try {
382                         WorkerHelper.closeWTX(abort, wtx, session);
383                     } catch (final TTException exce) {
384                         throw new JaxRxException(exce);
385                     }
386                 }
387             }
388         }
389     }
390 
391     /**
392      * This method serializes requested resource
393      * 
394      * @param resource
395      *            The requested resource
396      * @param nodeId
397      *            The node id of the requested resource.
398      * @param revision
399      *            The revision of the requested resource.
400      * @param doNodeId
401      *            Specifies whether the node id's have to be shown in the
402      *            result.
403      * @param output
404      *            The output stream to be written.
405      * @param wrapResult
406      *            Specifies whether the result has to be wrapped with a result
407      *            element.
408      */
409     private void serialize(final String resource, final long nodeId, final Long revision,
410         final boolean doNodeId, final OutputStream output, final boolean wrapResult) {
411         if (mDatabase.existsResource(resource)) {
412             ISession session = null;
413             try {
414                 session = mDatabase.getSession(new SessionConfiguration(resource, StandardSettings.KEY));
415                 if (wrapResult) {
416                     output.write(BEGINRESULT);
417                     final XMLSerializerProperties props = new XMLSerializerProperties();
418                     final XMLSerializerBuilder builder =
419                         new XMLSerializerBuilder(session, nodeId, output, props);
420                     builder.setREST(doNodeId);
421                     builder.setID(doNodeId);
422                     builder.setDeclaration(false);
423                     final XMLSerializer serializer = builder.build();
424                     serializer.call();
425                     output.write(ENDRESULT);
426                 } else {
427                     final XMLSerializerProperties props = new XMLSerializerProperties();
428                     final XMLSerializerBuilder builder =
429                         new XMLSerializerBuilder(session, nodeId, output, props);
430                     builder.setREST(doNodeId);
431                     builder.setID(doNodeId);
432                     builder.setDeclaration(false);
433                     final XMLSerializer serializer = builder.build();
434                     serializer.call();
435 
436                 }
437             } catch (final TTException ttExcep) {
438                 throw new JaxRxException(ttExcep);
439             } catch (final IOException ioExcep) {
440                 throw new JaxRxException(ioExcep);
441             } catch (final Exception globExcep) {
442                 if (globExcep instanceof JaxRxException) { // NOPMD due
443                     // to
444                     // different
445                     // exception
446                     // types
447                     throw (JaxRxException)globExcep;
448                 } else {
449                     throw new JaxRxException(globExcep);
450                 }
451             } finally {
452                 try {
453                     WorkerHelper.closeRTX(null, session);
454                 } catch (final TTException exce) {
455                     throw new JaxRxException(exce);
456                 }
457             }
458 
459         } else {
460             throw new JaxRxException(404, "Resource does not exist");
461         }
462 
463     }
464 
465     /**
466      * This method serializes requested resource with an access type.
467      * 
468      * @param resource
469      *            The requested resource
470      * @param nodeId
471      *            The node id of the requested resource.
472      * @param revision
473      *            The revision of the requested resource.
474      * @param doNodeId
475      *            Specifies whether the node id's have to be shown in the
476      *            result.
477      * @param output
478      *            The output stream to be written.
479      * @param wrapResult
480      *            Specifies whether the result has to be wrapped with a result
481      *            element.
482      * @param accessType
483      *            The {@link EIdAccessType} which indicates the access to a
484      *            special node.
485      */
486     private void serializeAT(final String resource, final long nodeId, final Long revision,
487         final boolean doNodeId, final OutputStream output, final boolean wrapResult,
488         final EIdAccessType accessType) {
489         if (mDatabase.existsResource(resource)) {
490             ISession session = null;
491             INodeReadTrx rtx = null;
492             try {
493                 session = mDatabase.getSession(new SessionConfiguration(resource, StandardSettings.KEY));
494                 if (revision == null) {
495                     rtx = new NodeReadTrx(session.beginBucketRtx(session.getMostRecentVersion()));
496                 } else {
497                     rtx = new NodeReadTrx(session.beginBucketRtx(revision));
498                 }
499 
500                 if (rtx.moveTo(nodeId)) {
501 
502                     switch (accessType) {
503                     case FIRSTCHILD:
504                         if (!rtx.moveTo(((IStructNode)rtx.getNode()).getFirstChildKey()))
505                             throw new JaxRxException(404, NOTFOUND);
506                         break;
507                     case LASTCHILD:
508                         if (rtx.moveTo(((IStructNode)rtx.getNode()).getFirstChildKey())) {
509                             long last = rtx.getNode().getDataKey();
510                             while (rtx.moveTo(((IStructNode)rtx.getNode()).getRightSiblingKey())) {
511                                 last = rtx.getNode().getDataKey();
512                             }
513                             rtx.moveTo(last);
514                         } else {
515                             throw new JaxRxException(404, NOTFOUND);
516                         }
517                         break;
518                     case RIGHTSIBLING:
519                         if (!rtx.moveTo(((IStructNode)rtx.getNode()).getRightSiblingKey()))
520                             throw new JaxRxException(404, NOTFOUND);
521                         break;
522                     case LEFTSIBLING:
523                         if (!rtx.moveTo(((IStructNode)rtx.getNode()).getLeftSiblingKey()))
524                             throw new JaxRxException(404, NOTFOUND);
525                         break;
526                     default: // nothing to do;
527                     }
528                     if (wrapResult) {
529                         output.write(BEGINRESULT);
530                         final XMLSerializerProperties props = new XMLSerializerProperties();
531                         final XMLSerializerBuilder builder =
532                             new XMLSerializerBuilder(session, rtx.getNode().getDataKey(), output, props);
533                         builder.setREST(doNodeId);
534                         builder.setID(doNodeId);
535                         builder.setDeclaration(false);
536                         builder.setIndend(false);
537                         final XMLSerializer serializer = builder.build();
538                         serializer.call();
539 
540                         output.write(ENDRESULT);
541                     } else {
542                         final XMLSerializerProperties props = new XMLSerializerProperties();
543                         final XMLSerializerBuilder builder =
544                             new XMLSerializerBuilder(session, rtx.getNode().getDataKey(), output, props);
545                         builder.setREST(doNodeId);
546                         builder.setID(doNodeId);
547                         builder.setDeclaration(false);
548                         builder.setIndend(false);
549                         final XMLSerializer serializer = builder.build();
550                         serializer.call();
551                     }
552                 } else {
553                     throw new JaxRxException(404, NOTFOUND);
554                 }
555             } catch (final TTException ttExcep) {
556                 throw new JaxRxException(ttExcep);
557             } catch (final IOException ioExcep) {
558                 throw new JaxRxException(ioExcep);
559             } catch (final Exception globExcep) {
560                 if (globExcep instanceof JaxRxException) { // NOPMD due
561                     // to
562                     // different
563                     // exception
564                     // types
565                     throw (JaxRxException)globExcep;
566                 } else {
567                     throw new JaxRxException(globExcep);
568                 }
569             } finally {
570                 try {
571                     WorkerHelper.closeRTX(rtx, session);
572                 } catch (final TTException exce) {
573                     throw new JaxRxException(exce);
574                 }
575             }
576 
577         } else {
578             throw new JaxRxException(404, "Resource does not exist");
579         }
580 
581     }
582 }