@Override
  public void copyDocuments(String fromWikiId, String toWikiId, boolean withHistory)
      throws WikiManagerException {
    XWikiContext context = xcontextProvider.get();
    XWiki xwiki = context.getWiki();

    try {
      Query query =
          queryManager.createQuery("select distinct doc.fullName from Document as doc", Query.XWQL);
      query.setWiki(fromWikiId);
      List<String> documentFullnames = query.execute();

      observationManager.notify(new PushLevelProgressEvent(documentFullnames.size()), this);

      WikiReference fromWikiReference = new WikiReference(fromWikiId);
      for (String documentFullName : documentFullnames) {
        DocumentReference origDocReference =
            documentReferenceResolver.resolve(documentFullName, fromWikiReference);
        DocumentReference newDocReference =
            new DocumentReference(
                toWikiId,
                origDocReference.getLastSpaceReference().getName(),
                origDocReference.getName());
        xwiki.copyDocument(origDocReference, newDocReference, null, !withHistory, true, context);

        observationManager.notify(new StepProgressEvent(), this);
      }

      observationManager.notify(new PopLevelProgressEvent(), this);
    } catch (QueryException e) {
      throw new WikiManagerException("Unable to get the source wiki documents.", e);
    } catch (XWikiException e) {
      throw new WikiManagerException("Failed to copy document.", e);
    }
  }
Esempio n. 2
0
  public void init(IndexUpdater indexUpdater, IndexRebuilder indexRebuilder, XWikiContext context) {
    super.init(context);

    try {
      @SuppressWarnings("unchecked")
      Class<? extends Analyzer> clazz =
          (Class<? extends Analyzer>)
              Class.forName(context.getWiki().Param(PROP_ANALYZER, DEFAULT_ANALYZER));
      this.analyzer = clazz.getConstructor(Version.class).newInstance(Version.LUCENE_36);
    } catch (Exception e) {
      LOGGER.error("Error instantiating analyzer: {}", e.getMessage());
      LOGGER.warn("Using default analyzer class: " + DEFAULT_ANALYZER);
      try {
        @SuppressWarnings("unchecked")
        Class<? extends Analyzer> clazz =
            (Class<? extends Analyzer>) Class.forName(DEFAULT_ANALYZER);
        this.analyzer = clazz.getConstructor(Version.class).newInstance(Version.LUCENE_36);
      } catch (Exception e1) {
        throw new RuntimeException(
            "Instantiation of default analyzer " + DEFAULT_ANALYZER + " failed", e1);
      }
    }
    Map<String, Analyzer> specialAnalyzers = new HashMap<String, Analyzer>();
    Analyzer preserve = new KeywordAnalyzer();
    specialAnalyzers.put(IndexFields.DOCUMENT_EXACTSPACE, preserve);
    specialAnalyzers.put(IndexFields.DOCUMENT_WIKI, preserve);
    specialAnalyzers.put(IndexFields.DOCUMENT_TYPE, preserve);
    specialAnalyzers.put(IndexFields.DOCUMENT_LANGUAGE, preserve);
    this.analyzer = new PerFieldAnalyzerWrapper(this.analyzer, specialAnalyzers);

    LOGGER.debug("Assigning index updater: {}", indexUpdater);

    if (this.indexDirs == null) {
      this.indexDirs = context.getWiki().Param(PROP_INDEX_DIR);
      if (StringUtils.isEmpty(this.indexDirs)) {
        File workDir = getLuceneWorkDirectory();
        this.indexDirs = workDir.getAbsolutePath();
      }
    }

    this.indexUpdater = indexUpdater;
    this.indexUpdater.setAnalyzer(this.analyzer);
    this.indexUpdaterThread = new Thread(indexUpdater, "Lucene Index Updater");
    this.indexUpdaterThread.start();
    this.indexRebuilder = indexRebuilder;

    openIndexReaders(context);

    // Register the Index Updater as an Event Listener so that modified documents/attachments are
    // added to the
    // Lucene indexing queue.
    // If the Index Updater is already registered don't do anything.
    ObservationManager observationManager = Utils.getComponent(ObservationManager.class);
    if (observationManager.getListener(indexUpdater.getName()) == null) {
      observationManager.addListener(indexUpdater);
    }

    LOGGER.debug("Lucene plugin initialized.");
  }
Esempio n. 3
0
 /**
  * {@inheritDoc} <br>
  * Implementation which gets all the annotation class objects in the document pointed by the
  * target, and matches their ids against the ids in the passed collection of annotations. If they
  * match, they are updated with the new data in the annotations in annotation.
  *
  * @see org.xwiki.annotation.io.IOService#updateAnnotations(String, java.util.Collection)
  */
 @Override
 public void updateAnnotations(String target, Collection<Annotation> annotations)
     throws IOServiceException {
   try {
     EntityReference targetReference = referenceResolver.resolve(target, EntityType.DOCUMENT);
     // get the document name from the parsed reference
     String docName = target;
     if (targetReference.getType() == EntityType.DOCUMENT
         || targetReference.getType() == EntityType.OBJECT_PROPERTY) {
       docName = serializer.serialize(targetReference.extractReference(EntityType.DOCUMENT));
     }
     // get the document pointed to by the target
     XWikiContext deprecatedContext = getXWikiContext();
     XWikiDocument document = deprecatedContext.getWiki().getDocument(docName, deprecatedContext);
     List<String> updateNotifs = new ArrayList<String>();
     boolean updated = false;
     for (Annotation annotation : annotations) {
       // parse annotation id as string. If cannot parse, then ignore annotation, is not valid
       int annId = 0;
       try {
         annId = Integer.parseInt(annotation.getId());
       } catch (NumberFormatException e) {
         continue;
       }
       BaseObject object = document.getXObject(configuration.getAnnotationClassReference(), annId);
       if (object == null) {
         continue;
       }
       updated = updateObject(object, annotation, deprecatedContext) || updated;
       updateNotifs.add(annotation.getId());
     }
     if (updated) {
       // set the author of the document to the current user
       document.setAuthor(deprecatedContext.getUser());
       deprecatedContext
           .getWiki()
           .saveDocument(document, "Updated annotations", deprecatedContext);
       // send annotation update notifications for all annotations set to notify for
       ObservationManager observationManager = componentManager.lookup(ObservationManager.class);
       for (String updateNotif : updateNotifs) {
         observationManager.notify(
             new AnnotationUpdatedEvent(docName, updateNotif), document, deprecatedContext);
       }
     }
   } catch (XWikiException e) {
     throw new IOServiceException("An exception has occurred while updating the annotation", e);
   } catch (ComponentLookupException exc) {
     this.logger.warn(
         "Could not get the observation manager to send notifications about the annotation update");
   }
 }
Esempio n. 4
0
  /**
   * {@inheritDoc} <br>
   * This implementation deletes the annotation object with the object number indicated by {@code
   * annotationID} from the document indicated by {@code target}, if its stored target matches the
   * passed target.
   *
   * @see org.xwiki.annotation.io.IOService#removeAnnotation(String, String)
   */
  @Override
  public void removeAnnotation(String target, String annotationID) throws IOServiceException {
    try {
      if (annotationID == null || target == null) {
        return;
      }

      EntityReference targetReference = referenceResolver.resolve(target, EntityType.DOCUMENT);
      // get the target identifier and the document name from the parsed reference
      String localTargetId = target;
      String docName = target;
      if (targetReference.getType() == EntityType.DOCUMENT
          || targetReference.getType() == EntityType.OBJECT_PROPERTY) {
        localTargetId = localSerializer.serialize(targetReference);
        docName = serializer.serialize(targetReference.extractReference(EntityType.DOCUMENT));
      }
      // get the document
      XWikiContext deprecatedContext = getXWikiContext();
      XWikiDocument document = deprecatedContext.getWiki().getDocument(docName, deprecatedContext);
      if (document.isNew()) {
        // if the document doesn't exist already skip it
        return;
      }
      // and the document object on it
      BaseObject annotationObject =
          document.getXObject(
              configuration.getAnnotationClassReference(),
              Integer.valueOf(annotationID.toString()));

      // if object exists and its target matches the requested target, delete it
      if (annotationObject != null
          && localTargetId.equals(annotationObject.getStringValue(Annotation.TARGET_FIELD))) {
        document.removeObject(annotationObject);
        document.setAuthor(deprecatedContext.getUser());
        deprecatedContext
            .getWiki()
            .saveDocument(document, "Deleted annotation " + annotationID, deprecatedContext);
        // notify listeners that an annotation was deleted
        ObservationManager observationManager = componentManager.lookup(ObservationManager.class);
        observationManager.notify(
            new AnnotationDeletedEvent(docName, annotationID), document, deprecatedContext);
      }
    } catch (NumberFormatException e) {
      throw new IOServiceException("An exception has occurred while parsing the annotation id", e);
    } catch (XWikiException e) {
      throw new IOServiceException("An exception has occurred while removing the annotation", e);
    } catch (ComponentLookupException exc) {
      this.logger.warn(
          "Could not get the observation manager to send notifications about the annotation delete");
    }
  }
  @Test
  public void testOnEvent_update_ed() {
    Event event = new DocumentUpdatedEvent();

    expect(remoteObsManContextMock.isRemoteState()).andReturn(false).once();
    expect(docMock.getXObject(eq(classRef))).andReturn(getBaseObject1()).once();
    expect(origDocMock.getXObject(eq(classRef))).andReturn(getBaseObject2()).once();
    obsManagerMock.notify(same(updatedEventMock), same(docMock), same(context));
    expectLastCall().once();

    replayDefault();
    listener.onEvent(event, docMock, context);
    verifyDefault();
  }
Esempio n. 6
0
  public ActionForward execute(XWikiContext context) throws Exception {
    MonitorPlugin monitor = null;
    FileUploadPlugin fileupload = null;
    String docName = "";

    try {
      String action = context.getAction();

      // Initialize context.getWiki() with the main wiki
      XWiki xwiki;

      // Verify that the requested wiki exists
      try {
        xwiki = XWiki.getXWiki(this.waitForXWikiInitialization, context);

        // If XWiki is still initializing display initialization template
        if (xwiki == null) {
          // Display initialization template
          renderInit(context);

          // Initialization template has been displayed, stop here.
          return null;
        }
      } catch (XWikiException e) {
        // If the wiki asked by the user doesn't exist, then we first attempt to use any existing
        // global
        // redirects. If there are none, then we display the specific error template.
        if (e.getCode() == XWikiException.ERROR_XWIKI_DOES_NOT_EXIST) {
          xwiki = XWiki.getMainXWiki(context);

          // Initialize the url factory
          XWikiURLFactory urlf =
              xwiki.getURLFactoryService().createURLFactory(context.getMode(), context);
          context.setURLFactory(urlf);

          // Initialize the velocity context and its bindings so that it may be used in the velocity
          // templates
          // that we
          // are parsing below.
          VelocityManager velocityManager = Utils.getComponent(VelocityManager.class);
          VelocityContext vcontext = velocityManager.getVelocityContext();

          if (!sendGlobalRedirect(context.getResponse(), context.getURL().toString(), context)) {
            // Starting XWiki 5.0M2, 'xwiki.virtual.redirect' was removed. Warn users still using
            // it.
            if (!StringUtils.isEmpty(context.getWiki().Param("xwiki.virtual.redirect"))) {
              LOGGER.warn(
                  String.format(
                      "%s %s",
                      "'xwiki.virtual.redirect' is no longer supported.",
                      "Please update your configuration and/or see XWIKI-8914 for more details."));
            }

            // Display the error template only for actions that are not ignored
            if (!ACTIONS_IGNORED_WHEN_WIKI_DOES_NOT_EXIST.contains(action)) {

              // Add localization resources to the context
              xwiki.prepareResources(context);

              // Set the main home page in the main space of the main wiki as the current requested
              // entity
              // since we cannot set the non existing one as it would generate errors obviously...
              EntityReferenceValueProvider valueProvider =
                  Utils.getComponent(EntityReferenceValueProvider.class);
              xwiki.setPhonyDocument(
                  new DocumentReference(
                      valueProvider.getDefaultValue(EntityType.WIKI),
                      valueProvider.getDefaultValue(EntityType.SPACE),
                      valueProvider.getDefaultValue(EntityType.DOCUMENT)),
                  context,
                  vcontext);

              // Parse the error template
              Utils.parseTemplate(
                  context.getWiki().Param("xwiki.wiki_exception", "wikidoesnotexist"), context);

              // Error template was displayed, stop here.
              return null;
            }

            // At this point, we allow regular execution of the ignored action because even if the
            // wiki
            // does not exist, we still need to allow UI resources to be retrieved (from the
            // filesystem
            // and the main wiki) or our error template will not be rendered properly.

            // Proceed with serving the main wiki

          } else {
            // Global redirect was executed, stop here.
            return null;
          }
        } else {
          LOGGER.error("Uncaught exception during XWiki initialisation:", e);
          throw e;
        }
      }

      // Send global redirection (if any)
      if (sendGlobalRedirect(context.getResponse(), context.getURL().toString(), context)) {
        return null;
      }

      XWikiURLFactory urlf =
          xwiki.getURLFactoryService().createURLFactory(context.getMode(), context);
      context.setURLFactory(urlf);

      String sajax = context.getRequest().get("ajax");
      boolean ajax = false;
      if (sajax != null && !sajax.trim().equals("") && !sajax.equals("0")) {
        ajax = true;
      }
      context.put("ajax", ajax);

      // Any error before this will be treated using a redirection to an error page

      if (monitor != null) {
        monitor.startTimer("request");
      }

      VelocityManager velocityManager = Utils.getComponent(VelocityManager.class);
      VelocityContext vcontext = velocityManager.getVelocityContext();

      boolean eventSent = false;
      try {
        // Prepare documents and put them in the context
        if (!xwiki.prepareDocuments(context.getRequest(), context, vcontext)) {
          return null;
        }

        // Start monitoring timer
        monitor = (MonitorPlugin) xwiki.getPlugin("monitor", context);
        if (monitor != null) {
          monitor.startRequest("", context.getAction(), context.getURL());
          monitor.startTimer("multipart");
        }

        // Parses multipart so that params in multipart are available for all actions
        fileupload = Utils.handleMultipart(context.getRequest().getHttpServletRequest(), context);
        if (monitor != null) {
          monitor.endTimer("multipart");
        }

        if (monitor != null) {
          monitor.setWikiPage(context.getDoc().getFullName());
        }

        // Let's handle the notification and make sure it never fails
        if (monitor != null) {
          monitor.startTimer("prenotify");
        }

        // For the moment we're sending the XWiki context as the data, but this will be
        // changed in the future, when the whole platform will be written using components
        // and there won't be a need for the context.
        try {
          ObservationManager om = Utils.getComponent(ObservationManager.class);
          ActionExecutingEvent event = new ActionExecutingEvent(context.getAction());
          om.notify(event, context.getDoc(), context);
          eventSent = true;
          if (event.isCanceled()) {
            // Action has been canceled
            // TODO: do something special ?
            return null;
          }
        } catch (Throwable ex) {
          LOGGER.error(
              "Cannot send action notifications for document ["
                  + context.getDoc()
                  + " using action ["
                  + context.getAction()
                  + "]",
              ex);
        }

        if (monitor != null) {
          monitor.endTimer("prenotify");
        }

        // Call the Actions

        // Call the new Entity Resource Reference Handler.
        ResourceReferenceHandler entityResourceReferenceHandler =
            Utils.getComponent(
                new DefaultParameterizedType(
                    null, ResourceReferenceHandler.class, ResourceType.class),
                "bin");
        ResourceReference resourceReference =
            Utils.getComponent(ResourceReferenceManager.class).getResourceReference();
        try {
          entityResourceReferenceHandler.handle(
              resourceReference,
              new DefaultResourceReferenceHandlerChain(
                  Collections.<ResourceReferenceHandler>emptyList()));
          // Don't let the old actions kick in!
          return null;
        } catch (NotFoundResourceHandlerException e) {
          // No Entity Resource Action has been found. Don't do anything and let it go through
          // so that the old Action system kicks in...
        } catch (Throwable e) {
          // Some real failure, log it since it's a problem but still allow the old Action system a
          // chance
          // to do something...
          LOGGER.error("Failed to handle Action for Resource [{}]", resourceReference, e);
        }

        // Then call the old Actions for backward compatibility (and because a lot of them have not
        // been
        // migrated to new Actions yet).
        String renderResult = null;
        XWikiDocument doc = context.getDoc();
        docName = doc.getFullName();
        if (action(context)) {
          renderResult = render(context);
        }

        if (renderResult != null) {
          if (doc.isNew()
              && "view".equals(context.getAction())
              && !"recyclebin".equals(context.getRequest().get("viewer"))) {
            String page = Utils.getPage(context.getRequest(), "docdoesnotexist");
            Utils.parseTemplate(page, context);
          } else {
            String page = Utils.getPage(context.getRequest(), renderResult);
            Utils.parseTemplate(page, !page.equals("direct"), context);
          }
        }
        return null;
      } catch (Throwable e) {
        if (e instanceof IOException) {
          e =
              new XWikiException(
                  XWikiException.MODULE_XWIKI_APP,
                  XWikiException.ERROR_XWIKI_APP_SEND_RESPONSE_EXCEPTION,
                  "Exception while sending response",
                  e);
        }

        if (!(e instanceof XWikiException)) {
          e =
              new XWikiException(
                  XWikiException.MODULE_XWIKI_APP,
                  XWikiException.ERROR_XWIKI_UNKNOWN,
                  "Uncaught exception",
                  e);
        }

        try {
          XWikiException xex = (XWikiException) e;
          if (xex.getCode() == XWikiException.ERROR_XWIKI_APP_SEND_RESPONSE_EXCEPTION) {
            // Connection aborted from the client side, there's not much we can do on the server
            // side. We
            // simply ignore it.
            LOGGER.debug("Connection aborted", e);
            // We don't write any other message to the response, as the connection is broken,
            // anyway.
            return null;
          } else if (xex.getCode() == XWikiException.ERROR_XWIKI_ACCESS_DENIED) {
            Utils.parseTemplate(
                context.getWiki().Param("xwiki.access_exception", "accessdenied"), context);
            return null;
          } else if (xex.getCode() == XWikiException.ERROR_XWIKI_USER_INACTIVE) {
            Utils.parseTemplate(
                context.getWiki().Param("xwiki.user_exception", "userinactive"), context);
            return null;
          } else if (xex.getCode() == XWikiException.ERROR_XWIKI_APP_ATTACHMENT_NOT_FOUND) {
            context.put("message", "attachmentdoesnotexist");
            Utils.parseTemplate(
                context.getWiki().Param("xwiki.attachment_exception", "attachmentdoesnotexist"),
                context);
            return null;
          } else if (xex.getCode() == XWikiException.ERROR_XWIKI_APP_URL_EXCEPTION) {
            vcontext.put("message", context.getMessageTool().get("platform.core.invalidUrl"));
            xwiki.setPhonyDocument(
                xwiki.getDefaultSpace(context) + "." + xwiki.getDefaultPage(context),
                context,
                vcontext);
            context.getResponse().setStatus(HttpServletResponse.SC_BAD_REQUEST);
            Utils.parseTemplate(
                context.getWiki().Param("xwiki.invalid_url_exception", "error"), context);
            return null;
          }
          vcontext.put("exp", e);
          if (LOGGER.isWarnEnabled()) {
            // Don't log "Broken Pipe" exceptions since they're not real errors and we don't want to
            // pollute
            // the logs with unnecessary stack traces. It just means the client side has cancelled
            // the
            // connection.
            if (ExceptionUtils.getRootCauseMessage(e).equals("IOException: Broken pipe")) {
              return null;
            }
            LOGGER.warn("Uncaught exception: " + e.getMessage(), e);
          }
          // If the request is an AJAX request, we don't return a whole HTML page, but just the
          // exception
          // inline.
          String exceptionTemplate = ajax ? "exceptioninline" : "exception";
          Utils.parseTemplate(Utils.getPage(context.getRequest(), exceptionTemplate), context);
          return null;
        } catch (XWikiException ex) {
          if (ex.getCode() == XWikiException.ERROR_XWIKI_APP_SEND_RESPONSE_EXCEPTION) {
            LOGGER.error("Connection aborted");
          }
        } catch (Exception e2) {
          // I hope this never happens
          LOGGER.error("Uncaught exceptions (inner): ", e);
          LOGGER.error("Uncaught exceptions (outer): ", e2);
        }
        return null;
      } finally {
        // Let's make sure we have flushed content and closed
        try {
          context.getResponse().getWriter().flush();
        } catch (Throwable e) {
          // This might happen if the connection was closed, for example.
          // If we can't flush, then there's nothing more we can send to the client.
        }

        if (monitor != null) {
          monitor.endTimer("request");
          monitor.startTimer("notify");
        }

        if (eventSent) {
          // For the moment we're sending the XWiki context as the data, but this will be
          // changed in the future, when the whole platform will be written using components
          // and there won't be a need for the context.
          try {
            ObservationManager om = Utils.getComponent(ObservationManager.class);
            om.notify(new ActionExecutedEvent(context.getAction()), context.getDoc(), context);
          } catch (Throwable ex) {
            LOGGER.error(
                "Cannot send action notifications for document ["
                    + docName
                    + " using action ["
                    + context.getAction()
                    + "]",
                ex);
          }
        }

        if (monitor != null) {
          monitor.endTimer("notify");
        }

        // Make sure we cleanup database connections
        // There could be cases where we have some
        if ((context != null) && (xwiki != null)) {
          xwiki.getStore().cleanUp(context);
        }
      }
    } finally {
      // End request
      if (monitor != null) {
        monitor.endRequest();
      }

      if (context != null) {

        if (fileupload != null) {
          fileupload.cleanFileList(context);
        }
      }
    }
  }
Esempio n. 7
0
  public int install(XWikiContext context) throws XWikiException {
    boolean isAdmin = context.getWiki().getRightService().hasWikiAdminRights(context);

    if (testInstall(isAdmin, context) == DocumentInfo.INSTALL_IMPOSSIBLE) {
      setStatus(DocumentInfo.INSTALL_IMPOSSIBLE, context);
      return DocumentInfo.INSTALL_IMPOSSIBLE;
    }

    boolean hasCustomMappings = false;
    for (DocumentInfo docinfo : this.customMappingFiles) {
      BaseClass bclass = docinfo.getDoc().getXClass();
      hasCustomMappings |= context.getWiki().getStore().injectCustomMapping(bclass, context);
    }

    if (hasCustomMappings) {
      context.getWiki().getStore().injectUpdatedCustomMappings(context);
    }

    int status = DocumentInfo.INSTALL_OK;

    // Determine if the user performing the installation is a farm admin.
    // We allow author preservation from the package only to farm admins.
    // In order to prevent sub-wiki admins to take control of a farm with forged packages.
    // We test it once for the whole import in case one of the document break user during the import
    // process.
    boolean backup = this.backupPack && isFarmAdmin(context);

    // Notify all the listeners about import
    ObservationManager om = Utils.getComponent(ObservationManager.class);

    // FIXME: should be able to pass some sort of source here, the name of the attachment or the
    // list of
    // imported documents. But for the moment it's fine
    om.notify(new XARImportingEvent(), null, context);

    try {
      // Start by installing all documents having a class definition so that their
      // definitions are available when installing documents using them.
      for (DocumentInfo classFile : this.classFiles) {
        if (installDocument(classFile, isAdmin, backup, context) == DocumentInfo.INSTALL_ERROR) {
          status = DocumentInfo.INSTALL_ERROR;
        }
      }

      // Install the remaining documents (without class definitions).
      for (DocumentInfo docInfo : this.files) {
        if (!this.classFiles.contains(docInfo)) {
          if (installDocument(docInfo, isAdmin, backup, context) == DocumentInfo.INSTALL_ERROR) {
            status = DocumentInfo.INSTALL_ERROR;
          }
        }
      }
      setStatus(status, context);

    } finally {
      // FIXME: should be able to pass some sort of source here, the name of the attachment or the
      // list of
      // imported documents. But for the moment it's fine
      om.notify(new XARImportedEvent(), null, context);

      registerExtension(context);
    }

    return status;
  }
Esempio n. 8
0
  /**
   * {@inheritDoc} <br>
   * This implementation saves the added annotation in the document where the target of the
   * annotation is.
   *
   * @see org.xwiki.annotation.io.IOService#addAnnotation(String, org.xwiki.annotation.Annotation)
   */
  @Override
  public void addAnnotation(String target, Annotation annotation) throws IOServiceException {
    try {
      // extract the document name from the passed target
      // by default the fullname is the passed target
      String documentFullName = target;
      EntityReference targetReference = referenceResolver.resolve(target, EntityType.DOCUMENT);
      // try to get a document reference from the passed target reference
      EntityReference docRef = targetReference.extractReference(EntityType.DOCUMENT);
      if (docRef != null) {
        documentFullName = serializer.serialize(docRef);
      }
      // now get the document with that name
      XWikiContext deprecatedContext = getXWikiContext();
      XWikiDocument document =
          deprecatedContext.getWiki().getDocument(documentFullName, deprecatedContext);
      // create a new object in this document to hold the annotation
      int id =
          document.createXObject(configuration.getAnnotationClassReference(), deprecatedContext);
      BaseObject object = document.getXObject(configuration.getAnnotationClassReference(), id);
      updateObject(object, annotation, deprecatedContext);
      // and set additional data: author to annotation author, date to now and the annotation target
      object.set(Annotation.DATE_FIELD, new Date(), deprecatedContext);
      // TODO: maybe we shouldn't trust what we receive from the caller but set the author from the
      // context.
      // Or the other way around, set the author of the document from the annotations author.
      object.set(Annotation.AUTHOR_FIELD, annotation.getAuthor(), deprecatedContext);
      // store the target of this annotation, serialized with a local serializer, to be exportable
      // and importable
      // in a different wiki
      // TODO: figure out if this is the best idea in terms of target serialization
      // 1/ the good part is that it is a fixed value that can be searched with a query in all
      // objects in the wiki
      // 2/ the bad part is that copying a document to another space will not also update its
      // annotation targets
      // 3/ if annotations are stored in the same document they annotate, the targets are only
      // required for object
      // fields
      // ftm don't store the type of the reference since we only need to recognize the field, not to
      // also read it.
      if (targetReference.getType() == EntityType.OBJECT_PROPERTY
          || targetReference.getType() == EntityType.DOCUMENT) {
        object.set(
            Annotation.TARGET_FIELD, localSerializer.serialize(targetReference), deprecatedContext);
      } else {
        object.set(Annotation.TARGET_FIELD, target, deprecatedContext);
      }
      // set the author of the document to the current user
      document.setAuthor(deprecatedContext.getUser());
      deprecatedContext
          .getWiki()
          .saveDocument(
              document,
              "Added annotation on \"" + annotation.getSelection() + "\"",
              deprecatedContext);

      // notify listeners that an annotation was added
      ObservationManager observationManager = componentManager.lookup(ObservationManager.class);
      observationManager.notify(
          new AnnotationAddedEvent(documentFullName, object.getNumber() + ""),
          document,
          deprecatedContext);

    } catch (XWikiException e) {
      throw new IOServiceException(
          "An exception message has occurred while saving the annotation", e);
    } catch (ComponentLookupException exc) {
      this.logger.warn(
          "Could not get the observation manager to send notifications about the annotation add");
    }
  }