Package plugins.Freetalk.WoT

Source Code of plugins.Freetalk.WoT.WoTMessageManager

/* This code is part of Freenet. It is distributed under the GNU General
* Public License, version 2 (or at your option any later version). See
* http://www.gnu.org/ for further details of the GPL. */
package plugins.Freetalk.WoT;

import java.util.Date;
import java.util.List;
import java.util.Set;

import plugins.Freetalk.Board;
import plugins.Freetalk.FetchFailedMarker;
import plugins.Freetalk.Freetalk;
import plugins.Freetalk.Identity;
import plugins.Freetalk.IdentityManager;
import plugins.Freetalk.IdentityStatistics;
import plugins.Freetalk.Message;
import plugins.Freetalk.Message.Attachment;
import plugins.Freetalk.MessageList;
import plugins.Freetalk.MessageList.MessageListID;
import plugins.Freetalk.MessageManager;
import plugins.Freetalk.MessageRating;
import plugins.Freetalk.MessageURI;
import plugins.Freetalk.OwnIdentity;
import plugins.Freetalk.OwnMessageList;
import plugins.Freetalk.Persistent;
import plugins.Freetalk.Persistent.InitializingObjectSet;
import plugins.Freetalk.exceptions.DuplicateElementException;
import plugins.Freetalk.exceptions.NoSuchFetchFailedMarkerException;
import plugins.Freetalk.exceptions.NoSuchMessageException;
import plugins.Freetalk.exceptions.NoSuchMessageListException;
import plugins.Freetalk.exceptions.NoSuchMessageRatingException;
import plugins.Freetalk.exceptions.NoSuchObjectException;
import plugins.Freetalk.tasks.PersistentTaskManager;

import com.db4o.ObjectContainer;
import com.db4o.ObjectSet;
import com.db4o.ext.ExtObjectContainer;
import com.db4o.query.Query;

import freenet.keys.FreenetURI;
import freenet.node.RequestClient;
import freenet.pluginmanager.PluginRespirator;
import freenet.support.CurrentTimeUTC;
import freenet.support.Logger;

public final class WoTMessageManager extends MessageManager {
 
  /** One for all requests for WoTMessage*, for fairness. */
  final RequestClient mRequestClient;
 
  /* These booleans are used for preventing the construction of log-strings if logging is disabled (for saving some cpu cycles) */
 
  private static transient volatile boolean logDEBUG = false;
  private static transient volatile boolean logMINOR = false;
 
  static {
    Logger.registerClass(WoTMessageManager.class);
  }
 

  public WoTMessageManager(ExtObjectContainer myDB, IdentityManager myIdentityManager, Freetalk myFreetalk, PluginRespirator myPluginRespirator) {
    super(myDB, myIdentityManager, myFreetalk, myPluginRespirator);
   
    mRequestClient = new RequestClient() {

      public boolean persistent() {
        return false;
      }

      public void removeFrom(ObjectContainer container) {
        throw new UnsupportedOperationException();
      }

      public boolean realTimeFlag() {
        return false; // We want throughput.
      }
     
    };;
  }

  /**
   * For being used in JUnit tests to run without a node.
   */
  public WoTMessageManager(Freetalk myFreetalk) {
    super(myFreetalk);
    mRequestClient = null;
  }
 
  /**
   * Only for being used by the MessageManager itself and by unit tests.
   */
  protected synchronized void clearExpiredFetchFailedMarkers() {
    super.clearExpiredFetchFailedMarkers();
  }

  public WoTOwnMessage postMessage(MessageURI myParentThreadURI, Message myParentMessage, Set<Board> myBoards, Board myReplyToBoard,
      OwnIdentity myAuthor, String myTitle, Date myDate, String myText, List<Attachment> myAttachments) throws Exception {
    WoTOwnMessage m;
   
    if(myParentThreadURI != null && !(myParentThreadURI instanceof WoTMessageURI))
      throw new IllegalArgumentException("Parent thread URI is no WoTMessageURI: " + myParentThreadURI);
   
    if(myParentMessage != null && !(myParentMessage instanceof WoTMessage))
      throw new IllegalArgumentException("Parent message is no WoTMessage");
   
    if(!(myAuthor instanceof WoTOwnIdentity))
      throw new IllegalArgumentException("Author is no WoTOwnIdentity");

    Date date = myDate!=null ? myDate : CurrentTimeUTC.get();
    m = WoTOwnMessage.construct(mFreetalk, (WoTMessageURI)myParentThreadURI, myParentMessage, myBoards, myReplyToBoard, myAuthor, myTitle, date, myText, myAttachments);
    m.initializeTransient(mFreetalk);
    synchronized(this) {
      m.storeAndCommit();
    }

    if(mFreetalk != null) {
      PersistentTaskManager taskManager = mFreetalk.getTaskManager();
      if(taskManager != null)
        taskManager.onOwnMessagePosted(m);
    }
   
    /* We do not add the message to the boards it is posted to because the user should only see the message if it has been downloaded
     * successfully. This helps the user to spot problems: If he does not see his own messages we can hope that he reports a bug */
   
    return m;
  }
 
  @Override
  public synchronized void onMessageListInsertFailed(FreenetURI uri,boolean collision) throws NoSuchMessageListException {
    synchronized(Persistent.transactionLock(db)) {
      try {
        WoTOwnMessageList list = (WoTOwnMessageList)getOwnMessageList(MessageListID.construct(uri).toString());
        list.cancelInsert();
       
        if(collision)
          list.incrementInsertIndex();
       
        Persistent.checkedCommit(db, this);
      }
      catch(RuntimeException e) {
        Persistent.checkedRollback(db, this, e);
      }
    }
  }
 
  public synchronized void onMessageListFetchFailed(Identity author, FreenetURI uri, FetchFailedMarker.Reason reason) {
    WoTMessageList ghostList = new WoTMessageList(mFreetalk, author, uri);
    ghostList.initializeTransient(mFreetalk);
    MessageList.MessageListFetchFailedMarker marker;
   
      try {
        getMessageList(ghostList.getID());
       
        long oldestIndex = -1;
        long newestIndex = -1;
       
        try {
          oldestIndex = getUnavailableOldMessageListIndex(author);
          newestIndex = getUnavailableNewMessageListIndex(author);
        } catch(Exception e) {
          Logger.error(this, "Getting indices failed", e);
        }
       
        // TODO: Optimization: Remove the above debug code once the following exception doesn't happen anymore
       
        Logger.error(this, "Download failed of a MessageList which we already have (oldest available index: " +
            oldestIndex + "; newest available index: " + newestIndex + "):" + ghostList.getURI());
        return;
      }
      catch(NoSuchMessageListException e1) {
        try {
          marker = getMessageListFetchFailedMarker(ghostList.getID());
        } catch(NoSuchFetchFailedMarkerException e) {
          marker = null;
        }
      }
     
      // It's not possible to keep the synchronization order of message lists to synchronize BEFORE synchronizing on Persistent.transactionLock(db) some places so we
      // do not synchronize here.
      // And in this function we don't need to synchronize on it anyway because it is not known to anything which might modify it anyway.
      // In general, due to those issues the functions which modify message lists just use the message manager as synchronization object.
      //synchronized(ghostList) {
      synchronized(Persistent.transactionLock(db)) {
        try {
          Date date = CurrentTimeUTC.get();
          Date dateOfNextRetry;
         
          ghostList.storeWithoutCommit();
         
          final IdentityStatistics stats = getOrCreateIdentityStatistics(author);
          stats.onMessageListFetched(ghostList);
          stats.storeWithoutCommit();
         
          if(marker == null) {
            dateOfNextRetry = calculateDateOfNextMessageListFetchRetry(reason, date, 0);
            marker = new MessageList.MessageListFetchFailedMarker(ghostList, reason, date, dateOfNextRetry);
            marker.initializeTransient(mFreetalk);
          } else  {
            marker.setReason(reason);
            marker.incrementNumberOfRetries();
            dateOfNextRetry = calculateDateOfNextMessageListFetchRetry(reason, date, marker.getNumberOfRetries());
            marker.setDate(date);
            marker.setDateOfNextRetry(dateOfNextRetry);
          }
       
          // marker.setAllowRetryNow(false); // setDateOfNextRetry does this for us
          marker.storeWithoutCommit();
         
          Logger.normal(this, "Marked MessageList as download failed with reason " + reason + " (next retry is at " + dateOfNextRetry
              + ", number of retries: " + marker.getNumberOfRetries() + "): "
              +  ghostList);
         
          Persistent.checkedCommit(db, this);
        }
        catch(Exception ex) {
          Persistent.checkedRollback(db, this, ex);
        }
      }
      //}
  }
 
  public synchronized void onOwnMessageInserted(String id, FreenetURI freenetURI) throws NoSuchMessageException {
    WoTOwnMessage message = (WoTOwnMessage) getOwnMessage(id);
    synchronized(message) {
    synchronized(Persistent.transactionLock(db)) {
      try {
        message.markAsInserted(freenetURI);
        addMessageToMessageList(message);
        Persistent.checkedCommit(db, this);
      }
      catch(RuntimeException e) {
        Persistent.checkedRollbackAndThrow(db, this, e);
      }
    }
    }
  }
 
  /**
   * You have to synchronize on this MessageManager and on Persistent.transactionLock(db) when using this function.
   */
  private void addMessageToMessageList(WoTOwnMessage message) {
    Query query = db.query();
    query.constrain(WoTOwnMessageList.class);
    query.descend("mAuthor").constrain(message.getAuthor()).identity();
    query.descend("mWasInserted").constrain(false);
    query.descend("mIsBeingInserted").constrain(false);
   
    for(WoTOwnMessageList list : new Persistent.InitializingObjectSet<WoTOwnMessageList>(mFreetalk, query)) {
      try {
        synchronized(list) {
        list.addMessage(message);
        list.storeWithoutCommit();
        }
        if(logDEBUG) Logger.debug(this, "Added own message " + message + " to list " + list);
        return;
      }
      catch(RuntimeException e) {
        /* The list is full. */
        if(logDEBUG) Logger.debug(this, "Not adding message " + message.getID() + " to message list " + list.getID(), e);
      }
    }
   
    WoTOwnIdentity author = (WoTOwnIdentity)message.getAuthor();
    WoTOwnMessageList list = new WoTOwnMessageList(author, getFreeOwnMessageListIndex(author));
    list.initializeTransient(mFreetalk);
   
    // TODO: Optimization: This is debug code which was added on 2011-02-13 for preventing DuplicateMessageListException, it can be removed after some months if they do not happen.
    try {
      final MessageList existingList = mFreetalk.getMessageManager().getOwnMessageList(list.getID());
      throw new RuntimeException("getFreeOwnMessageListIndex reported non-free index, taken by: " + existingList);
    } catch(NoSuchMessageListException e) {}
   
    list.addMessage(message);
    list.storeWithoutCommit();
    if(logDEBUG) Logger.debug(this, "Found no list with free space, created the new list " + list.getID() + " for own message " + message.getID());
  }

  /**
   * ATTENTION: Due to a db4o bug you must check whether the messages are really not inserted by using testFreenetURIisNull() on them
   * TODO: Remove this workaround notice for the db4o bug as soon as we are sure that it does not happen anymore.
   */
  public synchronized ObjectSet<WoTOwnMessage> getNotInsertedOwnMessages() {
    final Query query = db.query();
    query.constrain(WoTOwnMessage.class);
    query.descend("mFreenetURI").constrain(null).identity();
    return new Persistent.InitializingObjectSet<WoTOwnMessage>(mFreetalk, query);
  }

  /**
   * Returns <code>OwnMessageList</code> objects which are marked as not inserted and not being inserted.
   */
  public synchronized ObjectSet<WoTOwnMessageList> getNotInsertedOwnMessageLists() {
    Query query = db.query();
    query.constrain(WoTOwnMessageList.class);
    query.descend("mWasInserted").constrain(false);
    query.descend("mIsBeingInserted").constrain(false);
    return new Persistent.InitializingObjectSet<WoTOwnMessageList>(mFreetalk, query);
  }
 
  public synchronized ObjectSet<WoTOwnMessageList> getBeingInsertedOwnMessageLists() {
    Query query = db.query();
    query.constrain(WoTOwnMessageList.class);
    query.descend("mWasInserted").constrain(false);
    query.descend("mIsBeingInserted").constrain(true);
    return new Persistent.InitializingObjectSet<WoTOwnMessageList>(mFreetalk, query);
  }

  /**
   * Get the highest index of a not fetched message list of the given identity.
   *
   * Notice that this uses a cached number from the IdentityStatistics object and it is possible that there are message lists with
   * higher index numbers which HAVE been fetched already. This function is for being used in scheduling message list fetches so this is
   * not a problem.
   */
  public synchronized long getUnavailableNewMessageListIndex(Identity identity) {
    try {
      return getIdentityStatistics(identity).getIndexOfLatestAvailableMessageList() + 1;
    } catch(NoSuchObjectException e) {
      return 0;
    }
  }
 
  /**
   * Get a hint for the highest index of a not fetched message list of the given identity.
   * In the current implementation, this just calls {@link getUnavailableNewMessageListIndex}
   *
   * Notice that this uses a cached number from the IdentityStatistics object and it is possible that there are message lists with
   * higher index numbers which HAVE been fetched already. This function is for being used in scheduling message list fetches so this is
   * not a problem.
   */
  public long getNewMessageListIndexEditionHint(Identity identity) {
    // TODO: Implement storage of edition hints in message lists.
    return getUnavailableNewMessageListIndex(identity);
  }


  /**
   * Get a hint for the lowest index of a not fetched message list of the given identity.
   *
   * Notice that this uses a cached number from the IdentityStatistics object and it is possible that there are message lists with
   * lower index numbers which HAVE been fetched already. This function is for being used in scheduling message list fetches so this is
   * not a problem.
   */
  public synchronized long getUnavailableOldMessageListIndex(Identity identity) throws NoSuchMessageListException {
    long unavailableIndex;
   
    try {
      unavailableIndex = getIdentityStatistics(identity).getIndexOfOldestAvailableMessageList() - 1;
    } catch(NoSuchObjectException e) {
      unavailableIndex = 0;
    }
   
    if(unavailableIndex < 0)
      throw new NoSuchMessageListException("");
   
    return unavailableIndex;
  }

  /**
   * Get the next free index for an OwnMessageList. You have to synchronize on this MessageManager while creating an OwnMessageList, this
   * function does not provide synchronization.
   */
  public long getFreeOwnMessageListIndex(final WoTOwnIdentity messageAuthor)  {
    // We do not use IdentityStatistics since it does not guarantee to return the highest existing list, it just guarantees that
    // the index after the one which it has returned does not exist. We must of course not re-use own message list indices.
    final Query q = db.query();
    // We query for MessageList and not OwnMessageList because the user might have deleted his own messages or lost his database
    q.constrain(MessageList.class);
    q.descend("mAuthor").constrain(messageAuthor).identity();
    q.descend("mIndex").orderDescending(); // TODO: This is inefficient!
    final ObjectSet<MessageList> result = new Persistent.InitializingObjectSet<MessageList>(mFreetalk, q);
   
    // We must look for the latest message list which was not marked as fetch failed and return its index + 1
    // Further, if a list is marked as fetch failed but the reason is NOT data not found, we must return its index + 1; explanation:
    // There are 2 things which could go wrong here:
    // 1) We accidentally return the index of a list which was already inserted
    // 2) We accidentally consider an empty slot as filled already and return its index + 1
    // Case 1 is much worse: We do not ever want to insert a message list into an old slot which has fallen out of the network already
    // because that would result in nobody seeing the list.
    // Therefore, we bias towards case 2 which Freetalk should be able to handle well:
    // It is normal for content to fall out of the network, therefore the USK queue must be able to deal well with empty slots.
   
    for(final MessageList list : result) {
      FetchFailedMarker marker;
     
      if(!(list instanceof OwnMessageList)) {
        // If the list is no OwnMessageList, it might be a ghost list only for a data-not-found slot... we shall ignore
        // those lists in the computation, so we check whether there is a FetchFailedMarker
        try {
          marker = getMessageListFetchFailedMarker(list.getID());
        } catch(NoSuchFetchFailedMarkerException e) {
          marker = null;
        }
      } else {
        // The list is an OwnMessageList, we MUST NOT check for a FetchFailedMarker: We ignore DataNotFound non-own MessageLists when
        // returning a free slot index, so both an OwnMessageList and a MessageList with DNF FetchFailedMarker might exist at once
        // for the same index. If we DID take its FetchFailedMarker into consideration, the index would be returned by this function
        // even though an OwnMessageList exists for that index already.
        marker = null;
      }
     
      if(marker == null || marker.getReason() != FetchFailedMarker.Reason.DataNotFound)
        return list.getIndex() + 1;
    }
   
    // There are no message lists or all are marked as DNF => Slot 0 must be free, return it.
    return 0;
  }
 
  public WoTMessageRating rateMessage(final WoTOwnIdentity rater, final WoTMessage message, final byte value) {
    synchronized(mIdentityManager) {
    synchronized(this) {
      // We do not have to re-query the rater/message because MessageRating.storeWithout commit throws if they are not stored anymore
     
      try {
        getMessageRating(rater, message);
        throw new IllegalArgumentException("The message was already rated");
      } catch(NoSuchMessageRatingException e) {}
     
      final WoTMessageRating rating = new WoTMessageRating(rater, message, value);
      rating.initializeTransient(mFreetalk);
      rating.storeAndCommit();
     
      return rating;
    }
    }
  }
 
  /**
   * This function is not synchronized to allow calls to it when only having locked a {@link Board} and not the whole MessageManager.
   */
  public WoTMessageRating getMessageRating(final OwnIdentity rater, final Message message) throws NoSuchMessageRatingException {
    if(!(rater instanceof WoTOwnIdentity))
      throw new IllegalArgumentException("No WoT identity: " + rater);
   
    if(!(message instanceof WoTMessage))
      throw new IllegalArgumentException("No WoT message: " + message);
   
    final Query query = db.query();
    query.constrain(WoTMessageRating.class);
    query.descend("mRater").constrain(rater).identity();
    query.descend("mMessage").constrain(message).identity();
    final InitializingObjectSet<WoTMessageRating> result = new Persistent.InitializingObjectSet<WoTMessageRating>(mFreetalk, query);
   
    switch(result.size()) {
      case 0: throw new NoSuchMessageRatingException();
      case 1: return result.next();
      default: throw new DuplicateElementException("Duplicate rating from " + rater + " of " + message);
    }
  }
 
  public ObjectSet<WoTMessageRating> getAllMessageRatings(final Message message) {
    final Query query = db.query();
    query.constrain(WoTMessageRating.class);
    query.descend("mMessage").constrain(message).identity();
    return new Persistent.InitializingObjectSet<WoTMessageRating>(mFreetalk, query);
  }
 
  public ObjectSet<? extends MessageRating> getAllMessageRatingsBy(OwnIdentity rater) {
    final Query query = db.query();
    query.constrain(WoTMessageRating.class);
    query.descend("mRater").constrain(rater).identity();
    return new Persistent.InitializingObjectSet<WoTMessageRating>(mFreetalk, query);
  }

  private void deleteMessageRating(final MessageRating rating, boolean undoTrustChange) {
    if(!(rating instanceof WoTMessageRating))
      throw new IllegalArgumentException("No WoT rating: " + rating);
   
    final WoTMessageRating realRating = (WoTMessageRating)rating;
   
    synchronized(this) {
      realRating.initializeTransient(mFreetalk);
      realRating.deleteAndCommit(undoTrustChange);
    }
  }

  @Override
  public void deleteMessageRatingWithoutRevertingEffect(MessageRating rating) {
    deleteMessageRating(rating, false);
  }

  @Override
  public void deleteMessageRatingAndRevertEffect(MessageRating rating) {
    deleteMessageRating(rating, true);
  }
 
}
TOP

Related Classes of plugins.Freetalk.WoT.WoTMessageManager

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.