/* * See the NOTICE file distributed with this work for additional * information regarding copyright ownership. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.xwiki.annotation.rest.internal; import java.io.PrintWriter; import java.io.StringWriter; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Level; import javax.inject.Inject; import org.xwiki.annotation.Annotation; import org.xwiki.annotation.AnnotationService; import org.xwiki.annotation.AnnotationServiceException; import org.xwiki.annotation.rest.model.jaxb.AnnotatedContent; import org.xwiki.annotation.rest.model.jaxb.AnnotationField; import org.xwiki.annotation.rest.model.jaxb.AnnotationRequest; import org.xwiki.annotation.rest.model.jaxb.AnnotationResponse; import org.xwiki.annotation.rest.model.jaxb.AnnotationStub; import org.xwiki.annotation.rest.model.jaxb.ObjectFactory; import org.xwiki.annotation.rights.AnnotationRightService; import org.xwiki.context.Execution; import org.xwiki.model.reference.DocumentReference; import org.xwiki.rest.XWikiResource; import com.xpn.xwiki.XWiki; import com.xpn.xwiki.XWikiContext; import com.xpn.xwiki.XWikiException; import com.xpn.xwiki.doc.XWikiDocument; import com.xpn.xwiki.web.XWikiURLFactory; /** * Base class for the annotation REST services, to implement common functionality to all annotation REST services. * * @version $Id: 93d54b22ad00436644338a329acff923dda99fc6 $ * @since 2.3M1 */ public abstract class AbstractAnnotationRESTResource extends XWikiResource { /** * The default action to render the document for. <br> * TODO: action should be obtained from the calling client in the parameters */ protected static final String DEFAULT_ACTION = "view"; /** * The annotations service to be used by this REST interface. */ @Inject protected AnnotationService annotationService; /** * The annotations rights checking service, to check user rights to perform annotations actions. */ @Inject protected AnnotationRightService annotationRightService; /** * The execution needed to get the annotation author from the context user. */ @Inject protected Execution execution; /** * Builds an annotation response containing the annotated content along with the annotation stubs, according to the * requirements in the passed annotations request. * * @param request the annotations request * @param documentName the name of the document to provide an annotated response for * @return an annotation response with the annotated content and the annotation stubs * @throws AnnotationServiceException in case something goes wrong handling the annotations * @throws XWikiException in case something goes wrong manipulating the xwiki context & documents */ protected AnnotationResponse getSuccessResponseWithAnnotatedContent(String documentName, AnnotationRequest request) throws AnnotationServiceException, XWikiException { ObjectFactory factory = new ObjectFactory(); AnnotationResponse response = factory.createAnnotationResponse(); // get the annotations on this content Collection<Annotation> annotations = annotationService.getAnnotations(documentName); // filter them according to the request Collection<Annotation> filteredAnnotations = filterAnnotations(annotations, request); // render the document with the filtered annotations on it String renderedHTML = renderDocumentWithAnnotations(documentName, null, DEFAULT_ACTION, filteredAnnotations); // prepare the annotated content AnnotatedContent annotatedContentResponse = factory.createAnnotatedContent(); annotatedContentResponse.getAnnotations().addAll( prepareAnnotationStubsSet(filteredAnnotations, request.getRequest().getFields())); annotatedContentResponse.setContent(renderedHTML); // set the annotated content along with the return code in the response and return it response.setAnnotatedContent(annotatedContentResponse); response.setResponseCode(0); return response; } /** * Helper function to translate a collection of annotations from the {@link Annotation} model to the JAXB model to * be serialized for REST communication. * * @param annotations the annotations collection to be translated * @param requestedFields the extra parameters that should be set for the prepared annotations * @return translate set of org.xwiki.annotation.internal.annotation.Annotation to set of * org.xwiki.annotation.internal.annotation.Annotation */ private Collection<AnnotationStub> prepareAnnotationStubsSet(Collection<Annotation> annotations, List<String> requestedFields) { ObjectFactory factory = new ObjectFactory(); List<AnnotationStub> set = new ArrayList<AnnotationStub>(); for (Annotation xwikiAnnotation : annotations) { AnnotationStub annotation = factory.createAnnotationStub(); annotation.setAnnotationId(xwikiAnnotation.getId()); annotation.setState(xwikiAnnotation.getState().toString()); // for all the requested extra fields, get them from the annotation and send them for (String extraField : requestedFields) { Object value = xwikiAnnotation.get(extraField); AnnotationField field = new AnnotationField(); field.setName(extraField); // value.toString() by default, null if value is missing field.setValue(value != null ? value.toString() : null); annotation.getFields().add(field); } set.add(annotation); } return set; } /** * Helper function to create an error response from a passed exception. <br> * * @param exception the exception that was encountered during regular execution of service * @return an error response */ protected AnnotationResponse getErrorResponse(Throwable exception) { AnnotationResponse result = new ObjectFactory().createAnnotationResponse(); result.setResponseCode(1); String responseMessage = exception.getMessage(); if (responseMessage == null) { // serialize the stack trace and send it as an error response StringWriter stackTraceWriter = new StringWriter(); exception.printStackTrace(new PrintWriter(stackTraceWriter)); responseMessage = stackTraceWriter.toString(); } result.setResponseMessage(responseMessage); result.setAnnotatedContent(null); return result; } /** * Helper function to get the rendered content of the document with annotations. All setup of context for rendering * content similar to the rendering on standard view will be done in this function. <br> * FIXME: find out if this whole context setup code has to be here or in the annotations service * * @param docName the name of the document to render * @param language the language in which to render the document * @param action the context action to render the document for * @param annotations the annotations to render on the document * @return the HTML rendered content of the document * @throws XWikiException if anything wrong happens while setting up the context for rendering * @throws AnnotationServiceException if anything goes wrong during the rendering of the annotations */ private String renderDocumentWithAnnotations(String docName, String language, String action, Collection<Annotation> annotations) throws XWikiException, AnnotationServiceException { String isInRenderingEngineKey = "isInRenderingEngine"; XWikiContext context = this.xcontextProvider.get(); Object isInRenderingEngine = context.get(isInRenderingEngineKey); // set the context url factory to the servlet url factory so that all links get correctly generated as if we // were view-ing the page XWikiURLFactory oldFactory = context.getURLFactory(); int oldMode = context.getMode(); String result = null; try { context.setMode(XWikiContext.MODE_SERVLET); XWikiURLFactory urlf = context.getWiki().getURLFactoryService().createURLFactory(context.getMode(), context); context.setURLFactory(urlf); // setup documents on the context, and velocity context, and message tool for i18n and all setUpDocuments(docName, language); // set the current action on the context context.setAction(action); context.put(isInRenderingEngineKey, true); // render the content in xhtml syntax, with the passed list of annotations result = annotationService.getAnnotatedRenderedContent(docName, null, "xhtml/1.0", annotations); } finally { if (isInRenderingEngine != null) { context.put(isInRenderingEngineKey, isInRenderingEngine); } else { context.remove(isInRenderingEngineKey); } context.setURLFactory(oldFactory); context.setMode(oldMode); } return result; } /** * Helper function to prepare the XWiki documents and translations on the context and velocity context. <br> * TODO: check how this code could be written only once (not duplicate the prepareDocuments function in XWiki) * * @param docName the full name of the document to prepare context for * @param language the language of the document * @throws XWikiException if anything goes wrong accessing documents */ private void setUpDocuments(String docName, String language) throws XWikiException { XWikiContext context = xcontextProvider.get(); XWiki xwiki = context.getWiki(); // prepare the messaging tools and set them on context xwiki.prepareResources(context); XWikiDocument doc = xwiki.getDocument(docName, context); // setup the xwiki context context.put("doc", doc); context.put("cdoc", doc); XWikiDocument tdoc = doc.getTranslatedDocument(language, context); context.put("tdoc", tdoc); // and render the xwikivars to have all the variables set ($has*, $blacklistedSpaces, etc) context.getWiki().renderTemplate("xwikivars.vm", context); } /** * Helper method to filter a set of annotations according to the criteria in the passed annotation request. The * fields in the filter of the request will be interpreted as a filter for equality with the value in the actual * annotation, and all the fields conditions will be put together with an "or" operation. * * @param annotations the collection of annotations to filter * @param request the request according which to filter * @return the filtered collection of annotations */ protected Collection<Annotation> filterAnnotations(Collection<Annotation> annotations, AnnotationRequest request) { Collection<Annotation> result = new ArrayList<Annotation>(); Map<String, List<String>> filters = new HashMap<String, List<String>>(); for (AnnotationField annotationField : request.getFilter().getFields()) { String filterName = annotationField.getName(); List<String> values = filters.get(filterName); if (values == null) { values = new ArrayList<String>(); filters.put(filterName, values); } if (annotationField.getValue() != null) { values.add(annotationField.getValue()); } } if (filters.size() == 0) { return annotations; } for (Annotation ann : annotations) { boolean matches = true; for (Map.Entry<String, List<String>> filter : filters.entrySet()) { Object annotationValue = ann.get(filter.getKey()); // if the values is not set or is not among the requested values, if (annotationValue == null || !filter.getValue().contains(annotationValue.toString())) { // it doesn't match and exit matches = false; break; } } // if it matches in the end, add it to the results if (matches) { result.add(ann); } } return result; } /** * @return the xwiki user in the context. */ protected String getXWikiUser() { return this.xcontextProvider.get().getUser(); } /** * Helper method to make sure that the context is set to the right document and database name. * * @param wiki the REST wikiName path parameter * @param space the REST spaceName path parameter * @param page the REST pageName path parameter */ protected void updateContext(DocumentReference documentReference) { try { // Set the database to the current wiki. XWikiContext deprecatedContext = (XWikiContext) execution.getContext().getProperty("xwikicontext"); deprecatedContext.setWikiId(documentReference.getWikiReference().getName()); // Set the document to the current document. XWiki xwiki = deprecatedContext.getWiki(); XWikiDocument currentDocument = xwiki.getDocument(documentReference, deprecatedContext); deprecatedContext.setDoc(currentDocument); } catch (Exception e) { // Just log it. getLogger().error("Failed to update the context for page [{}].", documentReference, e); } } }