Package org.olat.modules.webFeed.managers

Source Code of org.olat.modules.webFeed.managers.FeedManagerImpl

/**
* OLAT - Online Learning and Training<br>
* http://www.olat.org
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); <br>
* you may not use this file except in compliance with the License.<br>
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing,<br>
* software distributed under the License is distributed on an "AS IS" BASIS, <br>
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
* See the License for the specific language governing permissions and <br>
* limitations under the License.
* <p>
* Copyright (c) frentix GmbH<br>
* http://www.frentix.com<br>
* <p>
*/
package org.olat.modules.webFeed.managers;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.olat.core.commons.modules.bc.FolderConfig;
import org.olat.core.commons.services.commentAndRating.CommentAndRatingService;
import org.olat.core.gui.components.form.flexible.elements.FileElement;
import org.olat.core.gui.media.MediaResource;
import org.olat.core.id.Identity;
import org.olat.core.id.OLATResourceable;
import org.olat.core.logging.AssertException;
import org.olat.core.logging.OLog;
import org.olat.core.logging.Tracing;
import org.olat.core.service.ServiceFactory;
import org.olat.core.util.StringHelper;
import org.olat.core.util.ZipUtil;
import org.olat.core.util.cache.n.CacheWrapper;
import org.olat.core.util.coordinate.CoordinatorManager;
import org.olat.core.util.coordinate.LockResult;
import org.olat.core.util.coordinate.SyncerCallback;
import org.olat.core.util.coordinate.SyncerExecutor;
import org.olat.core.util.resource.OresHelper;
import org.olat.core.util.vfs.LocalFileImpl;
import org.olat.core.util.vfs.LocalFolderImpl;
import org.olat.core.util.vfs.VFSContainer;
import org.olat.core.util.vfs.VFSItem;
import org.olat.core.util.vfs.VFSLeaf;
import org.olat.core.util.vfs.VFSMediaResource;
import org.olat.core.util.xml.XStreamHelper;
import org.olat.fileresource.FileResourceManager;
import org.olat.fileresource.types.BlogFileResource;
import org.olat.fileresource.types.FeedFileResource;
import org.olat.fileresource.types.PodcastFileResource;
import org.olat.modules.webFeed.RSSFeed;
import org.olat.modules.webFeed.SyndFeedMediaResource;
import org.olat.modules.webFeed.dispatching.FeedMediaDispatcher;
import org.olat.modules.webFeed.models.Enclosure;
import org.olat.modules.webFeed.models.Feed;
import org.olat.modules.webFeed.models.Item;
import org.olat.repository.RepositoryEntry;
import org.olat.repository.RepositoryManager;
import org.olat.repository.controllers.RepositoryEntryImageController;
import org.olat.resource.OLATResource;
import org.olat.resource.OLATResourceManager;

import com.sun.syndication.feed.synd.SyndContent;
import com.sun.syndication.feed.synd.SyndEnclosure;
import com.sun.syndication.feed.synd.SyndEntry;
import com.sun.syndication.feed.synd.SyndFeed;
import com.sun.syndication.feed.synd.SyndImage;
import com.sun.syndication.io.FeedException;
import com.sun.syndication.io.ParsingFeedException;
import com.sun.syndication.io.SyndFeedInput;
import com.sun.syndication.io.XmlReader;
import com.thoughtworks.xstream.XStream;

/**
* This is the actual feed manager implementation. It handles all operations on
* the various feeds and items.
*
* <P>
* Initial Date: Feb 17, 2009 <br>
*
* @author Gregor Wassmann
*/
public class FeedManagerImpl extends FeedManager {

  public static final String ITEMS_DIR = "items";

  private static final String FEED_FILE_NAME = "feed.xml";
  private static final OLog log = Tracing.createLoggerFor(FeedManagerImpl.class);
  private static final String ITEM_FILE_NAME = "item.xml";
  private static final String MEDIA_DIR = "media";

  public static final XStream xstream;
  static {
    xstream = new XStream();
    xstream.alias("feed", Feed.class);
    xstream.alias("item", Item.class);
  }

  // Better perfermance when protected (apparently)
  protected CacheWrapper feedCache;

  /**
   * Please use FeedManager.getInstance()!!! This constructor is ONLY intended
   * for JUnit tests.
   */
  public FeedManagerImpl() {
  // Make default constructor public for jUnit tests.
  }
 
  /**
   * Creates a blank feed object and writes it to the (virtual) file system
   *
   * @see org.olat.modules.webFeed.managers.FeedManager#createPodcastResource()
   */
  public OLATResourceable createPodcastResource() {
    FeedFileResource podcastResource = new PodcastFileResource();
    return createFeedResource(podcastResource);
  }

  /**
   * Creates a blank feed object and writes it to the file system
   *
   * @see org.olat.modules.webFeed.managers.FeedManager#createPodcastResource()
   */
  public OLATResourceable createBlogResource() {
    FeedFileResource blogResource = new BlogFileResource();
    return createFeedResource(blogResource);
  }

  /**
   * @param feedResource
   * @return The feed resourcable after creation on file system
   */
  private OLATResourceable createFeedResource(FeedFileResource feedResource) {
    OLATResourceManager rm = OLATResourceManager.getInstance();
    OLATResource ores = rm.createOLATResourceInstance(feedResource);
    rm.saveOLATResource(ores);
    Feed feed = new Feed(feedResource);
    VFSContainer podcastContainer = getFeedContainer(feedResource);
    VFSLeaf leaf = podcastContainer.createChildLeaf(FEED_FILE_NAME);
    podcastContainer.createChildContainer(MEDIA_DIR);
    podcastContainer.createChildContainer(ITEMS_DIR);
    XStreamHelper.writeObject(xstream, leaf, feed);
    return feedResource;
  }

  /**
   * Instanciates or just returns the feed cache. (Protected for better
   * performance)
   *
   * @return The feed cache
   */
  protected CacheWrapper initFeedCache() {
    if (feedCache == null) {
      OLATResourceable ores = OresHelper.createOLATResourceableType(this.getClass());
      CoordinatorManager.getCoordinator().getSyncer().doInSync(ores, new SyncerExecutor() {
        public void execute() {
          if (feedCache == null) {
            feedCache = CoordinatorManager.getCoordinator().getCacher().getOrCreateCache(this.getClass(), "feed");
          }
        }
      });
    }
    return feedCache;
  }

  /**
   * @see org.olat.modules.webFeed.managers.FeedManager#delete(org.olat.core.id.OLATResourceable)
   */
  @Override
  public void delete(OLATResourceable feed) {
    FileResourceManager.getInstance().deleteFileResource(feed);
    // Delete comments and ratings
    CommentAndRatingService commentAndRatingService = (CommentAndRatingService) ServiceFactory.getServiceOrNull(CommentAndRatingService.class);
    if (commentAndRatingService != null) {       
      commentAndRatingService.init(null, feed, null, true, false);
      commentAndRatingService.deleteAllIgnoringSubPath();
    }
    //
    feed = null;
  }

  /**
   * @see org.olat.modules.webFeed.managers.FeedManager#readFeed(org.olat.core.id.OLATResourceable)
   */
  @Override
  public Feed readFeed(OLATResourceable ores) {
    return readFeed(ores, true);
  }
 
  Feed readFeed(OLATResourceable ores, boolean inSync) {
    Feed feed = (Feed) initFeedCache().get(ores.getResourceableId().toString());
    if (feed == null) {
      feed = getFeedLigth(ores, inSync);
      getItems(feed);
    } else if (feed.getItems() == null || feed.getItems().size() == 0) {
      getItems(feed);
    }
    return feed;
  }

  /**
   * @see org.olat.modules.webFeed.managers.FeedManager#getFeedLigth(org.olat.core.id.OLATResourceable)
   */
  public Feed getFeedLigth(OLATResourceable ores) {
    return getFeedLigth(ores, true);
  }
 
  private Feed getFeedLigth(OLATResourceable ores, boolean inSync) {
    // Attempt to fetch the feed from the cache
    Feed myFeed = (Feed) initFeedCache().get(ores.getResourceableId().toString());
    if (myFeed == null) {
      // Load the feed from file and put it to the cache
      VFSContainer feedContainer = getFeedContainer(ores);
      myFeed = readFeedFile(feedContainer);
      if (myFeed != null) {
        // Reset the feed id. (This is necessary for imported feeds.)
        myFeed.setId(ores.getResourceableId());
        // Get repository entry information
        enrichFeedByRepositoryEntryInfromation(myFeed);
        // must be final for sync
        final Feed feed = myFeed;
        syncedFeedCacheUpdate(feed, inSync);
      }
    }
    return myFeed;
  }

  /**
   * Puts the feed to the feedCache in a synchronized manner.
   *
   * @param ores
   * @param feed
   */
  void syncedFeedCacheUpdate(final Feed feed, boolean inSync) {
    initFeedCache();
    if(inSync) {
      CoordinatorManager.getCoordinator().getSyncer().doInSync(feed, new SyncerExecutor() {
        public void execute() {
          // update and put behaves the same way
          feedCache.update(feed.getResourceableId().toString(), feed);
        }
      });
    } else {
      feedCache.update(feed.getResourceableId().toString(), feed);
    }
  }

  /**
   * Gets the items of the feed either from cache or filesystem.
   *
   * @param feed
   * @return The items of the feed
   */
  private List<Item> getItems(Feed feed) {
    List<Item> items = new ArrayList<Item>();
    if (feed.isExternal() && (feed.getItemIds() == null || feed.getItemIds().size() == 0)) {
      items = getItemsFromFeed(feed);
    } else if (feed.getItemIds() != null) {
      for (String itemId : feed.getItemIds()) {
        // No sync needed here
        Item item = (Item) initFeedCache().get(itemKey(itemId, feed.getId().toString()));
        if (item != null) {
          items.add(item);
        } else {
          // reload all items
          items = loadItems(feed);
          break;
        }
      }
    }
    feed.setItems(items);
    return items;
  }

  /**
   * Update the feed resource with the latest set properties in the repository
   * entry.
   * <p>
   * Properties are:
   * <ul>
   * <li>Title
   * <li>Author
   * <li>Descripion (wiki style in repository)
   * <li>Image
   * </ul>
   *
   * @param feed
   */
  private void enrichFeedByRepositoryEntryInfromation(Feed feed) {
    RepositoryEntry entry = getRepositoryEntry(feed);
    if (entry != null && feed != null) {
      Date whenTheFeedWasLastModified = feed.getLastModified();
      if (whenTheFeedWasLastModified == null || entry.getLastModified().after(whenTheFeedWasLastModified)) {
        // entry is newer then feed, update feed
        feed.setTitle(entry.getDisplayname());
        // Formatter.formatWikiMarkup(entry.getDescription())
        feed.setDescription(entry.getDescription());
        feed.setAuthor(entry.getInitialAuthor());
        // Update the image
        String imageFilename = RepositoryEntryImageController.getImageFilename(entry);
        String repoImagePath = FolderConfig.getCanonicalRoot() + FolderConfig.getRepositoryHome() + "/" + imageFilename;
        File repoEntryImage = new File(repoImagePath);
        if (repoEntryImage.exists()) {
          VFSLeaf repoImage = new LocalFileImpl(repoEntryImage);
          getFeedMediaContainer(feed).copyFrom(repoImage);
          VFSLeaf newImage = (VFSLeaf) getFeedMediaContainer(feed).resolve(imageFilename);
          if (newImage != null) {
            feed.setImageName(imageFilename);
          }
        } else {
          // There's no repo entry image -> delete the feed image as well.
          deleteImage(feed);
        }
      }
    }
  }

  /**
   * @param ores
   * @return The repository entry of ores or null
   */
  private RepositoryEntry getRepositoryEntry(OLATResourceable ores) {
    RepositoryManager resMgr = RepositoryManager.getInstance();
    return resMgr.lookupRepositoryEntry(ores, false);
  }

  /**
   * Returns the feed in the given container. It is public and static to be
   * accessible by PodcastFileResource.
   *
   * @param feedContainer
   * @return The Feed upon success
   */
  public static Feed readFeedFile(VFSContainer feedContainer) {
    Feed myFeed = null;
    if (feedContainer != null) {
      VFSLeaf leaf = (VFSLeaf) feedContainer.resolve(FEED_FILE_NAME);
      if (leaf != null) {
        myFeed = (Feed) XStreamHelper.readObject(xstream, leaf.getInputStream());
      }
    } else {
      log.error("Feed xml-file could not be found on file system. Feed container: " + feedContainer);
    }
    return myFeed;
  }

  /**
   * Load all items of the feed (from file system or the external feed)
   *
   * @param feed
   */
  public List<Item> loadItems(final Feed feed) {
    List<Item> items = new ArrayList<Item>();

    if (feed.isExternal()) {
      items = getItemsFromFeed(feed);

    } else if (feed.isInternal()) {
      // Load from virtual file system
      VFSContainer itemsContainer = getItemsContainer(feed);

      for (String itemId : feed.getItemIds()) {
        VFSItem itemContainer = itemsContainer.resolve(itemId);
        Item item = loadItem(itemContainer);
        if (item != null) {
          items.add(item);
        }
      }
    }
    // else, this feed is undefined and should have no items. It probably has
    // just been created.
    feed.setItems(items);
    return items;
  }

  /**
   * Read the items of an external feed url
   *
   * @param feedURL
   * @return The list of all items
   */
  // ROME library uses untyped lists
  @SuppressWarnings("unchecked")
  private List<Item> getItemsFromFeed(Feed extFeed) {
    List<Item> items = new ArrayList<Item>();
    SyndFeed feed = getSyndFeed(extFeed);
    if (feed != null) {
      List<SyndEntry> entries = feed.getEntries();
      for (SyndEntry entry : entries) {
        Item item = convertToItem(entry);
        items.add(item);
      }
    }
    return items;
  }

  /**
   * @param extFeed
   * @param items
   */
  private SyndFeed getSyndFeed(Feed extFeed) {
    SyndFeed feed = null;
    SyndFeedInput input = new SyndFeedInput();
    String feedURL = extFeed.getExternalFeedUrl();
    try {
      URL url = new URL(feedURL);
      feed = input.build(new XmlReader(url));
      // also add the external image url just in case we'll need it later
      addExternalImageURL(feed, extFeed);
    } catch (MalformedURLException e) {
      log.error("The externalFeedUrl is invalid: " + feedURL);
    } catch (FeedException e) {
      log.error("The read feed is invalid: " + feedURL);
    } catch (IOException e) {
      log.error("Cannot read from feed: " + feedURL);
    } finally {
      // No streams to be closed
    }
    return feed;
  }

  /**
   * @param extFeed
   * @param feed
   */
  private void addExternalImageURL(SyndFeed feed, Feed extFeed) {
    SyndImage img = feed.getImage();
    if (img != null) {
      extFeed.setExternalImageURL(img.getUrl());
    } else {
      extFeed.setExternalImageURL(null);
    }
  }

  /**
   * Converts a <code>SyndEntry</code> into an <code>Item</code>
   *
   * @param entry The SyndEntry
   * @return The Item
   */
  private Item convertToItem(SyndEntry entry) {
    // A SyncEntry can potentially have many attributes like title, description,
    // guid, link, enclosure or content. In OLAT, however, items are limited
    // to the attributes, title, description and one media file (called
    // enclosure in RSS) for simplicity.
    Item e = new Item();
    e.setTitle(entry.getTitle());
    e.setDescription(entry.getDescription() != null ? entry.getDescription().getValue() : null);
    // Extract content objects from syndication item
    StringBuffer sb = new StringBuffer();
    for (SyndContent content : (List<SyndContent>) entry.getContents()) {
      // we don't check for type, assume it is html or txt
      if (sb.length() > 0) {
        sb.append("<p />");
      }
      sb.append(content.getValue());
    }
    // Set aggregated content from syndication item as our content
    if (sb.length() > 0) {
      e.setContent(sb.toString());     
    }
    e.setGuid(entry.getUri());
    e.setExternalLink(entry.getLink());
    e.setLastModified(entry.getUpdatedDate());
    e.setPublishDate(entry.getPublishedDate());

    for (Object enclosure : entry.getEnclosures()) {
      if (enclosure instanceof SyndEnclosure) {
        SyndEnclosure syndEnclosure = (SyndEnclosure) enclosure;
        Enclosure media = new Enclosure();
        media.setExternalUrl(syndEnclosure.getUrl());
        media.setType(syndEnclosure.getType());
        media.setLength(syndEnclosure.getLength());
        e.setEnclosure(media);
      }
      // Break after one cycle because only one media file is supported
      break;
    }
    return e;
  }

  /**
   * Loads an item from file. Used for validation in PodcastFileResource, that's
   * why its public and static.
   *
   * @param container
   * @return The item
   */
  public static Item loadItem(VFSItem container) {
    VFSLeaf itemLeaf = null;
    Item item = null;

    if (container != null) {
      itemLeaf = (VFSLeaf) container.resolve(ITEM_FILE_NAME);
      if (itemLeaf != null) {
        item = (Item) XStreamHelper.readObject(xstream, itemLeaf.getInputStream());
      }
    }
    return item;
  }

  /**
   * @see org.olat.modules.webFeed.managers.FeedManager#remove(org.olat.modules.webFeed.models.Item,
   *      org.olat.modules.webFeed.models.Feed)
   */
  @Override
  public void remove(final Item item,  final Feed feed) {   
    // synchronize all feed item CUD operations on this feed to prevend
    // overwriting of changes
    // o_clusterOK by:fg
    CoordinatorManager.getCoordinator().getSyncer().doInSync(feed, new SyncerCallback<Object>() {
      public VFSLeaf execute() {
        // reload feed to prevent stale feed overwriting
        Feed reloadedFeed = readFeed(feed, false);
        reloadedFeed.remove(item);
        // If the last item has been removed, set the feed to undefined.
        // The user can then newly decide whether to add items manually or from
        // an external source.
        if (!reloadedFeed.hasItems()) {
          // set undefined
          reloadedFeed.setExternal(null);
        }
       
        // Delete the item's container on the virtual file system.
        VFSContainer itemContainer = getItemContainer(item, reloadedFeed);
        if (itemContainer != null) {
          itemContainer.delete();
        }
       
        // Update feed
        reloadedFeed.setLastModified(new Date());
        update(reloadedFeed, false);
       
        // Delete comments and ratings
        CommentAndRatingService commentAndRatingService = (CommentAndRatingService) ServiceFactory.getServiceOrNull(CommentAndRatingService.class);
        if (commentAndRatingService != null) {       
          commentAndRatingService.init(null, feed, item.getGuid(), true, false);
          commentAndRatingService.deleteAll();
        }
        //
        return null;
      }
    });
  }

  /**
   * @see org.olat.modules.webFeed.managers.FeedManager#addItem(org.olat.modules.webFeed.models.Item,
   *      org.olat.core.gui.components.form.flexible.elements.FileElement,
   *      org.olat.modules.webFeed.models.Feed)
   */
  @Override
  public void addItem(final Item item, final FileElement file, final Feed feed) {
    if (feed.isInternal()) {
      // synchronize all feed item CUD operations on this feed to prevend
      // overwriting of changes
      // o_clusterOK by:fg
      CoordinatorManager.getCoordinator().getSyncer().doInSync(feed, new SyncerCallback<Object>() {
        public VFSLeaf execute() {
          // reload feed to prevent stale feed overwriting
          Feed reloadedFeed = readFeed(feed, false);
          // Set the current date as published date.
          if (item.getPublishDate() == null) item.setPublishDate(new Date());

          // Set the file properties to the item and store the file
          setEnclosure(file, item, reloadedFeed);

          // Write the item.xml file
          VFSContainer itemContainer = createItemContainer(feed, item);
          VFSLeaf itemFile = itemContainer.createChildLeaf(ITEM_FILE_NAME);
          XStreamHelper.writeObject(xstream, itemFile, item);

          // finally add the item to the feed
          reloadedFeed.add(item);
          reloadedFeed.setLastModified(item.getLastModified());

          // Save the feed (needed because of itemIds list)
          update(reloadedFeed, false);
          return null;
        }
      });
    }
  }

  /**
   * Method to write the feed object to disk using xstream.
   * <p>
   * This method MUST be called from a cluster-synced block with the most recent
   * feed object from the cluster feed cache!
   *
   * @param feed
   */
  void update(Feed feed, boolean inSync) {
    feed.setLastModified(new Date());

    // If the feed url has changed, the items must be reloaded.
    if (feed.isExternal()) {
      String oldFeed = getFeedLigth(feed, inSync).getExternalFeedUrl();
      String newFeed = feed.getExternalFeedUrl();
      if (newFeed != null && !newFeed.equals("")) {
        if (!newFeed.equals(oldFeed)) {
          loadItems(feed);
        }
      }
    }
    // Write the feed file. (Items are saved when adding them.)
    feed.sortItems();
    VFSContainer container = getFeedContainer(feed);
    VFSLeaf leaf = (VFSLeaf) container.resolve(FEED_FILE_NAME);
    XStreamHelper.writeObject(xstream, leaf, feed);
    initFeedCache().update(feed.getResourceableId().toString(), feed);
    enrichRepositoryEntryByFeedInformation(feed);
  }

  /**
   * A unique key for the item of the feed. Can be used e.g. for locking and
   * caching.
   *
   * @param itemId
   * @param feedId
   * @return A unique key for the item of the feed
   */
  private String itemKey(String itemId, String feedId) {
    final StringBuffer key = new StringBuffer();
    key.append("feed").append(feedId);
    key.append("_item_").append(itemId);
    return key.toString();
  }

  /**
   * A unique key for the item of the feed. Can be used e.g. for locking and
   * caching. (Protected for performance reasons)
   *
   * @param item
   * @param feed
   * @return A unique key for the item of the feed
   */
  protected String itemKey(Item item, OLATResourceable feed) {
    String key = itemKey(item.getGuid(), feed.getResourceableId().toString());
    return key;
  }

  /**
   * The unique keys for the items of the feed. (Protected for performance
   * reasons)
   *
   * @param feed
   * @return The unique keys for the items of the feed
   */
  protected String[] itemKeys(Feed feed) {
    List<Item> items = feed.getItems();
    String[] keys = null;
    if (items != null) {
      int size = items.size();
      keys = new String[size];
      for (int i = 0; i < size; i++) {
        keys[i] = itemKey(items.get(i), feed);
      }
    }
    return keys;
  }

  /**
   * Update the repository entry with the latest set properties in the feed
   * resource.
   * <p>
   * Properties are:
   * <ul>
   * <li>Title
   * <li>Author
   * <li>Descripion (wiki style in repository)
   * <li>Image
   * </ul>
   *
   * @param feed
   */
  void enrichRepositoryEntryByFeedInformation(Feed feed) {
    RepositoryEntry entry = getRepositoryEntry(feed);
    if (entry != null && feed != null) {
      Date whenTheFeedWasLastModified = feed.getLastModified();
      if (whenTheFeedWasLastModified != null && entry.getLastModified().before(whenTheFeedWasLastModified)) {
        // feed is newer than repository entry, update repository entry
        entry.setDisplayname(feed.getTitle());
        entry.setDescription(feed.getDescription());
        // Update the image
        VFSContainer repoHome = new LocalFolderImpl(new File(FolderConfig.getCanonicalRoot() + FolderConfig.getRepositoryHome()));
        String imageFilename = RepositoryEntryImageController.getImageFilename(entry);
        VFSItem oldEntryImage = repoHome.resolve(imageFilename);
        if (oldEntryImage != null) {
          // Delete the old File
          oldEntryImage.delete();
        }
        // Copy the feed image to the repository home folder unless it was
        // deleted.
        String feedImage = feed.getImageName();
        if (feedImage != null) {
          VFSItem newImage = getFeedMediaContainer(feed).resolve(feedImage);
          repoHome.copyFrom(newImage);
          VFSItem newEntryImage = repoHome.resolve(feed.getImageName());
          newEntryImage.rename(imageFilename);
        }
      }
    }
  }

  /**
   * @see org.olat.modules.webFeed.managers.FeedManager#getFeedContainer(org.olat.core.id.OLATResourceable)
   */
  public VFSContainer getFeedContainer(OLATResourceable ores) {
    VFSContainer feedDir = null;

    if (ores != null) {
      VFSContainer resourceDir = getResourceContainer(ores);
      String feedDirName = getFeedKind(ores);
      feedDir = (VFSContainer) resourceDir.resolve(feedDirName);
      if (feedDir == null) {
        // If the folder does not exist create it.
        feedDir = resourceDir.createChildContainer(feedDirName);
      }
    }
    return feedDir;
  }

  /**
   * @param ores
   * @return The resource (root) container of the feed
   */
  private VFSContainer getResourceContainer(OLATResourceable ores) {
    return FileResourceManager.getInstance().getFileResourceRootImpl(ores);
  }

  /**
   * @param file
   * @param item
   * @param feed
   */
  public void setEnclosure(FileElement file, Item item, Feed feed) {
    if (file != null) {
      VFSContainer itemMediaContainer = (VFSContainer) getItemContainer(item, feed).resolve(MEDIA_DIR);

      // Empty the container and write the new media file (called 'enclosure' in
      // rss)
      for (VFSItem fileItem : itemMediaContainer.getItems()) {
        if (!fileItem.getName().startsWith(".")) {
          fileItem.delete();
        }
      }
      File uploadedFile = file.getUploadFile();

      String saveFileName = getSaveFileName(file.getUploadFileName());

      File renamed = new File(uploadedFile.getParent() + "/" + saveFileName);
      uploadedFile.renameTo(renamed);
      VFSLeaf mediaFile = new LocalFileImpl(renamed);
      itemMediaContainer.copyFrom(mediaFile);

      // Update the enclosure
      Enclosure enclosure = new Enclosure();
      enclosure.setFileName(saveFileName);
      enclosure.setLength(renamed.length());
      enclosure.setType(file.getUploadMimeType());

      item.setEnclosure(enclosure);
    }
  }

  /**
   * Returns a alphanumeric string without whitespaces.
   *
   * @param file name
   * @return The save file name
   */
  private String getSaveFileName(String fileName) {
    // Replace all white spaces by underline
    fileName = fileName.replaceAll("[ \t\n\r]", "_");
    // Remove all non alphanumeric characters (except the dot)
    fileName = fileName.replaceAll("[^a-zA-Z0-9_.]", "");
    return fileName;
  }

  /**
   * @see org.olat.modules.webFeed.managers.FeedManager#getItemContainer(org.olat.modules.webFeed.models.Item,
   *      org.olat.modules.webFeed.models.Feed)
   */
  public VFSContainer getItemContainer(Item item, Feed feed) {
    return (VFSContainer) getItemsContainer(feed).resolve(item.getGuid());
  }

  /**
   * @see org.olat.modules.webFeed.managers.FeedManager#getItemMediaContainer(org.olat.modules.webFeed.models.Item,
   *      org.olat.modules.webFeed.models.Feed)
   */
  public VFSContainer getItemMediaContainer(Item item, Feed feed) {
    VFSContainer itemMediaContainer = null;
    VFSContainer itemContainer = getItemContainer(item, feed);
    if (itemContainer != null) {
      itemMediaContainer = (VFSContainer) itemContainer.resolve(MEDIA_DIR);
    }
   
    return itemMediaContainer;
  }

  /**
   * @see org.olat.modules.webFeed.managers.FeedManager#getItemMediaContainer(org.olat.modules.webFeed.models.Item,
   *      org.olat.modules.webFeed.models.Feed)
   */
  public File getItemEnclosureFile(Item item, Feed feed) {
    VFSContainer mediaDir = getItemMediaContainer(item, feed);
    Enclosure enclosure = item.getEnclosure();
    File file = null;
    if (mediaDir != null && enclosure != null) {
      VFSLeaf mediaFile = (VFSLeaf) mediaDir.resolve(enclosure.getFileName());
      if (mediaFile != null && mediaFile instanceof LocalFileImpl) {
        file = ((LocalFileImpl) mediaFile).getBasefile();
      }
    }
    return file;
  }

  /**
   * Returns the items container of feed
   *
   * @param feed
   * @return The container of all items
   */
  private VFSContainer getItemsContainer(OLATResourceable feed) {
    VFSContainer items = null;
    VFSContainer feedContainer = getFeedContainer(feed);
    // If feed container is null we're in trouble
    items = (VFSContainer) feedContainer.resolve(ITEMS_DIR);
    if (items == null) {
      items = feedContainer.createChildContainer(ITEMS_DIR);
    }
    return items;
  }

  /**
   * Returns the container of media files
   *
   * @param feed
   * @return The feed media container
   */
  public VFSContainer getFeedMediaContainer(OLATResourceable feed) {
    VFSContainer media = null;
    VFSContainer feedContainer = getFeedContainer(feed);
    // If feed container is null we're in trouble
    media = (VFSContainer) feedContainer.resolve(MEDIA_DIR);
    if (media == null) {
      media = feedContainer.createChildContainer(MEDIA_DIR);
    }
    return media;
  }

  /**
   * @see org.olat.modules.webFeed.managers.FeedManager#updateItem(org.olat.modules.webFeed.models.Item,
   *      org.olat.core.gui.components.form.flexible.elements.FileElement,
   *      org.olat.modules.webFeed.models.Feed)
   */
  @Override
  public void updateItem(final Item item, final FileElement file, final Feed feed) {
    if (feed.isInternal()) {
      // synchronize all feed item CUD operations on this feed to prevend
      // overwriting of changes
      // o_clusterOK by:fg
      CoordinatorManager.getCoordinator().getSyncer().doInSync(feed, new SyncerCallback<Object>() {
        public VFSLeaf execute() {
          // reload feed to prevent stale feed overwriting
          Feed reloadedFeed = readFeed(feed, false);
          if (reloadedFeed.getItemIds().contains(item.getGuid())) {
            if (file != null) {
              setEnclosure(file, item, reloadedFeed);
            }
           
            // Write the item.xml file
            VFSLeaf itemFile = (VFSLeaf) getItemContainer(item, reloadedFeed).resolve(ITEM_FILE_NAME);
            XStreamHelper.writeObject(xstream, itemFile, item);
           
            update(reloadedFeed, false);           
          } else {
            // do nothing, item was deleted by someone in the meantime
          }
          return null;
        }
      });     
    }
  }

  /**
   * @see org.olat.modules.webFeed.managers.FeedManager#updateFeedMode(java.lang.Boolean, org.olat.modules.webFeed.models.Feed)
   */
  public Feed updateFeedMode(final Boolean external, final Feed feed) {
    return CoordinatorManager.getCoordinator().getSyncer().doInSync(feed, new SyncerCallback<Feed>() {
      public Feed execute() {
        // reload feed to prevent stale feed overwriting
        Feed reloadedFeed = readFeed(feed, false);
        reloadedFeed.setExternal(external);       
        update(reloadedFeed, false);
        return reloadedFeed;
      }
    });
  }

  /**
   * @see org.olat.modules.webFeed.managers.FeedManager#updateFeedMetadata(org.olat.modules.webFeed.models.Feed)
   */
  public Feed updateFeedMetadata(final Feed feed) {
    // reload feed to prevent stale feed overwriting
    final Feed reloadedFeed = readFeed(feed);
    reloadedFeed.setAuthor(feed.getAuthor());
    reloadedFeed.setDescription(feed.getDescription());
    reloadedFeed.setExternalFeedUrl(feed.getExternalFeedUrl());
    reloadedFeed.setExternalImageURL(feed.getExternalImageURL());
    reloadedFeed.setImageName(feed.getImageName());
    reloadedFeed.setTitle(feed.getTitle());
     
    return CoordinatorManager.getCoordinator().getSyncer().doInSync(feed, new SyncerCallback<Feed>() {
      public Feed execute() {
        update(reloadedFeed, false);
        return reloadedFeed;
      }
    });   
  }

 
  /**
   * @see org.olat.modules.webFeed.managers.FeedManager#createMediaResource(java.lang.Long,
   *      java.lang.String, java.lang.String, java.lang.String)
   */
  @Override
  public MediaResource createItemMediaFile(OLATResourceable feed, String itemId, String fileName) {
    VFSMediaResource mediaResource = null;
    // Brute force method for fast delivery
    try {
      VFSItem item = getItemsContainer(feed);
      item = item.resolve(itemId);
      item = item.resolve(MEDIA_DIR);
      item = item.resolve(fileName);
      mediaResource = new VFSMediaResource((VFSLeaf) item);
    } catch (NullPointerException e) {
      log.debug("Media resource could not be created from file: ", fileName);
    }
    return mediaResource;
  }

  /**
   * @see org.olat.modules.webFeed.managers.FeedManager#createMediaResource(java.lang.Long,
   *      java.lang.String, java.lang.String)
   */
  @Override
  public MediaResource createFeedMediaFile(OLATResourceable feed, String fileName) {
    VFSMediaResource mediaResource = null;
    // Brute force method for fast delivery
    try {
      VFSItem item = getFeedMediaContainer(feed);
      item = item.resolve(fileName);
      mediaResource = new VFSMediaResource((VFSLeaf) item);
    } catch (NullPointerException e) {
      log.debug("Media resource could not be created from file: ", fileName);
    }
    return mediaResource;
  }

  /**
   * @see org.olat.modules.webFeed.managers.FeedManager#getFeedUri(org.olat.modules.webFeed.models.Feed,
   *      java.lang.Long)
   */
  @Override
  public String getFeedBaseUri(Feed feed, Identity identity, Long courseId, String nodeId) {
    return FeedMediaDispatcher.getFeedBaseUri(feed, identity, courseId, nodeId);
  }

  /**
   * @see org.olat.modules.webFeed.managers.FeedManager#createFeedMediaResource(java.lang.Long,
   *      java.lang.String, java.lang.Long)
   */
  @Override
  public MediaResource createFeedFile(OLATResourceable ores, Identity identity, Long courseId, String nodeId) {
    MediaResource media = null;
    Feed feed = readFeed(ores);

    if (feed != null) {
      SyndFeed rssFeed = new RSSFeed(feed, identity, courseId, nodeId);
      media = new SyndFeedMediaResource(rssFeed);
    }
    return media;
  }

  /**
   * @see org.olat.modules.webFeed.managers.FeedManager#isValidFeedUrl(java.lang.String)
   */
  @Override
  public ValidatedURL validateFeedUrl(String url, String type) {
    SyndFeedInput input = new SyndFeedInput();
   
    boolean modifiedProtocol = false;
    try {
      if (url != null) {
        url = url.trim();
      }
      if (url.startsWith("feed") || url.startsWith("itpc")) {
        // accept feed(s) urls like generated in safari browser
        url = "http" + url.substring(4);
        modifiedProtocol = true;
      }
      URL realUrl = new URL(url);
      SyndFeed feed = input.build(new XmlReader(realUrl));
      if(!feed.getEntries().isEmpty()) {
        //check for enclosures
        SyndEntry entry = (SyndEntry)feed.getEntries().get(0);
        if(type != null && type.indexOf("BLOG") >= 0) {
          return new ValidatedURL(url, ValidatedURL.State.VALID);
        }
        if(entry.getEnclosures().isEmpty()) {
          return new ValidatedURL(url, ValidatedURL.State.NO_ENCLOSURE);
        }
      }
      //The feed was read successfully
      return new ValidatedURL(url, ValidatedURL.State.VALID);
    } catch (ParsingFeedException e) {
      if(modifiedProtocol) {
        //fallback for SWITCHcast itpc -> http -> https
        url = "https" + url.substring(4);
        return validateFeedUrl(url, type);
      }
      return new ValidatedURL(url, ValidatedURL.State.NOT_FOUND);
    } catch (FileNotFoundException e) {
      return new ValidatedURL(url, ValidatedURL.State.NOT_FOUND);
    } catch (MalformedURLException e) {
      // The url is invalid
    } catch (FeedException e) {
      // The feed couldn't be read
    } catch (IOException e) {
      // Maybe network or file problems
    } catch (IllegalArgumentException e) {
      // something very wrong with the feed
    }
    return new ValidatedURL(url, ValidatedURL.State.MALFORMED);
  }

  /**
   * @see org.olat.modules.webFeed.managers.FeedManager#copy(org.olat.core.id.OLATResourceable)
   */
  @Override
  public OLATResourceable copy(OLATResourceable feed) {
    FileResourceManager manager = FileResourceManager.getInstance();
    return manager.createCopy(feed, getFeedKind(feed));
  }

  /**
   * @see org.olat.modules.webFeed.managers.FeedManager#getFeedArchiveMediaResource(org.olat.core.id.OLATResourceable)
   */
  @Override
  public VFSMediaResource getFeedArchiveMediaResource(OLATResourceable resource) {
    VFSLeaf zip = getFeedArchive(resource);
    return new VFSMediaResource(zip);
  }
 
  /**
   * @see org.olat.modules.webFeed.managers.FeedManager#getFeedArchive(org.olat.core.id.OLATResourceable)
   */
  public VFSLeaf getFeedArchive(final OLATResourceable resource) {
    final VFSContainer rootContainer, feedContainer;
    rootContainer = getResourceContainer(resource);
    feedContainer = getFeedContainer(resource);
   
    //prepare fallback for author if needed
    final Feed feed = getFeedLigth(resource, true);
    if(feed.isInternal()) {
      CoordinatorManager.getCoordinator().getSyncer().doInSync(feed, new SyncerCallback<Boolean>() {
        public Boolean execute() { 
          for(Item item : getItems(feed)) {
            if(!item.isAuthorFallbackSet()) {
              //get used authorKey first
              String author = item.getAuthor();
              if(StringHelper.containsNonWhitespace(author)) {
                //set author fallback
                item.setAuthor(author);
                //update item.xml
                VFSContainer itemContainer = getItemContainer(item, feed);
                if(itemContainer != null) {
                  VFSLeaf itemFile = (VFSLeaf)itemContainer.resolve(ITEM_FILE_NAME);
                  XStreamHelper.writeObject(xstream, itemFile, item);
                }
              }
            }
          }
          return Boolean.TRUE;
        }
      });
    }
   
    // synchronize all zip processes for this feed
    // o_clusterOK by:fg
    VFSLeaf zip = CoordinatorManager.getCoordinator().getSyncer().doInSync(resource, new SyncerCallback<VFSLeaf>() {
      public VFSLeaf execute() {
        // Delete the old archive and recreate it from scratch
        String zipFileName = getZipFileName(resource);
        VFSItem oldArchive = rootContainer.resolve(zipFileName);
        if (oldArchive != null) {
          oldArchive.delete();
        }
        ZipUtil.zip(feedContainer.getItems(), rootContainer.createChildLeaf(zipFileName));
        return (VFSLeaf) rootContainer.resolve(zipFileName);
      }
    });
    return zip;
  }

  /**
   * Returns the file name of the archive that is to be exported. Depends on the
   * kind of the resource.
   *
   * @param resource
   * @return The zip archive file name
   */
  String getZipFileName(OLATResourceable resource) {
    return getFeedKind(resource) + ".zip";
  }

  /**
   * @see org.olat.modules.webFeed.managers.FeedManager#releaseLock(org.olat.core.util.coordinate.LockResult)
   */
  public void releaseLock(LockResult lock) {
    if (lock != null) {
      CoordinatorManager.getCoordinator().getLocker().releaseLock(lock);
    }
  }

  /**
   * @see org.olat.modules.webFeed.managers.FeedManager#acquireLock(org.olat.core.id.OLATResourceable,
   *      org.olat.core.id.Identity)
   */
  public LockResult acquireLock(OLATResourceable feed, Identity identity) {
    // OLATResourceable itemLock =
    // OresHelper.createOLATResourceableInstance("podcastlock_" +
    // feed.getResourceableId() + "_meta", item.getId())
    LockResult lockResult = CoordinatorManager.getCoordinator().getLocker().acquireLock(feed, identity, null);
    return lockResult;
  }

  /**
   * @see org.olat.modules.webFeed.managers.FeedManager#acquireLock(org.olat.core.id.OLATResourceable,
   *      org.olat.modules.webFeed.models.Item, org.olat.core.id.Identity)
   */
  public LockResult acquireLock(OLATResourceable feed, Item item, Identity identity) {
    String key = itemKey(item, feed);
    OLATResourceable itemResource = OresHelper.createOLATResourceableType(key);
    LockResult lockResult = CoordinatorManager.getCoordinator().getLocker().acquireLock(itemResource, identity, key);
    return lockResult;
  }

  /**
   * @see org.olat.modules.webFeed.managers.FeedManager#setImage(org.olat.core.gui.components.form.flexible.elements.FileElement,
   *      org.olat.modules.webFeed.models.Feed)
   */
  @Override
  public void setImage(FileElement image, Feed feed) {
    if (image != null) {
      // Delete the old image
      deleteImage(feed);
      // Save the new image
      File uploadedFile = image.getUploadFile();
      String saveFileName = getSaveFileName(image.getUploadFileName());
      File renamed = new File(uploadedFile.getParent() + "/" + saveFileName);
      uploadedFile.renameTo(renamed);
      VFSLeaf imageLeaf = new LocalFileImpl(renamed);
      getFeedMediaContainer(feed).copyFrom(imageLeaf);
      feed.setImageName(saveFileName);
    }
  }

  /**
   * @see org.olat.modules.webFeed.managers.FeedManager#deleteImage(org.olat.modules.webFeed.models.Feed)
   */
  @Override
  public void deleteImage(Feed feed) {
    VFSContainer mediaContainer = getFeedMediaContainer(feed);
    String imageName = feed.getImageName();
    if (imageName != null) {
      VFSLeaf image = (VFSLeaf) mediaContainer.resolve(imageName);
      if (image != null) {
        image.delete();
        feed.setImageName(null);
      }
    }
  }

  /**
   * @see org.olat.modules.webFeed.managers.FeedManager#createItemContainer(org.olat.modules.webFeed.models.Feed, org.olat.modules.webFeed.models.Item)
   */
  @Override
  public VFSContainer createItemContainer(Feed feed, Item item) {
    VFSContainer itemContainer = getItemContainer(item, feed);
    if (itemContainer == null) {
      if (!StringHelper.containsNonWhitespace(item.getGuid())) {
        throw new AssertException("Programming error, item has no GUID set, can not create an item container for this item");
      }
      itemContainer = getItemsContainer(feed).createChildContainer(item.getGuid());
    }
    // prepare media container
    VFSContainer mediaContainer = getItemMediaContainer(item, feed);
    if (mediaContainer == null) {
      itemContainer.createChildContainer(MEDIA_DIR);           
    }
    return itemContainer;
  }
}
TOP

Related Classes of org.olat.modules.webFeed.managers.FeedManagerImpl

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.