/**
* 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;
}
}