Package org.rssowl.core.tests.model

Source Code of org.rssowl.core.tests.model.DBManagerTest

/*   **********************************************************************  **
**   Copyright notice                                                       **
**                                                                          **
**   (c) 2005-2009 RSSOwl Development Team                                  **
**   http://www.rssowl.org/                                                 **
**                                                                          **
**   All rights reserved                                                    **
**                                                                          **
**   This program and the accompanying materials are made available under   **
**   the terms of the Eclipse Public License v1.0 which accompanies this    **
**   distribution, and is available at:                                     **
**   http://www.rssowl.org/legal/epl-v10.html                               **
**                                                                          **
**   A copy is found in the file epl-v10.html and important notices to the  **
**   license from the team is found in the textfile LICENSE.txt distributed **
**   in this package.                                                       **
**                                                                          **
**   This copyright notice MUST APPEAR in all copies of the file!           **
**                                                                          **
**   Contributors:                                                          **
**     RSSOwl Development Team - initial API and implementation             **
**                                                                          **
**  **********************************************************************  */

package org.rssowl.core.tests.model;

import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;

import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.rssowl.core.Owl;
import org.rssowl.core.internal.persist.BookMark;
import org.rssowl.core.internal.persist.Category;
import org.rssowl.core.internal.persist.Feed;
import org.rssowl.core.internal.persist.Folder;
import org.rssowl.core.internal.persist.Guid;
import org.rssowl.core.internal.persist.Image;
import org.rssowl.core.internal.persist.Label;
import org.rssowl.core.internal.persist.News;
import org.rssowl.core.internal.persist.NewsBin;
import org.rssowl.core.internal.persist.NewsContainer;
import org.rssowl.core.internal.persist.Person;
import org.rssowl.core.internal.persist.SearchCondition;
import org.rssowl.core.internal.persist.SearchMark;
import org.rssowl.core.internal.persist.Source;
import org.rssowl.core.internal.persist.service.DBManager;
import org.rssowl.core.persist.IAttachment;
import org.rssowl.core.persist.IBookMark;
import org.rssowl.core.persist.ICategory;
import org.rssowl.core.persist.IConditionalGet;
import org.rssowl.core.persist.IFeed;
import org.rssowl.core.persist.IFilterAction;
import org.rssowl.core.persist.IFolder;
import org.rssowl.core.persist.IImage;
import org.rssowl.core.persist.ILabel;
import org.rssowl.core.persist.IMark;
import org.rssowl.core.persist.IModelFactory;
import org.rssowl.core.persist.INews;
import org.rssowl.core.persist.INewsBin;
import org.rssowl.core.persist.IPersistable;
import org.rssowl.core.persist.IPerson;
import org.rssowl.core.persist.ISearch;
import org.rssowl.core.persist.ISearchCondition;
import org.rssowl.core.persist.ISearchField;
import org.rssowl.core.persist.ISearchFilter;
import org.rssowl.core.persist.ISearchMark;
import org.rssowl.core.persist.ISource;
import org.rssowl.core.persist.SearchSpecifier;
import org.rssowl.core.persist.INews.State;
import org.rssowl.core.persist.dao.DynamicDAO;
import org.rssowl.core.persist.dao.IConditionalGetDAO;
import org.rssowl.core.persist.dao.IFeedDAO;
import org.rssowl.core.persist.dao.INewsDAO;
import org.rssowl.core.persist.event.BookMarkAdapter;
import org.rssowl.core.persist.event.BookMarkEvent;
import org.rssowl.core.persist.event.BookMarkListener;
import org.rssowl.core.persist.event.CategoryAdapter;
import org.rssowl.core.persist.event.CategoryEvent;
import org.rssowl.core.persist.event.CategoryListener;
import org.rssowl.core.persist.event.FeedAdapter;
import org.rssowl.core.persist.event.FeedEvent;
import org.rssowl.core.persist.event.FeedListener;
import org.rssowl.core.persist.event.FolderAdapter;
import org.rssowl.core.persist.event.FolderEvent;
import org.rssowl.core.persist.event.FolderListener;
import org.rssowl.core.persist.event.LabelAdapter;
import org.rssowl.core.persist.event.LabelEvent;
import org.rssowl.core.persist.event.LabelListener;
import org.rssowl.core.persist.event.NewsAdapter;
import org.rssowl.core.persist.event.NewsEvent;
import org.rssowl.core.persist.event.NewsListener;
import org.rssowl.core.persist.event.PersonAdapter;
import org.rssowl.core.persist.event.PersonEvent;
import org.rssowl.core.persist.event.PersonListener;
import org.rssowl.core.persist.event.SearchMarkAdapter;
import org.rssowl.core.persist.event.SearchMarkEvent;
import org.rssowl.core.persist.event.SearchMarkListener;
import org.rssowl.core.persist.reference.FeedLinkReference;
import org.rssowl.core.persist.reference.FeedReference;
import org.rssowl.core.persist.reference.NewsReference;
import org.rssowl.core.persist.service.PersistenceException;
import org.rssowl.core.persist.service.UniqueConstraintException;
import org.rssowl.core.tests.TestUtils;

import com.db4o.ObjectContainer;
import com.db4o.query.Query;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
* @author Ismael Juma (ismael@juma.me.uk)
*/
@SuppressWarnings("nls")
public class DBManagerTest {

  private static final String NOT_IDENTICAL_MESSAGE = "Item in the database is not identical to initial item.";
  private IModelFactory fTypesFactory;
  private ObjectContainer fDb;
  private INewsDAO fNewsDAO;

  /**
   * @throws Exception
   */
  @Before
  public void setUp() throws Exception {
    Owl.getPersistenceService().recreateSchema();
    fTypesFactory = Owl.getModelFactory();
    fDb = DBManager.getDefault().getObjectContainer();
    fNewsDAO = DynamicDAO.getDAO(INewsDAO.class);
  }

  /**
   * @throws Exception
   */
  @Test
  public void testNewsDaoLoadAllFromFeed() throws Exception {
    IFolder root = fTypesFactory.createFolder(null, null, "Root");
    INewsBin bin = fTypesFactory.createNewsBin(null, root, "Bin");
    DynamicDAO.save(root);

    IFeed parent = fTypesFactory.createFeed(null, new URI("http://www.feed.com"));
    INews news = fTypesFactory.createNews(null, parent, new Date());
    DynamicDAO.save(parent);

    INews newsCopy = fTypesFactory.createNews(news, bin);
    DynamicDAO.save(newsCopy);
    DynamicDAO.save(bin);

    Collection<INews> newsFromFeed = DynamicDAO.getDAO(INewsDAO.class).loadAll(new FeedLinkReference(parent.getLink()), INews.State.getVisible());
    assertEquals(1, newsFromFeed.size());
    assertEquals(news, newsFromFeed.iterator().next());
  }

  /**
   *
   */
  @Test
  public void testAddDuplicateNewsDeadlocks() {
    List<INews> newsList = new ArrayList<INews>();
    IFeed feed = createFeed();
    INews news = createNews(feed);
    newsList.add(news);
    newsList.add(news);
    DynamicDAO.saveAll(newsList);
  }

  /**
   * Tests that {@link INewsDAO#setState(Set, State, boolean)} works correctly.
   * @throws Exception s
   */
  @Test
  public void testNewsDAODeleteByStates() throws Exception  {
    String feed0Link = "http://www.feed0.com";
    IFeed feed = fTypesFactory.createFeed(null, new URI(feed0Link));
    INews news = fTypesFactory.createNews(null, feed, new Date());
    news.setState(INews.State.HIDDEN);
    String news0Guid = "news0";
    fTypesFactory.createGuid(news, news0Guid, true);
    news = fTypesFactory.createNews(null, feed, new Date());
    news.setState(INews.State.NEW);
    String news1Guid = "news1";
    fTypesFactory.createGuid(news, news1Guid, true);
    DynamicDAO.save(feed);

    String feed1Link = "http://www.feed1.com";
    feed = fTypesFactory.createFeed(null, new URI(feed1Link));
    news = fTypesFactory.createNews(null, feed, new Date());
    news.setState(INews.State.HIDDEN);
    String news2Guid = "news2";
    fTypesFactory.createGuid(news, news2Guid, true);
    news = fTypesFactory.createNews(null, feed, new Date());
    news.setState(INews.State.READ);
    String news3Guid = "news3";
    fTypesFactory.createGuid(news, news3Guid, true);
    DynamicDAO.save(feed);
    news = null;
    feed = null;
    System.gc();

    final Set<NewsEvent> newsEvents = new HashSet<NewsEvent>();
    NewsListener newsListener = new NewsListener() {
      public void entitiesAdded(Set<NewsEvent> events) {
        fail("Only update events expected");
      }

      public void entitiesDeleted(Set<NewsEvent> events) {
        fail("Only update events expected");
      }

      public void entitiesUpdated(Set<NewsEvent> events) {
        newsEvents.addAll(events);
      }
    };
    DynamicDAO.addEntityListener(INews.class, newsListener);
    try {
      fNewsDAO.setState(EnumSet.of(INews.State.HIDDEN), INews.State.DELETED, false);
      assertEquals(2, newsEvents.size());
      for (NewsEvent newsEvent : newsEvents) {
        String guid = newsEvent.getEntity().getGuid().getValue();
        assertEquals(INews.State.DELETED, newsEvent.getEntity().getState());
        assertEquals(true, guid.equals(news0Guid) || guid.equals(news2Guid));
      }

      IFeedDAO feedDao = DynamicDAO.getDAO(IFeedDAO.class);
      feed = feedDao.load(new URI(feed0Link));
      assertEquals(2, feed.getNews().size());

      feed = feedDao.load(new URI(feed1Link));
      assertEquals(2, feed.getNews().size());
    } finally {
      DynamicDAO.removeEntityListener(INews.class, newsListener);
    }
  }

  /**
   * Tests that saving a NewsBin with a INews that was never saved in DELETED
   * state causes the INews to be removed from the INewsBin.
   * @throws Exception
   */
  @Test
  @Ignore("Not sure we need to support this case")
  public void testSaveNewsBinWithNeverSavedNewsInDeletedState() throws Exception {
    IFeed feed = fTypesFactory.createFeed(null, new URI("http://www.feed.com"));
    INews news = fTypesFactory.createNews(null, feed, new Date());
    news.setState(INews.State.DELETED);
    DynamicDAO.save(feed);

    IFolder folder = fTypesFactory.createFolder(null, null, "Folder");
    INewsBin newsBin = fTypesFactory.createNewsBin(null, folder, "NewsBin");
    DynamicDAO.save(folder);

    INews newsCopy = fTypesFactory.createNews(news, newsBin);
    DynamicDAO.save(newsCopy);
    DynamicDAO.save(newsBin);

    assertEquals(0, newsBin.getNews().size());
    assertEquals(null, fNewsDAO.load(newsCopy.getId()));
  }

  /**
   * Test case where original news is copied with a state != NEW, the copied news
   * state is changed to NEW before saving and then it's saved. The news bin
   * should have the news in the right bucket after save is finished.
   * @throws Exception
   */
  @Test
  public void testCopyReadNewsButSaveWithNewState() throws Exception {
    IFeed feed = fTypesFactory.createFeed(null, new URI("http://www.feed.com"));
    INews news = fTypesFactory.createNews(null, feed, new Date());
    news.setState(INews.State.READ);
    DynamicDAO.save(feed);

    IFolder folder = fTypesFactory.createFolder(null, null, "Folder");
    INewsBin newsBin = fTypesFactory.createNewsBin(null, folder, "NewsBin");
    DynamicDAO.save(folder);

    INews newsCopy = fTypesFactory.createNews(news, newsBin);
    newsCopy.setState(INews.State.NEW);
    DynamicDAO.save(newsCopy);
    DynamicDAO.save(newsBin);

    assertEquals(1, newsBin.getNews(EnumSet.of(INews.State.NEW)).size());
  }

  /**
   *
   */
  @Test
  public void testSetStateWhereTwoEquivalentNewsAreChangedToInvisible() {
    IFeed feed0 = createFeed();
    INews news0 = fTypesFactory.createNews(null, feed0, new Date());
    URI link = createURI("http://rssowl.org");
    news0.setLink(link);
    IFeed feed1 = fTypesFactory.createFeed(null, createURI("http://www.afeedlink.com"));
    INews news1 = fTypesFactory.createNews(null, feed1, new Date());
    news1.setLink(link);
    DynamicDAO.save(feed0);
    DynamicDAO.save(feed1);

    DynamicDAO.getDAO(INewsDAO.class).setState(Arrays.asList(news0, news1), INews.State.HIDDEN, true, false);
    assertEquals(INews.State.HIDDEN, news0.getState());
    assertEquals(INews.State.HIDDEN, news1.getState());
  }

  /**
   * Tests that deleting a INews.isCopy() will cause either:
   * <li>The original feed not to be deleted if it is referenced by a BM or
   * another INews.isCopy()</li>
   * <li>The original feed to be deleted otherwise.</li>
   */
  @Test
  public void testDeleteNewsIsCopy() {
    IFeed feed = createFeed();
    INews news = fTypesFactory.createNews(null, feed, new Date());
    fTypesFactory.createGuid(news, "http://www.link.com", true);
    DynamicDAO.save(feed);

    IFolder folder = fTypesFactory.createFolder(null, null, "Folder");
    IBookMark bookMark = fTypesFactory.createBookMark(null, folder, new FeedLinkReference(feed.getLink()), "BookMark");
    INewsBin newsBin = fTypesFactory.createNewsBin(null, folder, "NewsBin");
    DynamicDAO.save(folder);

    INews newsCopy = fTypesFactory.createNews(news, newsBin);
    DynamicDAO.save(newsCopy);
    DynamicDAO.save(newsBin);


    /* Feed referenced by bookMark and newsCopy */
    fNewsDAO.setState(Collections.singleton(newsCopy), INews.State.DELETED, false, false);
    assertEquals(feed, DynamicDAO.load(IFeed.class, feed.getId()));

    newsCopy = fTypesFactory.createNews(news, newsBin);
    fNewsDAO.save(newsCopy);
    DynamicDAO.save(newsBin);

    DynamicDAO.delete(bookMark);
    newsBin = fTypesFactory.createNewsBin(null, folder, "NewsBin2");
    DynamicDAO.save(newsBin);
    INews newsCopy2 = fTypesFactory.createNews(newsCopy, newsBin);
    DynamicDAO.save(newsCopy2);
    DynamicDAO.save(newsBin);

    /* Feed referenced by newsCopy and newsCopy2 */
    fNewsDAO.setState(Collections.singleton(newsCopy), INews.State.DELETED, false, false);
    assertEquals(feed, DynamicDAO.load(IFeed.class, feed.getId()));

    /* Feed referenced by newsCopy2 which is being deleted */
    fNewsDAO.setState(Collections.singleton(newsCopy2), INews.State.DELETED, false, false);
    assertEquals(null, DynamicDAO.load(IFeed.class, feed.getId()));
  }

  /**
   * Tests that changing the state of a INews#isCopy() does not update the
   * NewsCounter for its feed. Also tests that the parent INewsBin is updated.
   */
  @Test
  public void testNewsCopyStateChangeDoesNotUpdateNewsCounter() {
    IFeed feed = createFeed();
    INews news = fTypesFactory.createNews(null, feed, new Date());
    fTypesFactory.createGuid(news, "http://www.link.com", true);
    DynamicDAO.save(feed);

    IFolder folder = fTypesFactory.createFolder(null, null, "Folder");
    IBookMark bookMark = fTypesFactory.createBookMark(null, folder, new FeedLinkReference(feed.getLink()), "BookMark");
    INewsBin newsBin = fTypesFactory.createNewsBin(null, folder, "NewsBin");
    DynamicDAO.save(folder);

    INews newsCopy = fTypesFactory.createNews(news, newsBin);
    DynamicDAO.save(newsCopy);
    DynamicDAO.save(newsBin);

    assertEquals(1, bookMark.getNewsCount(EnumSet.of(INews.State.NEW)));
    assertEquals(1, newsBin.getNewsCount(EnumSet.of(INews.State.NEW)));

    fNewsDAO.setState(Collections.singleton(newsCopy), INews.State.READ, false, false);

    assertEquals(1, bookMark.getNewsCount(EnumSet.of(INews.State.NEW)));
    assertEquals(0, newsBin.getNewsCount(EnumSet.of(INews.State.NEW)));
  }

  /**
   * Tests that news copy is actually deleted and removed from INewsBin when
   * it's saved after changing state to DELETED. Also checks that INewsBin is
   * updated if the state of the INews contained in it is changed to HIDDEN.
   */
  @Test
  public void testNewsIsCopyActuallyDeletedOnDeleteStateChange() {
    IFeed feed = createFeed();
    INews news = fTypesFactory.createNews(null, feed, new Date());
    fTypesFactory.createGuid(news, "http://www.link.com", true);
    DynamicDAO.save(feed);

    IFolder folder = fTypesFactory.createFolder(null, null, "Folder");
    fTypesFactory.createBookMark(null, folder, new FeedLinkReference(feed.getLink()), "BookMark");
    INewsBin newsBin = fTypesFactory.createNewsBin(null, folder, "NewsBin");
    DynamicDAO.save(folder);

    INews newsCopy = fTypesFactory.createNews(news, newsBin);
    DynamicDAO.save(newsCopy);
    DynamicDAO.save(newsBin);

    fNewsDAO.setState(Collections.singleton(newsCopy), INews.State.HIDDEN, false, false);
    assertEquals(1, newsBin.getNewsRefs().size());
    assertEquals(1, newsBin.getNewsRefs(EnumSet.of(INews.State.HIDDEN)).size());
    assertEquals(0, newsBin.getNewsRefs(EnumSet.of(INews.State.NEW)).size());
    assertEquals(newsCopy, newsBin.getNews().get(0));

    fNewsDAO.setState(Collections.singleton(newsCopy), INews.State.DELETED, false, false);
    assertEquals(0, newsBin.getNewsRefs().size());
    assertNull(fNewsDAO.load(newsCopy.getId()));
  }

  /**
   * Tests that copying a sticky news from the original feed to a INewsBin
   * followed by deletion of the original IBookMark works correctly. In other words,
   * we want to verify that the original feed is not deleted with the IBookMark.
   */
  @Test
  public void testCopyStickyNewsAndDeleteOriginalBookMark() {
    IFeed feed = createFeed();
    INews news = fTypesFactory.createNews(null, feed, new Date());
    news.setFlagged(true);
    fTypesFactory.createGuid(news, "http://www.link.com", true);
    DynamicDAO.save(feed);

    IFolder folder = fTypesFactory.createFolder(null, null, "Folder");
    IBookMark mark = fTypesFactory.createBookMark(null, folder, new FeedLinkReference(feed.getLink()), "BookMark");
    INewsBin newsBin = fTypesFactory.createNewsBin(null, folder, "NewsBin");
    DynamicDAO.save(folder);

    INews newsCopy = fTypesFactory.createNews(news, newsBin);
    DynamicDAO.save(newsCopy);
    DynamicDAO.save(newsBin);

    DynamicDAO.delete(mark);
    /*
     * Mark containing the feed was deleted, but feed is not deleted because
     * it's still referenced by a copied news. The feed is hence empty.
     */
    assertEquals(0, feed.getNews().size());
    assertEquals(1, newsBin.getNewsRefs().size());
    assertEquals(newsCopy, newsBin.getNews().get(0));
  }

  /**
   * Tests that deleting a IBookMark where the feed has news copies does not
   * delete the feed.
   */
  @Test
  public void testDeleteBookMarkWithNewsCopies() {
    IFeed feed = createFeed();
    INews news = fTypesFactory.createNews(null, feed, new Date());
    fTypesFactory.createGuid(news, "http://www.link.com", true);
    DynamicDAO.save(feed);

    IFolder folder = fTypesFactory.createFolder(null, null, "Folder");
    IBookMark bookMark = fTypesFactory.createBookMark(null, folder, new FeedLinkReference(feed.getLink()), "Mark");
    INewsBin newsBin = fTypesFactory.createNewsBin(null, folder, "bin");
    DynamicDAO.save(folder);

    INews newsCopy = fTypesFactory.createNews(news, newsBin);
    DynamicDAO.save(newsCopy);
    DynamicDAO.save(newsBin);

    DynamicDAO.delete(bookMark);
    feed = DynamicDAO.load(IFeed.class, feed.getId());
    assertNotNull(feed);
    assertEquals(0, feed.getNews().size());
  }

  /**
   * Tests that querying on News#fParentId to determine if it is contained
   * in a INewsBin works.
   */
  @Test
  public void testQueryNewsOnIsCopy() {
    IFeed feed = createFeed();
    INews news = fTypesFactory.createNews(null, feed, new Date());
    fTypesFactory.createGuid(news, "http://www.link.com", true);
    DynamicDAO.save(feed);

    IFolder folder = fTypesFactory.createFolder(null, null, "Folder");
    fTypesFactory.createBookMark(null, folder, new FeedLinkReference(feed.getLink()), "BookMark");
    INewsBin newsBin = fTypesFactory.createNewsBin(null, folder, "NewsBin");
    DynamicDAO.save(folder);

    INews newsCopy = fTypesFactory.createNews(news, newsBin);
    DynamicDAO.save(newsCopy);
    DynamicDAO.save(newsBin);

    Query query = fDb.query();
    query.constrain(News.class);
    query.descend("fParentId").constrain(0).not();
    List<?> newsCopies = query.execute();
    assertEquals(1, newsCopies.size());
    assertEquals(newsCopy, newsCopies.get(0));

    query = fDb.query();
    query.constrain(News.class);
    query.descend("fParentId").constrain(0);
    List<?> newsList = query.execute();
    assertEquals(1, newsList.size());
    assertEquals(news, newsList.get(0));

    query = fDb.query();
    query.constrain(News.class);
    query.descend("fGuidValue").constrain(news.getGuid().getValue());
    query.descend("fParentId").constrain(0).not();
    newsCopies = query.execute();
    assertEquals(1, newsCopies.size());
    assertEquals(newsCopy, newsCopies.get(0));

    query = fDb.query();
    query.constrain(News.class);
    query.descend("fGuidValue").constrain(news.getGuid().getValue());
    query.descend("fParentId").constrain(0);
    newsList = query.execute();
    assertEquals(1, newsList.size());
    assertEquals(news, newsList.get(0));
  }

  /**
   * Tests that deleting a search condition removes it and its search field from
   * the database.
   */
  @Test
  public void testDeleteSearchCondition() {
    IFolder folder = fTypesFactory.createFolder(null, null, "Folder");
    ISearchMark searchMark = fTypesFactory.createSearchMark(null, folder, "Mark");
    DynamicDAO.save(folder);
    ISearchField searchField = fTypesFactory.createSearchField(0, "SomeEntity");
    ISearchCondition searchCondition = fTypesFactory.createSearchCondition(null, searchMark, searchField, SearchSpecifier.BEGINS_WITH, "value");
    DynamicDAO.save(searchMark);
    assertNotNull(DynamicDAO.load(ISearchCondition.class, searchCondition.getId()));
    long searchFieldId = fDb.ext().getID(searchCondition.getField());
    assertNotNull(fDb.ext().getByID(searchFieldId));
    DynamicDAO.delete(searchCondition);
    assertNull(DynamicDAO.load(ISearchCondition.class, searchCondition.getId()));
    assertNull(fDb.ext().getByID(searchFieldId));
    assertEquals(0, searchMark.getSearchConditions().size());
  }

  /**
   * Tests that deleting a search mark removes it from the database and cascades
   * it appropriately to its children.
   */
  @Test
  public void testDeleteSearchMark() {
    IFolder folder = fTypesFactory.createFolder(null, null, "Folder");
    ISearchMark searchMark = fTypesFactory.createSearchMark(null, folder, "Mark");
    ISearchField searchField = fTypesFactory.createSearchField(0, "SomeEntity");
    ISearchCondition searchCondition = fTypesFactory.createSearchCondition(null, searchMark, searchField, SearchSpecifier.BEGINS_WITH, "value");
    DynamicDAO.save(folder);
    assertNotNull(DynamicDAO.load(ISearchMark.class, searchMark.getId()));
    assertNotNull(DynamicDAO.load(ISearchCondition.class, searchCondition.getId()));
    long searchFieldId = fDb.ext().getID(searchCondition.getField());
    assertNotNull(fDb.ext().getByID(searchFieldId));
    DynamicDAO.delete(searchMark);
    assertNull(DynamicDAO.load(ISearchMark.class, searchMark.getId()));
    assertNull(DynamicDAO.load(ISearchCondition.class, searchCondition.getId()));
    assertNull(fDb.ext().getByID(searchFieldId));
  }

  /**
   * Tests that deleting a news bin deletes the internal news container too.
   */
  @Test
  public void testDeleteNewsBinDeletesNewsContainer() {
    IFeed feed = createFeed();
    INews news = fTypesFactory.createNews(null, feed, new Date());
    DynamicDAO.save(feed);

    IFolder folder = fTypesFactory.createFolder(null, null, "Folder");
    fTypesFactory.createBookMark(null, folder, new FeedLinkReference(feed.getLink()), "BookMark");
    NewsBin bin = (NewsBin) fTypesFactory.createNewsBin(null, folder, "News Bin");
    DynamicDAO.save(folder);

    INews newsCopy = fTypesFactory.createNews(news, bin);
    DynamicDAO.save(newsCopy);
    DynamicDAO.save(bin);

    /* Ensure that arrays are treated specially by db4o, don't need to delete them manually */
    assertFalse(fDb.ext().isStored(bin.internalGetNewsContainer().internalGetNewsIds()));

    NewsContainer newsContainer = bin.internalGetNewsContainer();
    DynamicDAO.delete(bin);
    assertFalse(fDb.ext().isStored(newsContainer));
  }

  /**
   * Tests that deleting a news bin is not sending out folder-deleted events.
   */
  @Test
  public void testDeleteBinIsNotSendindFolderDeleteEvents() {
    IFolder folder = fTypesFactory.createFolder(null, null, "Folder");
    INewsBin bin = fTypesFactory.createNewsBin(null, folder, "News Bin");
    DynamicDAO.save(folder);

    assertNotNull(DynamicDAO.load(INewsBin.class, bin.getId()));

    final boolean[] bool = new boolean[] { false };

    FolderAdapter folderListener = new FolderAdapter() {
      @Override
      public void entitiesDeleted(Set<FolderEvent> events) {
        bool[0] = true;
      }
    };

    DynamicDAO.addEntityListener(IFolder.class, folderListener);

    DynamicDAO.delete(bin);
    assertNull(DynamicDAO.load(INewsBin.class, bin.getId()));

    DynamicDAO.removeEntityListener(IFolder.class, folderListener);

    assertFalse("Unexpected Folder Event", bool[0]);
  }

  private ICategory createFeedCategory() throws PersistenceException {
    IFeed feed = DynamicDAO.save(createFeed());
    ICategory category = fTypesFactory.createCategory(null, feed);
    category.setName("categoryName");
    category.setDomain("some/domain");
    category.setProperty("one_property", "value");
    return category;
  }

  private ICategory createNewsCategory() throws PersistenceException {
    IFeed feed = DynamicDAO.save(createFeed());
    INews news = DynamicDAO.save(createNews(feed));
    ICategory category = fTypesFactory.createCategory(null, news);
    category.setName("categoryName");
    category.setDomain("some/domain");
    category.setProperty("one_property", "value");
    return category;
  }

  private IBookMark createBookMark() throws PersistenceException {
    IFolder folder = DynamicDAO.save(createFolder());
    return createBookMark(folder);
  }

  private ISearchMark createSearchMark() throws PersistenceException {
    IFolder folder = DynamicDAO.save(createFolder());
    return createSearchMark(folder);
  }

  private IBookMark createBookMark(IFolder folder) throws PersistenceException {
    IFeed feed = createFeed();
    IFeed savedFeed = Owl.getPersistenceService().getDAOService().getFeedDAO().load(feed.getLink());
    if (savedFeed == null)
      savedFeed = DynamicDAO.save(feed);

    IBookMark bookMark = fTypesFactory.createBookMark(null, folder, new FeedLinkReference(savedFeed.getLink()), "Default bookmark");
    bookMark.setLastVisitDate(createDate());
    bookMark.setPopularity(50);
    bookMark.setErrorLoading(false);
    return bookMark;
  }

  private IFolder createFolder() {
    return createFolder(null);
  }

  private IFolder createFolder(IFolder parent) {
    IFolder folder = fTypesFactory.createFolder(null, parent, "SomeFolder");
    folder.setBlogrollLink(createURI("http://someuri.com"));
    folder.setProperty("somekey", "somevalue");
    return folder;
  }

  /**
   * Checks: - Addition of person - That ADD event is issued - Updating of
   * person - That UPDATE event is issued - Getting of person
   */
  @Test
  public void testAddUpdateAndGetPerson() {
    IFeed feed = createFeed();
    final Person initialJohn = (Person) createPersonJohn(feed);
    final Person[] updatedJohn = new Person[1];
    final boolean[] personAddedCalled = new boolean[] { false };
    final boolean[] personUpdatedCalled = new boolean[] { false };
    PersonListener personListener = null;
    try {
      personListener = new PersonAdapter() {
        @Override
        public void entitiesAdded(Set<PersonEvent> events) {
          for (PersonEvent event : events) {
            personAddedCalled[0] = true;
            Person dbPerson = (Person) event.getEntity();
            initialJohn.setId(dbPerson.getId());
            assertTrue(initialJohn.isIdentical(dbPerson));
          }
        }

        @Override
        public void entitiesUpdated(Set<PersonEvent> events) {
          PersonEvent event = events.iterator().next();
          personUpdatedCalled[0] = true;
          Person dbPerson = (Person) event.getEntity();
          assertTrue(updatedJohn[0].isIdentical(dbPerson));
        }
      };
      DynamicDAO.addEntityListener(IPerson.class, personListener);
      long savedJohnId = DynamicDAO.save(initialJohn).getId().longValue();
      System.gc();
      IPerson savedJohn = DynamicDAO.load(IPerson.class, savedJohnId);
      initialJohn.setId(savedJohn.getId());
      assertTrue(initialJohn.isIdentical(savedJohn));
      URI oldJohnEmail = savedJohn.getEmail();
      IPerson dan = createPersonDan(feed);
      dan.setEmail(oldJohnEmail);
      DynamicDAO.removeEntityListener(IPerson.class, personListener);
      DynamicDAO.save(dan);
      savedJohn.setEmail(createURI("anewemailaddress@gmail.com"));
      updatedJohn[0] = (Person) savedJohn;
      DynamicDAO.addEntityListener(IPerson.class, personListener);
      DynamicDAO.save(savedJohn);
      assertTrue(personAddedCalled[0]);
      assertTrue(personUpdatedCalled[0]);
      DynamicDAO.delete(updatedJohn[0]);

    } catch (PersistenceException e) {
      fail(e.getMessage());
    } finally {
      if (personListener != null) {
        DynamicDAO.removeEntityListener(IPerson.class, personListener);
      }
    }

  }

  /**
   * Tests that {@link News#merge(INews)} copies the link property correctly.
   *
   * @throws Exception
   */
  @Test
  public void testNewsMergeLink() throws Exception {
    IFeed feed = DynamicDAO.save(createFeed());
    INews news = fTypesFactory.createNews(null, feed, new Date());
    news.setId(1L);
    news.setLink(createURI("http://www.uri.com"));

    INews anotherNews = fTypesFactory.createNews(null, feed, new Date());
    URI uri = createURI("http://www.anotheruri.com");
    anotherNews.setLink(uri);

    news.merge(anotherNews);

    assertEquals(uri, news.getLink());
  }

  /**
   * Tests INews#setLink.
   *
   * @throws Exception
   */
  @Test
  public void testNewsSetLink() throws Exception {
    IFeed feed = DynamicDAO.save(createFeed());
    INews news = fTypesFactory.createNews(null, feed, new Date());
    URI uri = createURI("http://uri.com");
    news.setLink(uri);
    assertEquals(uri, news.getLink());
    news.setLink(null);
    assertNull(news.getLink());
    news.setLink(uri);
    assertEquals(uri, news.getLink());
    URI anotherUri = createURI("http://anotheruri.com");
    news.setLink(anotherUri);

    assertEquals(anotherUri, news.getLink());
  }

  /**
   * Tests deleting a INews and checking that the parent feed doesn't have it
   * anymore.
   *
   * @throws PersistenceException
   */
  @Test
  public void testDeleteNews() throws PersistenceException {
    IFeed feed = createFeed();
    feed = DynamicDAO.save(feed);
    INews news = createNews(feed);
    news = DynamicDAO.save(news);
    Long newsId = news.getId();
    DynamicDAO.delete(new NewsReference(newsId).resolve());
    feed = DynamicDAO.load(IFeed.class, feed.getId());
    assertEquals(0, feed.getNews().size());
    assertNull(DynamicDAO.load(INews.class, newsId));
  }

  /**
   * Tests loading the root folders.
   */
  @Test
  public void testLoadRootFolders() {
    try {
      // Save root folders
      IFolder folder1 = createFolder();
      folder1 = DynamicDAO.save(folder1);

      IFolder folder2 = fTypesFactory.createFolder(null, null, "AnotherFolder");
      folder2 = DynamicDAO.save(folder2);

      // Save non-root folder
      IFolder nonRootFolder = createFolderWithParent();
      DynamicDAO.save(nonRootFolder);

      Collection<IFolder> rootFolderRefs = Owl.getPersistenceService().getDAOService().getFolderDAO().loadRoots();
      assertEquals(3, rootFolderRefs.size());

      for (IFolder rootFolder : rootFolderRefs) {
        assertTrue(rootFolder.equals(folder1) || rootFolder.equals(folder2) || rootFolder.equals(nonRootFolder.getParent()));
      }

    } catch (PersistenceException e) {
      fail(e.getMessage());
    }
  }

  /**
   * Simply adds and deletes a IBookMark and fails if an exceptions is thrown.
   */
  @Test
  public void testAddAndDeleteBookMark() {
    try {
      IBookMark bookMark = createBookMark();
      bookMark = DynamicDAO.save(bookMark);
      DynamicDAO.delete(bookMark);
    } catch (PersistenceException e) {
      fail(e.getMessage());
    }
  }

  /**
   * Simply adds and deletes a ICategory with a feed as parent and fails if an
   * exception is thrown.
   */
  @Test
  public void testAddAndDeleteFeedCategory() {
    try {
      ICategory category = createFeedCategory();
      ICategory savedCategory = DynamicDAO.save(category);
      DynamicDAO.delete(savedCategory);
    } catch (PersistenceException e) {
      fail(e.getMessage());
    }
  }

  /**
   * Simply adds and deletes a ICategory with a news as parent and fails if an
   * exception is thrown.
   */
  @Test
  public void testAddAndDeleteNewsCategory() {
    try {
      ICategory category = createNewsCategory();
      ICategory savedCategory = DynamicDAO.save(category);
      DynamicDAO.delete(savedCategory);
    } catch (PersistenceException e) {
      fail(e.getMessage());
    }
  }

  /**
   * Adds, retrieves and checks if the added object matches the retrieved one.
   */
  @Test
  public void testAddAndGetFeedCategory() {
    Category category = (Category) createFeedCategory();
    DynamicDAO.save(category);
    Collection<ICategory> categories = DynamicDAO.loadAll(ICategory.class);
    assertEquals(1, categories.size());
    category.setId(categories.iterator().next().getId());
    assertTrue(category.isIdentical(categories.iterator().next()));
    DynamicDAO.deleteAll(categories);
  }

  /**
   * Adds, retrieves and checks if the added object matches the retrieved one.
   */
  public void testAddAndGetNewsCategory() {
    Category category = (Category) createNewsCategory();
    DynamicDAO.save(category);
    Collection<ICategory> categories = DynamicDAO.loadAll(ICategory.class);
    assertEquals(1, categories.size());
    category.setId(categories.iterator().next().getId());
    assertTrue(category.isIdentical(categories.iterator().next()));
    DynamicDAO.deleteAll(categories);
  }

  private ILabel createLabel() {
    ILabel label = fTypesFactory.createLabel(null, "someLabel");
    label.setColor("200,100,009");
    return label;
  }

  /**
   * Checks: - Addition of label - That ADD event is issued - Updating of label -
   * That UPDATE event is issued - Getting of label
   */
  @Test
  public void testAddUpdateAndGetLabel() {
    final Label initialLabel = (Label) createLabel();
    final Label[] updatedLabel = new Label[1];
    final boolean[] labelAddedCalled = new boolean[] { false };
    final boolean[] labelUpdatedCalled = new boolean[] { false };
    LabelListener labelListener = null;
    try {
      labelListener = new LabelAdapter() {
        @Override
        public void entitiesAdded(Set<LabelEvent> events) {
          for (LabelEvent event : events) {
            labelAddedCalled[0] = true;
            Label dbLabel = (Label) event.getEntity();
            initialLabel.setId(dbLabel.getId());
            assertTrue(initialLabel.isIdentical(dbLabel));
          }
        }

        @Override
        public void entitiesUpdated(Set<LabelEvent> events) {
          LabelEvent event = events.iterator().next();
          labelUpdatedCalled[0] = true;
          Label dbLabel = (Label) event.getEntity();
          assertTrue(updatedLabel[0].isIdentical(dbLabel));
        }
      };
      DynamicDAO.addEntityListener(ILabel.class, labelListener);
      long savedLabelId = DynamicDAO.save(initialLabel).getId().longValue();
      System.gc();
      ILabel dbLabel = DynamicDAO.load(ILabel.class, savedLabelId);
      initialLabel.setId(dbLabel.getId());
      assertTrue(initialLabel.isIdentical(dbLabel));
      dbLabel.setColor("255,255,137");
      updatedLabel[0] = (Label) dbLabel;
      DynamicDAO.save(dbLabel);
      assertTrue(labelAddedCalled[0]);
      assertTrue(labelUpdatedCalled[0]);
      DynamicDAO.delete(updatedLabel[0]);
    } catch (PersistenceException e) {
      fail(e.getMessage());
    } finally {
      if (labelListener != null) {
        DynamicDAO.removeEntityListener(ILabel.class, labelListener);
      }
    }
  }

  private void doTestAddUpdateGetAndDeleteSearchFilter(boolean gc) {
    /* Add */
    {
      ISearch search = fTypesFactory.createSearch(null);
      search.setMatchAllConditions(true);

      ISearchCondition condition1 = fTypesFactory.createSearchCondition(fTypesFactory.createSearchField(INews.TITLE, INews.class.getName()), SearchSpecifier.IS, "Foo");
      search.addSearchCondition(condition1);
      ISearchCondition condition2 = fTypesFactory.createSearchCondition(fTypesFactory.createSearchField(INews.ATTACHMENTS_CONTENT, INews.class.getName()), SearchSpecifier.CONTAINS, "Bar");
      search.addSearchCondition(condition2);

      ISearchFilter filter = fTypesFactory.createSearchFilter(null, search, "Filter");
      filter.setEnabled(true);
      filter.setOrder(5);

      IFilterAction action = fTypesFactory.createFilterAction("org.rssowl.ActionId1");
      action.setData(100);
      filter.addAction(action);

      action = fTypesFactory.createFilterAction("org.rssowl.ActionId2");
      action.setData(new Long[] { 1l, 2l, 3l });
      filter.addAction(action);

      filter = DynamicDAO.save(filter);
      if (gc)
        System.gc();

      Collection<ISearchFilter> filters = DynamicDAO.loadAll(ISearchFilter.class);
      assertEquals(1, filters.size());
      ISearchFilter savedFilter = filters.iterator().next();
      assertEquals(filter.getName(), savedFilter.getName());
      assertEquals(filter.getOrder(), savedFilter.getOrder());
      assertEquals(filter.isEnabled(), savedFilter.isEnabled());

      List<IFilterAction> actions = savedFilter.getActions();
      assertEquals(2, actions.size());
      assertEquals("org.rssowl.ActionId1", actions.get(0).getActionId());
      assertEquals(100, actions.get(0).getData());
      assertEquals("org.rssowl.ActionId2", actions.get(1).getActionId());
      assertEquals(true, Arrays.equals((Object[]) actions.get(1).getData(), new Long[] { 1l, 2l, 3l }));

      ISearch savedSearch = savedFilter.getSearch();
      assertEquals(search.matchAllConditions(), savedSearch.matchAllConditions());
      List<ISearchCondition> savedConditions = savedSearch.getSearchConditions();
      assertEquals(2, savedConditions.size());

      assertEquals(true, ((SearchCondition) savedConditions.get(0)).isIdentical(condition1));
      assertEquals(true, ((SearchCondition) savedConditions.get(1)).isIdentical(condition2));
    }

    /* Update */
    {
      Collection<ISearchFilter> filters = DynamicDAO.loadAll(ISearchFilter.class);
      ISearchFilter savedFilter = filters.iterator().next();

      savedFilter.setName("Disabled Filter");
      savedFilter.setEnabled(false);
      savedFilter.setOrder(1);

      List<IFilterAction> actions = savedFilter.getActions();
      savedFilter.removeAction(actions.get(0));
      actions.get(1).setData(new Long[] { 3l, 2l, 1l });
      IFilterAction action = fTypesFactory.createFilterAction("org.rssowl.ActionId3");
      action.setData(200);
      savedFilter.addAction(action);

      ISearch savedSearch = savedFilter.getSearch();
      savedSearch.setMatchAllConditions(false);
      List<ISearchCondition> savedConditions = savedSearch.getSearchConditions();

      ISearchCondition condition1 = savedConditions.get(0);
      condition1.setSpecifier(SearchSpecifier.CONTAINS_NOT);
      savedSearch.removeSearchCondition(savedConditions.get(1));

      ISearchCondition condition2 = fTypesFactory.createSearchCondition(fTypesFactory.createSearchField(INews.LOCATION, INews.class.getName()), SearchSpecifier.IS, new Long[][] { { 2l } });
      savedSearch.addSearchCondition(condition2);

      DynamicDAO.save(savedFilter);
      if (gc)
        System.gc();

      filters = DynamicDAO.loadAll(ISearchFilter.class);
      assertEquals(1, filters.size());
      ISearchFilter loadedFilter = filters.iterator().next();
      assertEquals(savedFilter.getName(), loadedFilter.getName());
      assertEquals(savedFilter.getOrder(), loadedFilter.getOrder());
      assertEquals(savedFilter.isEnabled(), loadedFilter.isEnabled());

      List<IFilterAction> loadedActions = loadedFilter.getActions();
      assertEquals(2, loadedActions.size());
      assertEquals("org.rssowl.ActionId2", loadedActions.get(0).getActionId());
      assertEquals(true, Arrays.equals((Object[]) actions.get(1).getData(), new Long[] { 3l, 2l, 1l }));
      assertEquals("org.rssowl.ActionId3", loadedActions.get(1).getActionId());
      assertEquals(200, loadedActions.get(1).getData());

      ISearch loadedSearch = loadedFilter.getSearch();
      assertEquals(savedSearch.matchAllConditions(), loadedSearch.matchAllConditions());
      List<ISearchCondition> loadedConditions = loadedSearch.getSearchConditions();
      assertEquals(2, loadedConditions.size());

      assertEquals(true, ((SearchCondition) loadedConditions.get(0)).isIdentical(condition1));
      assertEquals(true, ((SearchCondition) loadedConditions.get(1)).isIdentical(condition2));
    }

    /* Delete */
    {
      Collection<ISearchFilter> filters = DynamicDAO.loadAll(ISearchFilter.class);
      assertEquals(1, filters.size());

      DynamicDAO.delete(filters.iterator().next());
      if (gc)
        System.gc();

      filters = DynamicDAO.loadAll(ISearchFilter.class);
      assertTrue(filters.isEmpty());
    }
  }

  /**
   * Test add, update, get and delete of ISearchFilter
   */
  @Test
  public void testAddUpdateGetAndDeleteSearchFilter() {
    doTestAddUpdateGetAndDeleteSearchFilter(true);
    doTestAddUpdateGetAndDeleteSearchFilter(false);
  }

  /**
   * Checks: - Addition of category - That ADD event is issued - Updating of
   * category - That UPDATE event is issued - Getting of category
   */
  @Test
  public void testAddUpdateAndGetCategory() {
    final Category[] initialCategory = new Category[1];
    try {
      initialCategory[0] = (Category) createNewsCategory();
    } catch (PersistenceException e) {
      fail(e.getMessage());
    }
    final Category[] updatedCategory = new Category[1];
    final boolean[] categoryAddedCalled = new boolean[] { false };
    final boolean[] categoryUpdatedCalled = new boolean[] { false };
    CategoryListener categoryListener = null;
    try {
      categoryListener = new CategoryAdapter() {
        @Override
        public void entitiesAdded(Set<CategoryEvent> events) {
          CategoryEvent event = events.iterator().next();
          try {
            categoryAddedCalled[0] = true;
            Category dbCategory = (Category) event.getEntity();
            initialCategory[0].setId(dbCategory.getId());
            assertTrue(initialCategory[0].isIdentical(dbCategory));
            dbCategory.setDomain("newDomain/newDomain");
            updatedCategory[0] = dbCategory;
            DynamicDAO.save(dbCategory);
          } catch (PersistenceException e) {
            fail(e.getMessage());
          }
        }

        @Override
        public void entitiesUpdated(Set<CategoryEvent> events) {
          CategoryEvent event = events.iterator().next();
          categoryUpdatedCalled[0] = true;
          Category dbCategory = (Category) event.getEntity();
          assertTrue(updatedCategory[0].isIdentical(dbCategory));
        }
      };
      DynamicDAO.addEntityListener(ICategory.class, categoryListener);
      DynamicDAO.save(initialCategory[0]);
      assertTrue(categoryAddedCalled[0]);
      assertTrue(categoryUpdatedCalled[0]);
      DynamicDAO.delete(updatedCategory[0]);
    } catch (PersistenceException e) {
      fail(e.getMessage());
    } finally {
      if (categoryListener != null) {
        DynamicDAO.removeEntityListener(ICategory.class, categoryListener);
      }
    }
  }

  /**
   * Tests adding, updating and getting a folder with no parent.
   */
  @Test
  public void testAddUpdateAndGetFolder() {
    final Folder initialFolder = (Folder) createFolder();
    final Folder[] updatedFolder = new Folder[1];
    final boolean[] folderAddedCalled = new boolean[] { false };
    final boolean[] folderUpdatedCalled = new boolean[] { false };
    FolderListener folderListener = null;
    try {
      folderListener = new FolderAdapter() {
        @Override
        public void entitiesAdded(Set<FolderEvent> events) {
          for (FolderEvent event : events) {
            try {
              folderAddedCalled[0] = true;
              Folder dbFolder = (Folder) event.getEntity();
              initialFolder.setId(dbFolder.getId());
              assertTrue(NOT_IDENTICAL_MESSAGE, initialFolder.isIdentical(dbFolder));
              dbFolder.setBlogrollLink(createURI("http://www.newuri.com"));
              dbFolder.setName("New name");
              updatedFolder[0] = dbFolder;
              DynamicDAO.save(dbFolder);
            } catch (PersistenceException e) {
              fail(e.getMessage());
            }
          }
        }

        @Override
        public void entitiesUpdated(Set<FolderEvent> events) {
          for (FolderEvent event : events) {
            folderUpdatedCalled[0] = true;
            Folder dbFolder = (Folder) event.getEntity();
            assertTrue(updatedFolder[0].isIdentical(dbFolder));
          }
        }
      };
      DynamicDAO.addEntityListener(IFolder.class, folderListener);
      DynamicDAO.save(initialFolder);
      assertTrue(folderAddedCalled[0]);
      assertTrue(folderUpdatedCalled[0]);
      DynamicDAO.delete(updatedFolder[0]);
    } catch (PersistenceException e) {
      fail(e.getMessage());
    } finally {
      if (folderListener != null) {
        DynamicDAO.removeEntityListener(IFolder.class, folderListener);
      }
    }
  }

  private IFolder createFolderWithParent() throws PersistenceException {
    IFolder parentFolder = createFolder();
    parentFolder = DynamicDAO.save(parentFolder);
    IFolder folder = fTypesFactory.createFolder(null, parentFolder, "MainFolder");
    folder.setBlogrollLink(createURI("http://www.rssowl.com"));
    folder.setProperty("skey", "svalue");
    return folder;

  }

  /**
   * Tests adding, updating and getting a folder that has a parent.
   */
  @Test
  public void testAddUpdateAndGetFolderWithParent() {
    final Folder[] initialFolder = new Folder[1];
    final String[] folderName = new String[1];
    try {
      initialFolder[0] = (Folder) createFolderWithParent();
      folderName[0] = initialFolder[0].getName();
    } catch (PersistenceException e) {
      fail(e.getMessage());
    }
    final Folder[] updatedFolder = new Folder[1];
    final boolean[] folderAddedCalled = new boolean[] { false };
    final boolean[] folderUpdatedCalled = new boolean[] { false };
    FolderListener folderListener = null;
    try {
      folderListener = new FolderAdapter() {
        @Override
        public void entitiesAdded(Set<FolderEvent> events) {
          for (FolderEvent event : events) {
            try {
              folderAddedCalled[0] = true;
              Folder dbFolder = (Folder) event.getEntity();
              initialFolder[0].setId(dbFolder.getId());
              assertTrue(initialFolder[0].isIdentical(dbFolder));
              dbFolder.setBlogrollLink(createURI("http://www.newuri.com"));
              updatedFolder[0] = dbFolder;
              DynamicDAO.save(dbFolder);
            } catch (PersistenceException e) {
              fail(e.getMessage());
            }
          }
        }

        @Override
        public void entitiesUpdated(Set<FolderEvent> events) {
          for (FolderEvent event : events) {
            /* Ignore event from parent */
            if (!folderName[0].equals(event.getEntity().getName())) {
              return;
            }
            folderUpdatedCalled[0] = true;
            Folder dbFolder = (Folder) event.getEntity();
            assertTrue(updatedFolder[0].isIdentical(dbFolder));
          }
        }
      };
      DynamicDAO.addEntityListener(IFolder.class, folderListener);
      DynamicDAO.save(initialFolder[0]);
      assertTrue(folderAddedCalled[0]);
      assertTrue(folderUpdatedCalled[0]);
      DynamicDAO.delete(updatedFolder[0]);
    } catch (PersistenceException e) {
      fail(e.getMessage());
    } finally {
      if (folderListener != null) {
        DynamicDAO.removeEntityListener(IFolder.class, folderListener);
      }
    }
  }

  /**
   * When updating a child folder, the root event should be on the folder where
   * save is called.
   */
  @Test
  public void testRootFolderWhenFolderChildIsUpdated() {
    final Folder[] initialFolder = new Folder[1];
    final String[] folderName = new String[1];
    try {
      initialFolder[0] = (Folder) createFolderWithParent();
      folderName[0] = initialFolder[0].getName();
    } catch (PersistenceException e) {
      fail(e.getMessage());
    }
    FolderListener folderListener = null;
    try {
      folderListener = new FolderAdapter() {
        @Override
        public void entitiesAdded(Set<FolderEvent> events) {
          for (FolderEvent event : events) {
            if (folderName[0].equals(event.getEntity().getName())) {
              assertEquals(true, event.isRoot());
            } else {
              assertEquals(false, event.isRoot());
            }
          }
        }
      };
      DynamicDAO.addEntityListener(IFolder.class, folderListener);
      DynamicDAO.save(initialFolder[0]);
    } catch (PersistenceException e) {
      fail(e.getMessage());
    } finally {
      if (folderListener != null) {
        DynamicDAO.removeEntityListener(IFolder.class, folderListener);
      }
    }
  }

  private IPerson createPersonJohn(IPersistable type) {
    IPerson person = fTypesFactory.createPerson(null, type);
    person.setName("John");
    person.setEmail(createURI("john@hotmail.com"));
    person.setUri(createURI("http://mysite.hotmail.com"));
    person.setProperty("property", "property_value");
    return person;
  }

  URI createURI(String uri) {
    try {
      return new URI(uri);
    } catch (URISyntaxException e) {
      // should not happen;
      return null;
    }
  }

  private IPerson createPersonMary(IPersistable type) {
    IPerson person = fTypesFactory.createPerson(null, type);
    person.setName("Mary");
    person.setEmail(createURI("mary@hotmail.com"));
    person.setUri(createURI("http://mary.hotmail.com"));
    person.setProperty("test", "property");
    return person;
  }

  @SuppressWarnings("unused")
  private IPerson createPersonDan(IPersistable type) {
    IPerson person = fTypesFactory.createPerson(null, type);
    person.setName("Dan");
    person.setEmail(createURI("dan@yahoo.com"));
    return person;
  }

  /**
   * Saves the same feed twice without merging. Should throw an
   * UniqueConstraintException.
   *
   * @throws PersistenceException
   */
  @Test(expected = UniqueConstraintException.class)
  public void testSaveFeedTwice() throws PersistenceException {
    DynamicDAO.save(createFeed());
    DynamicDAO.save(createFeed());
  }

  /**
   * Saves the same feed twice, but merge it before saving it a second time. No
   * exception should be thrown.
   */
  @Test
  public void testSaveFeedTwiceAfterMerging() {
      IFeed savedFeed = DynamicDAO.save(createFeed());
      savedFeed.merge(createFeed());
      DynamicDAO.save(savedFeed);
  }

  /**
   * Tests adding, updating and getting a bookmark.
   */
  @Test
  public void testAddUpdateAndGetBookMark() {
    BookMarkListener bookMarkListener = null;
    try {
      final BookMark initialBookMark = (BookMark) createBookMark();
      final BookMark[] updatedBookMark = new BookMark[1];
      final boolean[] bookMarkAddedCalled = new boolean[] { false };
      final boolean[] bookMarkUpdatedCalled = new boolean[] { false };
      bookMarkListener = new BookMarkAdapter() {
        @Override
        public void entitiesAdded(Set<BookMarkEvent> events) {
          BookMarkEvent event = events.iterator().next();
          try {
            bookMarkAddedCalled[0] = true;
            BookMark dbBookMark = (BookMark) event.getEntity();
            initialBookMark.setId(dbBookMark.getId());
            assertTrue(initialBookMark.isIdentical(dbBookMark));
            dbBookMark.setName("Another name");
            updatedBookMark[0] = dbBookMark;
            DynamicDAO.save(dbBookMark);
          } catch (PersistenceException e) {
            fail(e.getMessage());
          }
        }

        @Override
        public void entitiesUpdated(Set<BookMarkEvent> events) {
          BookMarkEvent event = events.iterator().next();
          bookMarkUpdatedCalled[0] = true;
          BookMark dbBookMark = (BookMark) event.getEntity();
          assertTrue(updatedBookMark[0].isIdentical(dbBookMark));
        }
      };
      DynamicDAO.addEntityListener(IBookMark.class, bookMarkListener);
      DynamicDAO.save(initialBookMark);
      assertTrue(bookMarkAddedCalled[0]);
      assertTrue(bookMarkUpdatedCalled[0]);
    } catch (PersistenceException e) {
      fail(e.getMessage());
    }

    finally {
      if (bookMarkListener != null) {
        DynamicDAO.removeEntityListener(IBookMark.class, bookMarkListener);
      }
    }
  }

  /**
   * Tests adding, updating and getting a ISearchMark.
   */
  @Test
  public void testAddUpdateAndGetSearchMark() {
    SearchMarkListener searchMarkListener = null;
    try {
      final SearchMark initialSearchMark = (SearchMark) createSearchMark();
      final SearchMark[] updatedSearchMark = new SearchMark[1];
      final boolean[] searchMarkAddedCalled = new boolean[] { false };
      final boolean[] searchMarkUpdatedCalled = new boolean[] { false };
      searchMarkListener = new SearchMarkAdapter() {
        @Override
        public void entitiesAdded(Set<SearchMarkEvent> events) {
          SearchMarkEvent event = events.iterator().next();
          try {
            searchMarkAddedCalled[0] = true;
            SearchMark dbSearchMark = (SearchMark) event.getEntity();
            initialSearchMark.setId(dbSearchMark.getId());
            assertTrue(initialSearchMark.isIdentical(dbSearchMark));
            dbSearchMark.setName("Another name");
            updatedSearchMark[0] = dbSearchMark;
            DynamicDAO.save(dbSearchMark);
          } catch (PersistenceException e) {
            fail(e.getMessage());
          }
        }

        @Override
        public void entitiesUpdated(Set<SearchMarkEvent> events) {
          SearchMarkEvent event = events.iterator().next();
          searchMarkUpdatedCalled[0] = true;
          SearchMark dbSearchMark = (SearchMark) event.getEntity();
          assertTrue(updatedSearchMark[0].isIdentical(dbSearchMark));
        }
      };
      DynamicDAO.addEntityListener(ISearchMark.class, searchMarkListener);
      DynamicDAO.save(initialSearchMark);
      assertTrue(searchMarkAddedCalled[0]);
      assertTrue(searchMarkUpdatedCalled[0]);
    } catch (PersistenceException e) {
      fail(e.getMessage());
    }

    finally {
      if (searchMarkListener != null) {
        DynamicDAO.removeEntityListener(ISearchMark.class, searchMarkListener);
      }
    }
  }

  private Date createDate() {
    return new Date();
  }

  private IImage createImage(IFeed feed) {
    IImage image = fTypesFactory.createImage(feed);
    image.setHomepage(createURI("http://www.rssowl.org/image.png"));
    return image;
  }

  /**
   * Tests that saving a news with an attachment stores the attachment
   * correctly.
   */
  @Test
  public void testAddAttachmentToNewsAfterGC() {
    NewsListener newsListener = null;
    try {
      IFeed feed = createFeed();
      INews news = createNews(feed);
      List<IAttachment> attachments = new ArrayList<IAttachment>(news.getAttachments());
      for (IAttachment attachment : attachments)
        news.removeAttachment(attachment);

      DynamicDAO.save(feed);
      NewsReference newsRef = new NewsReference(feed.getNews().get(0).getId());
      fTypesFactory.createAttachment(null, news);
      DynamicDAO.save(feed);
      feed = null;
      news = null;
      System.gc();
      assertEquals(1, newsRef.resolve().getAttachments().size());
      newsListener = new NewsAdapter() {
        @Override
        public void entitiesUpdated(Set<NewsEvent> events) {
          assertEquals(1, events.size());
          int attachmentsSize = events.iterator().next().getOldNews().getAttachments().size();
          assertEquals(1, attachmentsSize);
        }
      };
      DynamicDAO.addEntityListener(INews.class, newsListener);
      DynamicDAO.save(newsRef.resolve());
    } finally {
      if (newsListener != null)
        DynamicDAO.removeEntityListener(INews.class, newsListener);
    }
  }

  /**
   * Tests adding, updating and getting a news.
   */
  @Test
  public void testAddUpdateAndGetNews() {
    final IFeed feed;
    try {
      feed = DynamicDAO.save(createFeed());
    } catch (PersistenceException e) {
      fail(e.getMessage());
      return;
    }
    final News initialNews = (News) createNews(feed);
    final Feed[] initialFeed = new Feed[1];
    final Person[] initialAuthor = new Person[1];
    final Source[] initialSource = new Source[1];
    final Guid[] initialGuid = new Guid[1];
    final News[] updatedNews = new News[1];
    final boolean[] NewsAddedCalled = new boolean[] { false };
    final boolean[] NewsUpdatedCalled = new boolean[] { false };
    NewsListener newsListener = null;
    try {
      newsListener = new NewsAdapter() {
        @Override
        public void entitiesAdded(Set<NewsEvent> events) {
          NewsEvent event = events.iterator().next();
          NewsAddedCalled[0] = true;
          initialFeed[0] = (Feed) initialNews.getFeedReference().resolve();
          initialAuthor[0] = (Person) initialNews.getAuthor();
          initialSource[0] = (Source) initialNews.getSource();
          initialGuid[0] = (Guid) initialNews.getGuid();
          final News dbNews = (News) event.getEntity();
          initialAuthor[0].setId(dbNews.getAuthor().getId());
          initialAuthor[0].isIdentical(dbNews.getAuthor());
          //FIXME Find a way to verify this without changing the id from the
          //event handler
//          initialNews.setId(dbNews.getId());
//          assertTrue(initialNews.isIdentical(dbNews));
        }

        @Override
        public void entitiesUpdated(Set<NewsEvent> events) {
          NewsEvent event = events.iterator().next();
          NewsUpdatedCalled[0] = true;
          News dbNews = (News) event.getEntity();
          assertTrue(updatedNews[0].isIdentical(dbNews));
        }
      };
      DynamicDAO.addEntityListener(INews.class, newsListener);
      INews news = DynamicDAO.save(initialNews);
      news.setDescription("The description has been changed in the news");
      news.setState(State.UNREAD);
      updatedNews[0] = (News) news;
      DynamicDAO.save(news);
      assertTrue(NewsAddedCalled[0]);
      assertTrue(NewsUpdatedCalled[0]);
      DynamicDAO.delete(updatedNews[0]);
    } catch (PersistenceException e) {
      fail(e.getMessage());
    } finally {
      if (newsListener != null) {
        DynamicDAO.removeEntityListener(INews.class, newsListener);
      }
    }
  }

  /**
   * Tests that {@link IFeedDAO#save(IFeed)} cascades the complex types of INews
   * appropriately.
   */
  @Test
  public void testSaveFeedNewsCascadeWithGC() {
    IFeed feed = createFeed();
    INews news = createNews(feed);
    DynamicDAO.save(feed);
    String authorName = news.getAuthor().getName();
    URI attachmentLink = news.getAttachments().get(0).getLink();
    String categoryName = news.getCategories().get(0).getName();
    URI sourceLink = news.getSource().getLink();
    DynamicDAO.save(news);
    NewsReference newsRef = new NewsReference(news.getId());
    feed = null;
    news = null;
    System.gc();
    news = newsRef.resolve();
    assertNewsEquals(categoryName, attachmentLink, authorName, sourceLink, news);
    authorName = authorName + " changed";
    attachmentLink = createURI(attachmentLink.toString() + "/new");
    categoryName = categoryName + " changed";
    sourceLink = createURI(sourceLink.toString() + "/new");
    updateNews(authorName, attachmentLink, categoryName, sourceLink, news);
    DynamicDAO.save(news.getFeedReference().resolve());
    news = null;
    System.gc();
    news = newsRef.resolve();
    assertNewsEquals(categoryName, attachmentLink, authorName, sourceLink, news);
  }

  /**
   * Tests that {@link INewsDAO#save(INews)} cascades the complex types
   * appropriately.
   */
  @Test
  public void testSaveNewsCascadeWithGC() {
    IFeed feed = createFeed();
    DynamicDAO.save(feed);
    INews news = createNews(feed);
    String authorName = news.getAuthor().getName();
    URI attachmentLink = news.getAttachments().get(0).getLink();
    String categoryName = news.getCategories().get(0).getName();
    URI sourceLink = news.getSource().getLink();
    DynamicDAO.save(news);
    NewsReference newsRef = new NewsReference(news.getId());
    feed = null;
    news = null;
    System.gc();
    news = newsRef.resolve();
    assertNewsEquals(categoryName, attachmentLink, authorName, sourceLink, news);
    authorName = authorName + " changed";
    attachmentLink = createURI(attachmentLink.toString() + "/new");
    categoryName = categoryName + " changed";
    sourceLink = createURI(sourceLink.toString() + "/new");
    updateNews(authorName, attachmentLink, categoryName, sourceLink, news);
    DynamicDAO.save(news);
    news = null;
    System.gc();
    news = newsRef.resolve();
    assertNewsEquals(categoryName, attachmentLink, authorName, sourceLink, news);
  }

  /**
   * Tests that {@link DynamicDAO#saveAll(Collection)} cascades the complex
   * types appropriately.
   */
  @Test
  public void testSaveNewsListCascadeWithGC() {
    IFeed feed = createFeed();
    DynamicDAO.save(feed);
    INews news = createNews(feed);
    String authorName = news.getAuthor().getName();
    URI attachmentLink = news.getAttachments().get(0).getLink();
    String categoryName = news.getCategories().get(0).getName();
    URI sourceLink = news.getSource().getLink();
    DynamicDAO.saveAll(Collections.singletonList(news));
    NewsReference newsRef = new NewsReference(news.getId());
    feed = null;
    news = null;
    System.gc();
    news = newsRef.resolve();
    assertNewsEquals(categoryName, attachmentLink, authorName, sourceLink, news);
    authorName = authorName + " changed";
    attachmentLink = createURI(attachmentLink.toString() + "/new");
    categoryName = categoryName + " changed";
    sourceLink = createURI(sourceLink.toString() + "/new");
    updateNews(authorName, attachmentLink, categoryName, sourceLink, news);
    DynamicDAO.saveAll(Collections.singletonList(news));
    news = null;
    System.gc();
    news = newsRef.resolve();
    assertNewsEquals(categoryName, attachmentLink, authorName, sourceLink, news);
  }

  private void updateNews(String authorName, URI attachmentLink, String categoryName, URI sourceLink, INews news) {
    news.getAuthor().setName(authorName);
    news.getAttachments().get(0).setLink(attachmentLink);
    news.getCategories().get(0).setName(categoryName);
    news.getSource().setLink(sourceLink);
  }

  private void assertNewsEquals(String categoryName, URI attachmentLink, String authorName, URI sourceLink, INews news) {
    assertNotNull(news);
    assertEquals(1, news.getCategories().size());
    assertEquals(categoryName, news.getCategories().get(0).getName());
    assertEquals(1, news.getAttachments().size());
    assertEquals(attachmentLink, news.getAttachments().get(0).getLink());
    assertEquals(authorName, news.getAuthor().getName());
    assertEquals(sourceLink, news.getSource().getLink());
  }

  private INews createNews(IFeed feed) {
    INews news = fTypesFactory.createNews(null, feed, createDate());
    IAttachment attachment = fTypesFactory.createAttachment(null, news);
    attachment.setLink(createURI("http://attachmenturi.com"));
    ICategory category = fTypesFactory.createCategory(null, news);
    category.setName("Category name #1");
    news.setAuthor(createPersonMary(news));
    news.setBase(createURI("http://www.someuri.com"));
    news.setComments("One comment");
    news.setState(State.HIDDEN);
    news.setDescription("News description");
    fTypesFactory.createGuid(news, "someGUIDvalue", null);
    news.setLink(createURI("http://www.somelocation.com/feed.rss"));
    news.setModifiedDate(createDate());
    news.setProperty("property", "value");
    news.setPublishDate(createDate());
    ISource source = fTypesFactory.createSource(news);
    source.setLink(createURI("http://www.someuri.com"));
    news.setSource(source);
    news.setTitle("This is the news title");
    news.setRating(70);
    return news;
  }

  private Feed createFeed() {
    return createFeed("http://www.rssowl.org/feed.rss");
  }

  private Feed createFeed(String link) {
    Feed feed = (Feed) fTypesFactory.createFeed(null, createURI(link));
    feed.setTitle("feed title");
    feed.setDescription("feed description");
    feed.setHomepage(createURI("http://www.rssowl.org"));
    feed.setAuthor(createPersonJohn(feed));
    feed.setLanguage("English");
    feed.setCopyright("This feed is copyrighted");
    feed.setDocs(createURI("http://www.rssowl.org/documentation.html"));
    feed.setGenerator("Manual");
    feed.setImage(createImage(feed));
    feed.setPublishDate(createDate());
    feed.setLastBuildDate(createDate());
    feed.setLastModifiedDate(createDate());
    feed.setWebmaster("Webmaster");
    feed.setTTL(60);
    feed.setFormat("RSS");
    feed.setProperty("feedProperty", "randomValue");
    feed.setBase(createURI("http://www.baseuri.com/"));
    return feed;
  }

  /**
   * Saves feed and verifies that the feed still contains the news after being
   * GC'd and reloaded. See bug #261.
   *
   * @throws Exception
   */
  @Test
  public void testFeedRetainsLinkToNewsAfterSave() throws Exception {
    IFeed feed = createFeed();

    /* Need to save this without the news first */
    DynamicDAO.save(feed);

    createNews(feed);
    DynamicDAO.save(feed);

    long feedId = feed.getId();
    feed = null;
    System.gc();
    IFeed savedFeed = DynamicDAO.load(IFeed.class, feedId);
    assertEquals(1, savedFeed.getNews().size());
    DynamicDAO.delete(new FeedReference(feedId).resolve());
  }

  /**
   * @throws Exception
   */
  @Test
  public void testDeleteFeed() throws Exception {
    IFeed feed = createFeed();
    createNews(feed);
    fTypesFactory.createNews(null, feed, new Date());
    DynamicDAO.save(feed);

    FeedReference feedRef = new FeedReference(feed.getId());
    System.gc();
    DynamicDAO.delete(feedRef.resolve());
    assertNull(DynamicDAO.load(IFeed.class, feedRef.getId()));
  }

  /**
   * @throws Exception
   */
  @Test
  public void testDeleteFeedDeletesConditionalGet() throws Exception {
    IFeed feed = createFeed();

    DynamicDAO.save(feed);

    IConditionalGet conditionalGet = fTypesFactory.createConditionalGet("2005/11/04", feed.getLink(), null);

    DynamicDAO.save(conditionalGet);

    DynamicDAO.delete(feed);
    assertNull(DynamicDAO.load(IFeed.class, feed.getId()));
    assertNull(DynamicDAO.getDAO(IConditionalGetDAO.class).load(feed.getLink()));
  }

  /**
   *
   */
  @Test
  public void testAddUpdateAndGetConditionalGet() {
    IFeed feed = createFeed();
    DynamicDAO.save(feed);

    final String ifModifiedSince = "2005/11/04";
    final String ifNoneMatch = "2005/05/12";
    IConditionalGet conditionalGet = fTypesFactory.createConditionalGet(ifModifiedSince, feed.getLink(), ifNoneMatch);
    DynamicDAO.save(conditionalGet);
    conditionalGet = null;
    System.gc();
    conditionalGet = DynamicDAO.getDAO(IConditionalGetDAO.class).load(feed.getLink());
    assertEquals(ifModifiedSince, conditionalGet.getIfModifiedSince());
    assertEquals(ifNoneMatch, conditionalGet.getIfNoneMatch());
    assertEquals(feed.getLink().toString(), conditionalGet.getLink().toString());
  }

  /**
   * Simply adds and deletes a feed and fails if an exception is thrown
   */
  @Test
  public void testAddAndDeleteFeed() {
    try {
      IFeed feed = createFeed();
      feed = DynamicDAO.save(feed);
      DynamicDAO.delete(new FeedReference(feed.getId()).resolve());
      assertNull(DynamicDAO.load(IFeed.class, feed.getId()));
    } catch (PersistenceException e) {
      fail(e.getMessage());
    }
  }

  /**
   * Simply adds and deletes a feed and fails if an exception is thrown
   */
  @Test
  public void testAddAndDeleteFeed2() {
    try {
      IFeed feed = createFeed();
      feed = DynamicDAO.save(feed);
      DynamicDAO.delete(feed);
      assertNull(DynamicDAO.load(IFeed.class, feed.getId()));
    } catch (PersistenceException e) {
      fail(e.getMessage());
    }
  }

  /**
   * Tests adding, updating and getting a feed.
   */
  // TODO Need to test news and categories
  @Test
  public void testAddUpdateAndGetFeed() {
    final Feed initialFeed = createFeed();
    final Person[] initialAuthor = new Person[1];
    final Image[] initialImage = new Image[1];
    final Feed[] updatedFeed = new Feed[1];
    final boolean[] feedAddedCalled = new boolean[] { false };
    final boolean[] feedUpdatedCalled = new boolean[] { false };
    FeedListener feedListener = null;
    try {
      feedListener = new FeedAdapter() {
        @Override
        public void entitiesAdded(Set<FeedEvent> events) {
          FeedEvent event = events.iterator().next();
          try {
            feedAddedCalled[0] = true;
            initialAuthor[0] = (Person) initialFeed.getAuthor();
            initialImage[0] = (Image) initialFeed.getImage();
            Feed dbFeed = (Feed) event.getEntity();
            initialAuthor[0].setId(dbFeed.getAuthor().getId());
            initialAuthor[0].isIdentical(dbFeed.getAuthor());
            initialImage[0].equals(dbFeed.getImage());
            initialFeed.setId(dbFeed.getId());
            assertTrue(NOT_IDENTICAL_MESSAGE, initialFeed.isIdentical(dbFeed));
            dbFeed.setCopyright("GPL");
            dbFeed.setFormat("someDifferentformat");
            dbFeed.getImage().setHeight(150);
            dbFeed.getImage().setDescription("Some new description");
            dbFeed.getImage().setTitle("yet another title");
            updatedFeed[0] = dbFeed;
            DynamicDAO.save(dbFeed);
          } catch (PersistenceException e) {
            fail(e.getMessage());
          }
        }

        @Override
        public void entitiesUpdated(Set<FeedEvent> events) {
          FeedEvent event = events.iterator().next();
          feedUpdatedCalled[0] = true;
          Feed dbFeed = (Feed) event.getEntity();
          assertTrue(updatedFeed[0].isIdentical(dbFeed));
        }
      };
      DynamicDAO.addEntityListener(IFeed.class, feedListener);
      DynamicDAO.save(initialFeed);
      assertTrue(feedAddedCalled[0]);
      assertTrue(feedUpdatedCalled[0]);
      assertTrue(DynamicDAO.exists(IFeed.class, initialFeed.getId()));
      DynamicDAO.delete(updatedFeed[0]);
    } catch (PersistenceException e) {
      fail(e.getMessage());
    } finally {
      if (feedListener != null) {
        DynamicDAO.removeEntityListener(IFeed.class, feedListener);
      }
    }
  }

  /**
   * Tests that the image attribute override solves the name clashes between
   * certain column names in Feed and Image.
   */
  @Test
  public void testImageAttributeOverride() {
    final Feed initialFeed = createFeed();
    final URI feedLink = initialFeed.getLink();
    IImage image = fTypesFactory.createImage(initialFeed);
    image.setHomepage(createURI("http://www.someuri.com"));
    initialFeed.setImage(image);
    initialFeed.getImage().setDescription("Some description");
    initialFeed.getImage().setTitle("Title");
    initialFeed.getImage().setHomepage(createURI("http://www.imageuri.com"));
    final Feed[] updatedFeed = new Feed[1];
    FeedListener feedListener = null;
    try {
      feedListener = new FeedAdapter() {
        @Override
        public void entitiesAdded(Set<FeedEvent> events) {
          try {
            FeedEvent event = events.iterator().next();
            Feed dbFeed = (Feed) event.getEntity();
            dbFeed.setDescription("feed description2");
            dbFeed.setTitle("feed title2");
            dbFeed.getImage().setDescription("Some new description");
            dbFeed.getImage().setTitle("yet another title");
            dbFeed.getImage().setHomepage(createURI("http://www.newimageuri.com"));
            updatedFeed[0] = dbFeed;
            DynamicDAO.save(dbFeed);
          } catch (PersistenceException e) {
            TestUtils.fail(e);
          }
        }

        @Override
        public void entitiesUpdated(Set<FeedEvent> events) {
          try {
            FeedEvent event = events.iterator().next();
            // TODO Add method to load entities without using the cache and
            // then use it here. Atm, this test won't actually show the bug
            // we want it to show because it's getting the feed from the cache
            // bypassing the db inconsistency
            Feed dbFeed = (Feed) event.getEntity();
            assertEquals(feedLink.toString(), dbFeed.getLink().toString());
          } catch (RuntimeException re) {
            fail(re.getMessage());
          }
        }

      };
      DynamicDAO.addEntityListener(IFeed.class, feedListener);
      DynamicDAO.save(initialFeed);
      DynamicDAO.delete(updatedFeed[0]);
    } catch (PersistenceException e) {
      fail(e.getMessage());
    } finally {
      if (feedListener != null) {
        DynamicDAO.removeEntityListener(IFeed.class, feedListener);
      }
    }
  }

  /**
   * Tests that no event is sent when NewsManager#setState() is called and the
   * new state is the same as the old state.
   */
  @Test
  public void testNewsManagerSetSameStateWithQuery() {
    final IFeed feed;
    try {
      feed = DynamicDAO.save(createFeed());
    } catch (PersistenceException e) {
      fail(e.getMessage());
      return;
    }
    final News initialNews = (News) createNews(feed);
    initialNews.setState(State.NEW);
    INews news = null;
    try {
      news = DynamicDAO.save(initialNews);
    } catch (PersistenceException e) {
      fail(e.getMessage());
      return;
    }
    NewsListener newsListener = null;
    try {
      newsListener = new NewsListener() {
        public void entitiesAdded(Set<NewsEvent> events) {
          fail("No events should have been fired, but NewsListener#entitiesAdded() was called");
        }

        public void entitiesDeleted(Set<NewsEvent> events) {
          fail("No events should have been fired, but NewsListener#newsDeleted() was called.");
        }

        public void entitiesUpdated(Set<NewsEvent> events) {
          fail("No events should have been fired, but NewsListener#newsUpdated() was called.");
        }
      };
      DynamicDAO.addEntityListener(INews.class, newsListener);
      List<INews> newsList = new ArrayList<INews>();
      newsList.add(news);
      Owl.getPersistenceService().getDAOService().getNewsDAO().setState(newsList, State.NEW, true, false);
      DynamicDAO.removeEntityListener(INews.class, newsListener);
      DynamicDAO.delete(news);
      DynamicDAO.delete(feed);
    } catch (PersistenceException e) {
      fail(e.getMessage());
    } finally {
      if (newsListener != null) {
        DynamicDAO.removeEntityListener(INews.class, newsListener);
      }
    }
  }

  /**
   * Tests {@link INewsDAO#setState(Collection, State, boolean, boolean)}
   */
  @Test
  public void testNewsManagerSetState() {
    final IFeed feed;
    feed = DynamicDAO.save(createFeed());
    final News initialNews = (News) createNews(feed);
    initialNews.setState(State.NEW);
    INews newsItem = null;
    NewsReference newsRef = null;
    newsItem = DynamicDAO.save(initialNews);
    newsRef = new NewsReference(newsItem.getId());
    List<INews> newsList = new ArrayList<INews>();
    newsList.add(newsItem);
    Owl.getPersistenceService().getDAOService().getNewsDAO().setState(newsList, State.UPDATED, true, false);
    INews news = newsRef.resolve();
    assertEquals(State.UPDATED, news.getState());
    Owl.getPersistenceService().getDAOService().getNewsDAO().setState(newsList, State.DELETED, true, false);
    news = newsRef.resolve();
    assertEquals(State.DELETED, news.getState());
    Owl.getPersistenceService().getDAOService().getNewsDAO().setState(newsList, State.HIDDEN, false, false);
    news = newsRef.resolve();
    assertEquals(State.HIDDEN, news.getState());
    Owl.getPersistenceService().getDAOService().getNewsDAO().setState(newsList, State.READ, false, false);
    news = newsRef.resolve();
    assertEquals(State.READ, news.getState());
    Owl.getPersistenceService().getDAOService().getNewsDAO().setState(newsList, State.UNREAD, true, false);
    news = newsRef.resolve();
    assertEquals(State.UNREAD, news.getState());
    Owl.getPersistenceService().getDAOService().getNewsDAO().setState(newsList, State.NEW, true, false);
    news = newsRef.resolve();
    assertEquals(State.NEW, news.getState());
    // Make sure it doesn't change when we set it to the same
    Owl.getPersistenceService().getDAOService().getNewsDAO().setState(newsList, State.NEW, true, false);
    news = newsRef.resolve();
    assertEquals(State.NEW, news.getState());

    DynamicDAO.delete(newsRef.resolve());
    DynamicDAO.delete(feed);
  }

  /**
   * Tests that affectEquivalentNews is not supported when the news is not
   * visible.
   */
  @Test(expected = IllegalArgumentException.class)
  public void testNewsDAOSetStateFromDeletedWithAffectEquivalentNews() {
    final IFeed feed;
    feed = DynamicDAO.save(createFeed());
    final News initialNews = (News) createNews(feed);
    initialNews.setState(State.NEW);
    INews newsItem = null;
    NewsReference newsRef = null;
    newsItem = DynamicDAO.save(initialNews);
    newsRef = new NewsReference(newsItem.getId());
    List<INews> newsList = new ArrayList<INews>();
    newsList.add(newsItem);
    Owl.getPersistenceService().getDAOService().getNewsDAO().setState(newsList, State.DELETED, true, false);
    INews news = newsRef.resolve();
    assertEquals(State.DELETED, news.getState());
    Owl.getPersistenceService().getDAOService().getNewsDAO().setState(newsList, State.HIDDEN, true, false);
  }

  /**
   * Tests that affectEquivalentNews is not supported when the news is not
   * visible.
   */
  @Test(expected = IllegalArgumentException.class)
  public void testNewsDAOSetStateFromHiddenWithAffectEquivalentNews() {
    final IFeed feed;
    feed = DynamicDAO.save(createFeed());
    final News initialNews = (News) createNews(feed);
    initialNews.setState(State.NEW);
    INews newsItem = null;
    NewsReference newsRef = null;
    newsItem = DynamicDAO.save(initialNews);
    newsRef = new NewsReference(newsItem.getId());
    List<INews> newsList = new ArrayList<INews>();
    newsList.add(newsItem);
    Owl.getPersistenceService().getDAOService().getNewsDAO().setState(newsList, State.HIDDEN, true, false);
    INews news = newsRef.resolve();
    assertEquals(State.HIDDEN, news.getState());
    Owl.getPersistenceService().getDAOService().getNewsDAO().setState(newsList, State.READ, true, false);
  }

  /**
   * Tests that the folders inside the folder are returned from the db in the
   * same order as they were saved.
   *
   * @throws PersistenceException
   */
  @Test
  public void testFoldersOrder() throws PersistenceException {
    IFolder root = createFolder();
    root = DynamicDAO.save(root);
    for (int i = 0; i < 10; ++i) {
      IFolder child = createFolder(root);
      child.setName(String.valueOf(i));
    }
    root = DynamicDAO.save(root);
    int counter = 0;
    for (IFolder child : root.getFolders()) {
      String name = String.valueOf(counter++);
      assertEquals(name, child.getName());
    }
  }

  /**
   * Tests that the marks inside the folder are returned from the db in the same
   * order as they were saved.
   *
   * @throws PersistenceException
   */
  @Test
  public void testMarksOrder() throws PersistenceException {
    IFolder root = createFolder();
    root = DynamicDAO.save(root);
    int count = 10;
    for (int i = 0; i < count; ++i) {
      IBookMark child = createBookMark(root);
      child.setName(String.valueOf(i));
    }
    int count2 = count * 2;
    for (int i = count; i < count2; ++i) {
      ISearchMark child = createSearchMark(root);
      child.setName(String.valueOf(i));
    }
    root = DynamicDAO.save(root);
    int counter = 0;
    for (IMark child : root.getMarks()) {
      String name = String.valueOf(counter++);
      assertEquals(name, child.getName());
    }
  }

  private ISearchMark createSearchMark(IFolder folder) {
    ISearchMark mark = fTypesFactory.createSearchMark(null, folder, "SomeName");
    mark.setCreationDate(new Date());
    mark.setLastVisitDate(createDate());
    mark.setPopularity(50);
    ISearchField field1 = fTypesFactory.createSearchField(INews.STATE, INews.class.getName());
    fTypesFactory.createSearchCondition(null, mark, field1, SearchSpecifier.IS, State.NEW);

    ISearchField field2 = fTypesFactory.createSearchField(INews.STATE, INews.class.getName());
    fTypesFactory.createSearchCondition(null, mark, field2, SearchSpecifier.IS, State.UPDATED);

    ISearchField field3 = fTypesFactory.createSearchField(INews.STATE, INews.class.getName());
    fTypesFactory.createSearchCondition(null, mark, field3, SearchSpecifier.IS, State.UNREAD);

    return mark;
  }

  /**
   * Tests that {@link INewsDAO#setState(Collection, State, boolean, boolean)}
   * changes the state in news that have the same link but are in different
   * feeds.
   */
  @Test
  public void testNewsManagerSetStateWithMultipleFeedsAndGuidNull() {
    final IFeed feed1;
    IFeed feed2;
    NewsAdapter newsAdapter;
    try {
      feed1 = DynamicDAO.save(createFeed());
      IFeed tempFeed = createFeed("http://adifferentlink.com");
      tempFeed.setTitle("A different title");
      feed2 = DynamicDAO.save(tempFeed);
    } catch (PersistenceException e) {
      fail(e.getMessage());
      return;
    }

    final NewsReference[] newsRef = new NewsReference[1];
    newsAdapter = new NewsAdapter() {
      @Override
      public void entitiesAdded(Set<NewsEvent> events) {
        assertEquals(1, events.size());
        newsRef[0] = new NewsReference(events.iterator().next().getEntity().getId());
      }
    };
    DynamicDAO.addEntityListener(INews.class, newsAdapter);

    final News initialNews1 = (News) createNews(feed1);
    initialNews1.setGuid(null);
    initialNews1.setState(State.NEW);
    final News initialNews2 = (News) createNews(feed2);
    initialNews2.setGuid(null);
    initialNews2.setState(State.NEW);

    INews newsItem1 = null;
    NewsReference newsRef1 = null;

    INews newsItem2 = null;
    NewsReference newsRef2 = null;

    NewsReference newsRef3 = null;
    try {
      DynamicDAO.save(feed1);
      newsRef1 = newsRef[0];
      newsItem1 = newsRef1.resolve();

      feed2 = DynamicDAO.save(feed2);
      newsRef2 = newsRef[0];
      newsItem2 = newsRef2.resolve();

      final News initialNews3 = (News) createNews(feed2);
      initialNews3.setTitle("Some other title");
      initialNews3.setGuid(null);
      initialNews3.setLink(null);
      initialNews3.setState(State.NEW);
      DynamicDAO.save(feed2);
      newsRef3 = newsRef[0];
    } catch (PersistenceException e) {
      fail(e.getMessage());
      return;
    } finally {
      DynamicDAO.removeEntityListener(INews.class, newsAdapter);
    }
    try {
      List<INews> newsList1 = new ArrayList<INews>();
      newsList1.add(newsItem1);

      List<INews> newsList2 = new ArrayList<INews>();
      newsList2.add(newsItem2);

      Owl.getPersistenceService().getDAOService().getNewsDAO().setState(newsList1, State.UPDATED, true, false);
      INews news1 = newsRef1.resolve();
      INews news2 = newsRef2.resolve();
      INews news3 = newsRef3.resolve();
      assertEquals(State.UPDATED, news1.getState());
      assertEquals(State.UPDATED, news2.getState());
      assertEquals(State.NEW, news3.getState());

      Owl.getPersistenceService().getDAOService().getNewsDAO().setState(newsList2, State.READ, true, false);
      news1 = newsRef1.resolve();
      news2 = newsRef2.resolve();
      news3 = newsRef3.resolve();
      assertEquals(State.READ, news1.getState());
      assertEquals(State.READ, news2.getState());
      assertEquals(State.NEW, news3.getState());

      Owl.getPersistenceService().getDAOService().getNewsDAO().setState(newsList1, State.UNREAD, true, false);
      news1 = newsRef1.resolve();
      news2 = newsRef2.resolve();
      news3 = newsRef3.resolve();
      assertEquals(State.UNREAD, news1.getState());
      assertEquals(State.UNREAD, news2.getState());
      assertEquals(State.NEW, news3.getState());

      Owl.getPersistenceService().getDAOService().getNewsDAO().setState(newsList2, State.READ, true, false);
      news1 = newsRef1.resolve();
      news2 = newsRef2.resolve();
      news3 = newsRef3.resolve();
      assertEquals(State.READ, news1.getState());
      assertEquals(State.READ, news2.getState());
      assertEquals(State.NEW, news3.getState());

      Owl.getPersistenceService().getDAOService().getNewsDAO().setState(newsList1, State.UNREAD, true, false);
      news1 = newsRef1.resolve();
      news2 = newsRef2.resolve();
      news3 = newsRef3.resolve();
      assertEquals(State.UNREAD, news1.getState());
      assertEquals(State.UNREAD, news2.getState());
      assertEquals(State.NEW, news3.getState());

      Owl.getPersistenceService().getDAOService().getNewsDAO().setState(newsList2, State.NEW, true, false);
      news1 = newsRef1.resolve();
      news2 = newsRef2.resolve();
      news3 = newsRef3.resolve();
      assertEquals(State.NEW, news1.getState());
      assertEquals(State.NEW, news2.getState());
      assertEquals(State.NEW, news3.getState());

      // Make sure it doesn't change when we set it to the same
      Owl.getPersistenceService().getDAOService().getNewsDAO().setState(newsList1, State.NEW, true, false);
      news1 = newsRef1.resolve();
      news2 = newsRef2.resolve();
      news3 = newsRef3.resolve();
      assertEquals(State.NEW, news1.getState());
      assertEquals(State.NEW, news2.getState());
      assertEquals(State.NEW, news3.getState());

      Owl.getPersistenceService().getDAOService().getNewsDAO().setState(newsList1, State.HIDDEN, true, false);
      news1 = newsRef1.resolve();
      news2 = newsRef2.resolve();
      news3 = newsRef3.resolve();
      assertEquals(State.HIDDEN, news1.getState());
      assertEquals(State.HIDDEN, news2.getState());
      assertEquals(State.NEW, news3.getState());

      DynamicDAO.delete(newsRef1.resolve());
      DynamicDAO.delete(newsRef2.resolve());
      DynamicDAO.delete(feed1);
      DynamicDAO.delete(feed2);
    } catch (PersistenceException e) {
      fail(e.getMessage());
    }
  }

  /**
   * Tests that {@link INewsDAO#setState(Collection, State, boolean, boolean)}
   * changes the state in news that have the same guid but are in different
   * feeds.
   */
  @Test
  public void testNewsManagerSetStateWithMultipleFeeds() {
    final IFeed feed1;
    IFeed feed2;
    NewsListener newsAdapter = null;
    try {
      feed1 = DynamicDAO.save(createFeed());
      IFeed tempFeed = createFeed("http://adifferentlink.com");
      tempFeed.setTitle("A different title");
      feed2 = DynamicDAO.save(tempFeed);
    } catch (PersistenceException e) {
      fail(e.getMessage());
      return;
    }
    final News initialNews1 = (News) createNews(feed1);
    initialNews1.setState(State.NEW);
    final News initialNews2 = (News) createNews(feed2);
    initialNews2.setState(State.NEW);

    final NewsReference[] newsRef = new NewsReference[1];

    newsAdapter = new NewsAdapter() {
      @Override
      public void entitiesAdded(Set<NewsEvent> events) {
        assertEquals(1, events.size());
        newsRef[0] = new NewsReference(events.iterator().next().getEntity().getId());
      }
    };
    DynamicDAO.addEntityListener(INews.class, newsAdapter);
    INews newsItem1 = null;
    NewsReference newsRef1 = null;

    INews newsItem2 = null;
    NewsReference newsRef2 = null;

    NewsReference newsRef3 = null;
    try {
      DynamicDAO.save(feed1);
      newsRef1 = newsRef[0];
      newsItem1 = newsRef1.resolve();

      feed2 = DynamicDAO.save(feed2);
      newsRef2 = newsRef[0];
      newsItem2 = newsRef2.resolve();

      final News initialNews3 = (News) createNews(feed2);
      initialNews3.setTitle("Some other title");
      initialNews3.setGuid(null);
      initialNews3.setLink(null);
      initialNews3.setState(State.NEW);
      DynamicDAO.save(feed2);
      newsRef3 = newsRef[0];
    } catch (PersistenceException e) {
      fail(e.getMessage());
      return;
    } finally {
      DynamicDAO.removeEntityListener(INews.class, newsAdapter);
    }
    try {
      List<INews> newsList1 = new ArrayList<INews>();
      newsList1.add(newsItem1);

      List<INews> newsList2 = new ArrayList<INews>();
      newsList2.add(newsItem2);

      Owl.getPersistenceService().getDAOService().getNewsDAO().setState(newsList1, State.UPDATED, true, false);
      INews news1 = newsRef1.resolve();
      INews news2 = newsRef2.resolve();
      INews news3 = newsRef3.resolve();
      assertEquals(State.UPDATED, news1.getState());
      assertEquals(State.UPDATED, news2.getState());
      assertEquals(State.NEW, news3.getState());

      Owl.getPersistenceService().getDAOService().getNewsDAO().setState(newsList2, State.READ, true, false);
      news1 = newsRef1.resolve();
      news2 = newsRef2.resolve();
      news3 = newsRef3.resolve();
      assertEquals(State.READ, news1.getState());
      assertEquals(State.READ, news2.getState());
      assertEquals(State.NEW, news3.getState());

      Owl.getPersistenceService().getDAOService().getNewsDAO().setState(newsList1, State.UNREAD, true, false);
      news1 = newsRef1.resolve();
      news2 = newsRef2.resolve();
      news3 = newsRef3.resolve();
      assertEquals(State.UNREAD, news1.getState());
      assertEquals(State.UNREAD, news2.getState());
      assertEquals(State.NEW, news3.getState());

      Owl.getPersistenceService().getDAOService().getNewsDAO().setState(newsList2, State.READ, true, false);
      news1 = newsRef1.resolve();
      news2 = newsRef2.resolve();
      news3 = newsRef3.resolve();
      assertEquals(State.READ, news1.getState());
      assertEquals(State.READ, news2.getState());
      assertEquals(State.NEW, news3.getState());

      Owl.getPersistenceService().getDAOService().getNewsDAO().setState(newsList1, State.UNREAD, true, false);
      news1 = newsRef1.resolve();
      news2 = newsRef2.resolve();
      news3 = newsRef3.resolve();
      assertEquals(State.UNREAD, news1.getState());
      assertEquals(State.UNREAD, news2.getState());
      assertEquals(State.NEW, news3.getState());

      Owl.getPersistenceService().getDAOService().getNewsDAO().setState(newsList2, State.NEW, true, false);
      news1 = newsRef1.resolve();
      news2 = newsRef2.resolve();
      news3 = newsRef3.resolve();
      assertEquals(State.NEW, news1.getState());
      assertEquals(State.NEW, news2.getState());
      assertEquals(State.NEW, news3.getState());

      // Make sure it doesn't change when we set it to the same
      Owl.getPersistenceService().getDAOService().getNewsDAO().setState(newsList1, State.NEW, true, false);
      news1 = newsRef1.resolve();
      news2 = newsRef2.resolve();
      news3 = newsRef3.resolve();
      assertEquals(State.NEW, news1.getState());
      assertEquals(State.NEW, news2.getState());
      assertEquals(State.NEW, news3.getState());

      Owl.getPersistenceService().getDAOService().getNewsDAO().setState(newsList1, State.DELETED, true, false);
      news1 = newsRef1.resolve();
      news2 = newsRef2.resolve();
      news3 = newsRef3.resolve();
      assertEquals(State.DELETED, news1.getState());
      assertEquals(State.DELETED, news2.getState());
      assertEquals(State.NEW, news3.getState());

      DynamicDAO.delete(newsRef1.resolve());
      DynamicDAO.delete(newsRef2.resolve());
      DynamicDAO.delete(feed1);
      DynamicDAO.delete(feed2);
    } catch (PersistenceException e) {
      fail(e.getMessage());
    }
  }

  /**
   * Tests {@link INewsDAO#setState(Collection, State, boolean, boolean)}.
   */
  @Test
  public void testNewsManagerSetStateWithGuidNull() {
    final IFeed feed = DynamicDAO.save(createFeed());
    final News initialNews = (News) createNews(feed);
    initialNews.setState(State.NEW);
    initialNews.setGuid(null);
    INews newsItem = null;
    NewsReference newsRef = null;
    newsItem = DynamicDAO.save(initialNews);
    newsRef = new NewsReference(newsItem.getId());
    List<INews> newsList = new ArrayList<INews>();
    newsList.add(newsItem);
    Owl.getPersistenceService().getDAOService().getNewsDAO().setState(newsList, State.UPDATED, true, false);
    INews news = newsRef.resolve();
    assertEquals(State.UPDATED, news.getState());
    Owl.getPersistenceService().getDAOService().getNewsDAO().setState(newsList, State.READ, true, false);
    news = newsRef.resolve();
    assertEquals(State.READ, news.getState());
    Owl.getPersistenceService().getDAOService().getNewsDAO().setState(newsList, State.UNREAD, true, false);
    news = newsRef.resolve();
    assertEquals(State.UNREAD, news.getState());
    Owl.getPersistenceService().getDAOService().getNewsDAO().setState(newsList, State.READ, true, false);
    news = newsRef.resolve();
    assertEquals(State.READ, news.getState());
    Owl.getPersistenceService().getDAOService().getNewsDAO().setState(newsList, State.UNREAD, true, false);
    news = newsRef.resolve();
    assertEquals(State.UNREAD, news.getState());
    Owl.getPersistenceService().getDAOService().getNewsDAO().setState(newsList, State.NEW, true, false);
    news = newsRef.resolve();
    assertEquals(State.NEW, news.getState());
    // Make sure it doesn't change when we set it to the same
    Owl.getPersistenceService().getDAOService().getNewsDAO().setState(newsList, State.NEW, true, false);
    news = newsRef.resolve();
    assertEquals(State.NEW, news.getState());
    Owl.getPersistenceService().getDAOService().getNewsDAO().setState(newsList, State.DELETED, true, false);
    news = newsRef.resolve();
    assertEquals(State.DELETED, news.getState());
    Owl.getPersistenceService().getDAOService().getNewsDAO().setState(newsList, State.READ, false, false);
    news = newsRef.resolve();
    assertEquals(State.READ, news.getState());
    Owl.getPersistenceService().getDAOService().getNewsDAO().setState(newsList, State.HIDDEN, false, false);
    news = newsRef.resolve();
    assertEquals(State.HIDDEN, news.getState());

    DynamicDAO.delete(newsRef.resolve());
    DynamicDAO.delete(feed);
  }

  /**
   * Tests {@link NewsCounterService} for added news that are DELETED
   */
  @Test
  public void testNewsCounterOnDeletedAddedNews() {
    final IFeed feed = DynamicDAO.save(createFeed());
    INews news = createNews(feed);
    news.setFlagged(true);
    news.setState(INews.State.DELETED);

    DynamicDAO.save(news);

    IFolder folder = Owl.getModelFactory().createFolder(null, null, "Root");
    IBookMark bookmark = Owl.getModelFactory().createBookMark(null, folder, new FeedLinkReference(feed.getLink()), "Bookmark");

    DynamicDAO.save(bookmark);

    assertEquals(0, bookmark.getStickyNewsCount());
    assertEquals(0, bookmark.getNewsCount(INews.State.getVisible()));
  }

  /**
   * Tests {@link NewsCounterService} for added news that are DELETED and Sticky
   */
  @Test
  public void testNewsCounterOnStickyDeletedNews() {
    final IFeed feed = DynamicDAO.save(createFeed());
    INews news = createNews(feed);
    news.setState(INews.State.NEW);
    news.setFlagged(true);

    DynamicDAO.save(news);

    IFolder folder = Owl.getModelFactory().createFolder(null, null, "Root");
    IBookMark bookmark = Owl.getModelFactory().createBookMark(null, folder, new FeedLinkReference(feed.getLink()), "Bookmark");

    DynamicDAO.save(bookmark);

    assertEquals(1, bookmark.getStickyNewsCount());
    assertEquals(1, bookmark.getNewsCount(INews.State.getVisible()));

    DynamicDAO.getDAO(INewsDAO.class).setState(Collections.singleton(news), INews.State.HIDDEN, false, false);

    assertEquals(0, bookmark.getStickyNewsCount());
    assertEquals(0, bookmark.getNewsCount(INews.State.getVisible()));

    DynamicDAO.getDAO(INewsDAO.class).setState(Collections.singleton(news), INews.State.DELETED, false, false);

    assertEquals(0, bookmark.getStickyNewsCount());
    assertEquals(0, bookmark.getNewsCount(INews.State.getVisible()));
  }
}
TOP

Related Classes of org.rssowl.core.tests.model.DBManagerTest

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.