From ed86c26db8398f1dd9cd661b4469066b0ccb064b Mon Sep 17 00:00:00 2001
From: uhensler <none@none>
Date: Tue, 6 Jun 2017 16:08:09 +0200
Subject: [PATCH] OO-2723: Refactoring of the webfeed storage system to enhance
 the reliability of blogs and podcasts

---
 .../course/nodes/AbstractFeedCourseNode.java  |    2 +-
 .../org/olat/course/nodes/BlogCourseNode.java |    2 +-
 .../olat/course/nodes/PodcastCourseNode.java  |    2 +-
 .../nodes/feed/FeedPeekviewController.java    |   18 +-
 .../fileresource/types/FeedFileResource.java  |   72 +-
 .../manager/Card2BrainManagerImpl.java        |    2 +-
 .../org/olat/modules/webFeed/Enclosure.java   |   72 +
 .../modules/webFeed/ExternalFeedFetcher.java  |   51 +
 .../java/org/olat/modules/webFeed/Feed.java   |  110 ++
 .../olat/modules/webFeed/FeedViewHelper.java  |  588 ++++----
 .../java/org/olat/modules/webFeed/Item.java   |  158 ++
 .../org/olat/modules/webFeed/RSSFeed.java     |   13 +-
 .../webFeed/_spring/webFeedContext.xml        |    3 +-
 .../dispatching/FeedMediaDispatcher.java      |    6 +-
 .../modules/webFeed/dispatching/Path.java     |    4 +-
 .../BlogNotificationsHandler.java             |    2 +-
 .../olat/modules/webFeed/manager/FeedDAO.java |  110 ++
 .../webFeed/manager/FeedFileStorge.java       |  545 +++++++
 .../{managers => manager}/FeedManager.java    |  222 +--
 .../webFeed/manager/FeedManagerImpl.java      |  955 ++++++++++++
 .../FeedNotifications.java                    |   10 +-
 .../olat/modules/webFeed/manager/ItemDAO.java |  275 ++++
 .../PodcastNotificationsHandler.java          |    2 +-
 .../webFeed/manager/RomeFeedFetcher.java      |  185 +++
 .../{managers => manager}/ValidatedURL.java   |    2 +-
 .../{managers => manager}/package.html        |    0
 .../webFeed/managers/FeedManagerImpl.java     | 1339 -----------------
 .../EnclosureImpl.java}                       |   98 +-
 .../olat/modules/webFeed/model/FeedImpl.java  |  276 ++++
 .../olat/modules/webFeed/model/ItemImpl.java  |  387 +++++
 .../ItemPublishDateComparator.java            |    5 +-
 .../webFeed/{models => model}/package.html    |    0
 .../org/olat/modules/webFeed/models/Feed.java |  447 ------
 .../org/olat/modules/webFeed/models/Item.java |  344 -----
 .../BlogArtefactDetailsController.java        |    5 +-
 .../portfolio/BlogArtefactHandler.java        |   21 +-
 .../webFeed/portfolio/BlogEntryMedia.java     |    4 +-
 .../portfolio/BlogEntryMediaController.java   |    7 +-
 .../portfolio/BlogEntryMediaHandler.java      |   12 +-
 .../EPCreateLiveBlogArtefactStepForm00.java   |    6 +-
 .../webFeed/portfolio/LiveBlogArtefact.java   |    6 +-
 .../portfolio/LiveBlogArtefactHandler.java    |   13 +-
 ...LiveBlogContextEntryControllerCreator.java |   15 +-
 .../search/document/FeedItemDocument.java     |    2 +-
 .../search/document/FeedNodeDocument.java     |    2 +-
 .../search/indexer/FeedCourseNodeIndexer.java |   14 +-
 .../search/indexer/FeedRepositoryIndexer.java |   15 +-
 .../webFeed/ui/DisplayFeedUrlController.java  |    2 +-
 .../webFeed/ui/FeedFormController.java        |   10 +-
 .../webFeed/ui/FeedMainController.java        |   49 +-
 .../modules/webFeed/ui/FeedUIFactory.java     |    6 +-
 .../modules/webFeed/ui/ItemController.java    |    5 +-
 .../modules/webFeed/ui/ItemsController.java   |  508 ++++---
 .../ui/blog/BlogPostFormController.java       |   22 +-
 .../webFeed/ui/blog/BlogUIFactory.java        |    4 +-
 .../webFeed/ui/blog/_content/info.html        |   14 +-
 .../webFeed/ui/blog/_content/posts.html       |    8 +-
 .../ui/blog/_i18n/LocalStrings_ar.properties  |    1 -
 .../ui/blog/_i18n/LocalStrings_de.properties  |    1 -
 .../ui/blog/_i18n/LocalStrings_el.properties  |    1 -
 .../ui/blog/_i18n/LocalStrings_en.properties  |    1 -
 .../ui/blog/_i18n/LocalStrings_fr.properties  |    1 -
 .../ui/blog/_i18n/LocalStrings_it.properties  |    1 -
 .../ui/blog/_i18n/LocalStrings_jp.properties  |    1 -
 .../blog/_i18n/LocalStrings_nl_NL.properties  |    1 -
 .../ui/blog/_i18n/LocalStrings_pl.properties  |    1 -
 .../blog/_i18n/LocalStrings_pt_BR.properties  |    1 -
 .../ui/blog/_i18n/LocalStrings_ru.properties  |    1 -
 .../blog/_i18n/LocalStrings_zh_CN.properties  |    1 -
 .../blog/_i18n/LocalStrings_zh_TW.properties  |    1 -
 .../ui/podcast/EpisodeFormController.java     |   22 +-
 .../webFeed/ui/podcast/PodcastUIFactory.java  |    4 +-
 .../webFeed/ui/podcast/_content/episodes.html |   10 +-
 .../webFeed/ui/podcast/_content/info.html     |   14 +-
 .../olat/repository/handlers/BlogHandler.java |    5 +-
 .../repository/handlers/PodcastHandler.java   |    5 +-
 .../logging/activity/LoggingResourceable.java |    4 +-
 src/main/resources/META-INF/persistence.xml   |    2 +
 .../database/mysql/setupDatabase.sql          |   54 +
 .../database/oracle/setupDatabase.sql         |   51 +
 .../database/postgresql/setupDatabase.sql     |   54 +-
 .../modules/webFeed/FeedManagerImplTest.java  |  174 ---
 .../modules/webFeed/manager/FeedDAOTest.java  |  283 ++++
 .../webFeed/manager/FeedFileStorgeTest.java   |  668 ++++++++
 .../webFeed/manager/FeedManagerImplTest.java  |  236 +++
 .../modules/webFeed/manager/ItemDAOTest.java  |  570 +++++++
 .../webFeed/manager/RomeFeedFetcherTest.java  |  282 ++++
 .../java/org/olat/test/AllTestsJunit4.java    |    7 +-
 88 files changed, 6256 insertions(+), 3259 deletions(-)
 create mode 100644 src/main/java/org/olat/modules/webFeed/Enclosure.java
 create mode 100644 src/main/java/org/olat/modules/webFeed/ExternalFeedFetcher.java
 create mode 100644 src/main/java/org/olat/modules/webFeed/Feed.java
 create mode 100644 src/main/java/org/olat/modules/webFeed/Item.java
 rename src/main/java/org/olat/modules/webFeed/{managers => manager}/BlogNotificationsHandler.java (99%)
 create mode 100644 src/main/java/org/olat/modules/webFeed/manager/FeedDAO.java
 create mode 100644 src/main/java/org/olat/modules/webFeed/manager/FeedFileStorge.java
 rename src/main/java/org/olat/modules/webFeed/{managers => manager}/FeedManager.java (63%)
 create mode 100644 src/main/java/org/olat/modules/webFeed/manager/FeedManagerImpl.java
 rename src/main/java/org/olat/modules/webFeed/{managers => manager}/FeedNotifications.java (95%)
 create mode 100644 src/main/java/org/olat/modules/webFeed/manager/ItemDAO.java
 rename src/main/java/org/olat/modules/webFeed/{managers => manager}/PodcastNotificationsHandler.java (99%)
 create mode 100644 src/main/java/org/olat/modules/webFeed/manager/RomeFeedFetcher.java
 rename src/main/java/org/olat/modules/webFeed/{managers => manager}/ValidatedURL.java (96%)
 rename src/main/java/org/olat/modules/webFeed/{managers => manager}/package.html (100%)
 delete mode 100644 src/main/java/org/olat/modules/webFeed/managers/FeedManagerImpl.java
 rename src/main/java/org/olat/modules/webFeed/{models/Enclosure.java => model/EnclosureImpl.java} (58%)
 create mode 100644 src/main/java/org/olat/modules/webFeed/model/FeedImpl.java
 create mode 100644 src/main/java/org/olat/modules/webFeed/model/ItemImpl.java
 rename src/main/java/org/olat/modules/webFeed/{models => model}/ItemPublishDateComparator.java (93%)
 rename src/main/java/org/olat/modules/webFeed/{models => model}/package.html (100%)
 delete mode 100644 src/main/java/org/olat/modules/webFeed/models/Feed.java
 delete mode 100644 src/main/java/org/olat/modules/webFeed/models/Item.java
 delete mode 100644 src/test/java/org/olat/modules/webFeed/FeedManagerImplTest.java
 create mode 100644 src/test/java/org/olat/modules/webFeed/manager/FeedDAOTest.java
 create mode 100644 src/test/java/org/olat/modules/webFeed/manager/FeedFileStorgeTest.java
 create mode 100644 src/test/java/org/olat/modules/webFeed/manager/FeedManagerImplTest.java
 create mode 100644 src/test/java/org/olat/modules/webFeed/manager/ItemDAOTest.java
 create mode 100644 src/test/java/org/olat/modules/webFeed/manager/RomeFeedFetcherTest.java

diff --git a/src/main/java/org/olat/course/nodes/AbstractFeedCourseNode.java b/src/main/java/org/olat/course/nodes/AbstractFeedCourseNode.java
index 0a95578bd06..71ac03ceb7e 100644
--- a/src/main/java/org/olat/course/nodes/AbstractFeedCourseNode.java
+++ b/src/main/java/org/olat/course/nodes/AbstractFeedCourseNode.java
@@ -37,7 +37,7 @@ import org.olat.course.run.navigation.NodeRunConstructionResult;
 import org.olat.course.run.userview.NodeEvaluation;
 import org.olat.course.run.userview.UserCourseEnvironment;
 import org.olat.modules.ModuleConfiguration;
-import org.olat.modules.webFeed.managers.FeedManager;
+import org.olat.modules.webFeed.manager.FeedManager;
 import org.olat.repository.RepositoryEntry;
 import org.olat.repository.RepositoryEntryImportExport;
 import org.olat.repository.RepositoryManager;
diff --git a/src/main/java/org/olat/course/nodes/BlogCourseNode.java b/src/main/java/org/olat/course/nodes/BlogCourseNode.java
index ca248a241e0..7791c9a86db 100644
--- a/src/main/java/org/olat/course/nodes/BlogCourseNode.java
+++ b/src/main/java/org/olat/course/nodes/BlogCourseNode.java
@@ -50,7 +50,7 @@ import org.olat.course.run.userview.UserCourseEnvironment;
 import org.olat.fileresource.types.BlogFileResource;
 import org.olat.modules.webFeed.FeedReadOnlySecurityCallback;
 import org.olat.modules.webFeed.FeedSecurityCallback;
-import org.olat.modules.webFeed.managers.FeedManager;
+import org.olat.modules.webFeed.manager.FeedManager;
 import org.olat.modules.webFeed.ui.FeedMainController;
 import org.olat.modules.webFeed.ui.FeedUIFactory;
 import org.olat.modules.webFeed.ui.blog.BlogUIFactory;
diff --git a/src/main/java/org/olat/course/nodes/PodcastCourseNode.java b/src/main/java/org/olat/course/nodes/PodcastCourseNode.java
index e7295455b63..2b4c8000e96 100644
--- a/src/main/java/org/olat/course/nodes/PodcastCourseNode.java
+++ b/src/main/java/org/olat/course/nodes/PodcastCourseNode.java
@@ -50,7 +50,7 @@ import org.olat.course.run.userview.UserCourseEnvironment;
 import org.olat.fileresource.types.PodcastFileResource;
 import org.olat.modules.webFeed.FeedReadOnlySecurityCallback;
 import org.olat.modules.webFeed.FeedSecurityCallback;
-import org.olat.modules.webFeed.managers.FeedManager;
+import org.olat.modules.webFeed.manager.FeedManager;
 import org.olat.modules.webFeed.ui.FeedMainController;
 import org.olat.modules.webFeed.ui.FeedUIFactory;
 import org.olat.modules.webFeed.ui.podcast.PodcastUIFactory;
diff --git a/src/main/java/org/olat/course/nodes/feed/FeedPeekviewController.java b/src/main/java/org/olat/course/nodes/feed/FeedPeekviewController.java
index 6291886ded8..c4bcbe4fbd7 100644
--- a/src/main/java/org/olat/course/nodes/feed/FeedPeekviewController.java
+++ b/src/main/java/org/olat/course/nodes/feed/FeedPeekviewController.java
@@ -34,11 +34,11 @@ import org.olat.core.gui.control.WindowControl;
 import org.olat.core.gui.control.controller.BasicController;
 import org.olat.core.util.Formatter;
 import org.olat.core.util.StringHelper;
+import org.olat.modules.webFeed.Feed;
 import org.olat.modules.webFeed.FeedSecurityCallback;
 import org.olat.modules.webFeed.FeedViewHelper;
-import org.olat.modules.webFeed.managers.FeedManager;
-import org.olat.modules.webFeed.models.Feed;
-import org.olat.modules.webFeed.models.Item;
+import org.olat.modules.webFeed.Item;
+import org.olat.modules.webFeed.manager.FeedManager;
 import org.olat.modules.webFeed.ui.FeedUIFactory;
 import org.olat.resource.OLATResource;
 import org.olat.user.UserManager;
@@ -61,8 +61,6 @@ public class FeedPeekviewController extends BasicController implements Controlle
 	// the current course node id
 	private final String nodeId;
 	
-	@Autowired
-	private FeedManager feedManager;
 	@Autowired
 	private UserManager userManager;
 
@@ -84,7 +82,7 @@ public class FeedPeekviewController extends BasicController implements Controlle
 			Long courseId, String nodeId, FeedUIFactory feedUIFactory, int itemsToDisplay, String wrapperCssClass) {
 		super(ureq, wControl);
 		this.nodeId = nodeId;
-		Feed feed = feedManager.getFeed(olatResource);
+		Feed feed = FeedManager.getInstance().loadFeed(olatResource);
 
 		VelocityContainer peekviewVC = createVelocityContainer("peekview");
 		if(feed == null) {
@@ -94,11 +92,11 @@ public class FeedPeekviewController extends BasicController implements Controlle
 			peekviewVC.contextPut("wrapperCssClass", wrapperCssClass != null ? wrapperCssClass : "");
 			// add gui helper
 			String authorFullname = userManager.getUserDisplayName(feed.getAuthor());
-			FeedViewHelper helper = new FeedViewHelper(feed, getIdentity(), authorFullname, getTranslator(), courseId, nodeId, callback);
+			FeedViewHelper helper = new FeedViewHelper(feed, getIdentity(), getTranslator(), courseId, nodeId);
 			peekviewVC.contextPut("helper", helper);
 			// add items, only as many as configured
-			List<Item> allItems = feed.getFilteredItems(callback, getIdentity());
-			List<Item> items = new ArrayList<Item>();
+			List<Item> allItems = FeedManager.getInstance().loadFilteredAndSortedItems(feed, callback, getIdentity());
+			List<Item> items = new ArrayList<>();
 			for (int i = 0; i < allItems.size(); i++) {
 				if (items.size() == itemsToDisplay) {
 					break;
@@ -113,7 +111,7 @@ public class FeedPeekviewController extends BasicController implements Controlle
 					nodeLink.setCustomDisplayText(StringHelper.escapeHtml(item.getTitle()));
 					nodeLink.setIconLeftCSS("o_icon o_" + feed.getResourceableTypeName().replace(".", "-") + "_icon");
 					nodeLink.setCustomEnabledLinkCSS("o_gotoNode");
-					nodeLink.setUserObject(item.getGuid());
+					nodeLink.setUserObject(Long.toString(item.getKey()));
 				}
 			}
 			peekviewVC.contextPut("items", items);
diff --git a/src/main/java/org/olat/fileresource/types/FeedFileResource.java b/src/main/java/org/olat/fileresource/types/FeedFileResource.java
index 643cd05b76d..cd986afc68c 100644
--- a/src/main/java/org/olat/fileresource/types/FeedFileResource.java
+++ b/src/main/java/org/olat/fileresource/types/FeedFileResource.java
@@ -25,7 +25,6 @@ import java.nio.file.FileVisitResult;
 import java.nio.file.Path;
 import java.nio.file.SimpleFileVisitor;
 import java.nio.file.attribute.BasicFileAttributes;
-import java.util.List;
 
 import org.olat.core.logging.OLog;
 import org.olat.core.logging.Tracing;
@@ -34,9 +33,9 @@ import org.olat.core.util.vfs.LocalFolderImpl;
 import org.olat.core.util.vfs.VFSContainer;
 import org.olat.core.util.vfs.VFSItem;
 import org.olat.fileresource.FileResourceManager;
-import org.olat.modules.webFeed.managers.FeedManager;
-import org.olat.modules.webFeed.models.Feed;
-import org.olat.modules.webFeed.models.Item;
+import org.olat.modules.webFeed.Feed;
+import org.olat.modules.webFeed.manager.FeedFileStorge;
+import org.olat.modules.webFeed.manager.FeedManager;
 
 /**
  * Abstract feed file resource class. Used to decrease redundancy.
@@ -88,8 +87,7 @@ public abstract class FeedFileResource extends FileResource {
 			Path fPath = PathUtils.visit(file, filename, visitor);
 			
 			if(visitor.isValid()) {
-				Path feedXml = fPath.resolve(FeedManager.FEED_FILE_NAME);
-				Feed feed = FeedManager.getInstance().readFeedFile(feedXml);
+				Feed feed = FeedManager.getInstance().loadFeedFromXML(fPath);
 				if(feed != null && type.equals(feed.getResourceableTypeName())) {
 					eval.setValid(true);
 					eval.setDisplayname(feed.getTitle());
@@ -110,7 +108,7 @@ public abstract class FeedFileResource extends FileResource {
 		throws IOException {
 
 			String filename = file.getFileName().toString();
-			if(FeedManager.FEED_FILE_NAME.equals(filename)) {
+			if(FeedFileStorge.FEED_FILE_NAME.equals(filename)) {
 				feedFile = true;
 			}
 			return feedFile ? FileVisitResult.TERMINATE : FileVisitResult.CONTINUE;
@@ -129,59 +127,15 @@ public abstract class FeedFileResource extends FileResource {
 	 */
 	public static boolean validate(File directory, String type) {
 		boolean valid = false;
-		if (directory != null) {
-			// Verify the directory structure:
-			// /root
-			// __feed.xml
-			// __/items
-			// ____/item
-			// ______item.xml
-			// ______/media.xml
-			// ________...
-			// ____/item
-			// ______...
-			VFSContainer root = new LocalFolderImpl(directory);
-			// try to read podcast
-			try {
-				Feed feed = FeedManager.getInstance().readFeedFile(root);
-				if (feed != null) {
-					// The feed is valid, let's check the items
-					if (feed.isInternal()) {
-						List<String> itemIds = feed.getItemIds();
-						VFSContainer itemsContainer = (VFSContainer) root.resolve(FeedManager.ITEMS_DIR);
-						if (itemsContainer == null) {
-							valid = itemIds.isEmpty(); //empty podcast
-						} else {
-							int validItemsCount = 0;
-							for (String itemId : itemIds) {
-								// Try loading each item
-								VFSItem itemContainer = itemsContainer.resolve(itemId);
-								Item item = FeedManager.getInstance().loadItem(itemContainer);
-								if (item != null) {
-									// This item is valid, increase the counter
-									validItemsCount++;
-								}
-							}
-							if (validItemsCount == itemIds.size()) {
-								// The feed and all items are valid
-								valid = true;
-							}
-						}
-					} else if (feed.isExternal()) {
-						// assume the feed url is valid.
-						valid = true;
-					} else if (feed.isUndefined()) {
-						// the feed is empty.
-						valid = true;
-					}
-					// check type
-					if (!type.equals(feed.getResourceableTypeName())) {
-						valid = false;
-					}
-				}
-			} catch (Exception e) {
-				// Reading feed failed, the directory is hence invalid
+		// try to read feed file
+		try {
+			Feed feed = FeedManager.getInstance().loadFeedFromXML(directory.toPath());
+			if (feed != null && type.equals(feed.getResourceableTypeName())) {
+				valid = true;
 			}
+		} catch (Exception e) {
+			// Reading feed failed, the directory is hence invalid
+			log.error("", e);
 		}
 		return valid;
 	}
diff --git a/src/main/java/org/olat/modules/card2brain/manager/Card2BrainManagerImpl.java b/src/main/java/org/olat/modules/card2brain/manager/Card2BrainManagerImpl.java
index 42a1c3b6367..e7963a550e8 100644
--- a/src/main/java/org/olat/modules/card2brain/manager/Card2BrainManagerImpl.java
+++ b/src/main/java/org/olat/modules/card2brain/manager/Card2BrainManagerImpl.java
@@ -66,8 +66,8 @@ public class Card2BrainManagerImpl implements Card2BrainManager {
 		boolean setOfFlashcardExists = false;
 
 		String url = String.format(card2brainModule.getPeekViewUrl(), alias);
-		HttpGet request = new HttpGet(url);
 		
+		HttpGet request = new HttpGet(url);
 		try(CloseableHttpClient httpclient = HttpClients.createDefault();
 				CloseableHttpResponse response = httpclient.execute(request);) {
 			setOfFlashcardExists = isSetOfFlashcardExisting(response);
diff --git a/src/main/java/org/olat/modules/webFeed/Enclosure.java b/src/main/java/org/olat/modules/webFeed/Enclosure.java
new file mode 100644
index 00000000000..5da05fc3ed5
--- /dev/null
+++ b/src/main/java/org/olat/modules/webFeed/Enclosure.java
@@ -0,0 +1,72 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <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 the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <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>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.webFeed;
+
+/**
+ * Enclosure is a sub-element of an RSS item element. It can refer to a media
+ * uploaded File like an mp3-song or an mpeg-video.
+ * 
+ * Initial date: 02.05.2017<br>
+ * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
+ *
+ */
+public interface Enclosure {
+
+	/**
+	 * @return Returns the fileName.
+	 */
+	String getFileName();
+
+	/**
+	 * @return Returns the type.
+	 */
+	String getType();
+
+	/**
+	 * @return Returns the length.
+	 */
+	Long getLength();
+
+	/**
+	 * @return Returns the externalUrl.
+	 */
+	String getExternalUrl();
+
+	/**
+	 * @param url
+	 */
+	void setExternalUrl(String url);
+
+	/**
+	 * @param type
+	 */
+	void setType(String type);
+
+	/**
+	 * @param length
+	 */
+	void setLength(Long length);
+
+	/**
+	 * @param saveFileName
+	 */
+	void setFileName(String saveFileName);
+
+}
\ No newline at end of file
diff --git a/src/main/java/org/olat/modules/webFeed/ExternalFeedFetcher.java b/src/main/java/org/olat/modules/webFeed/ExternalFeedFetcher.java
new file mode 100644
index 00000000000..a4683d407b6
--- /dev/null
+++ b/src/main/java/org/olat/modules/webFeed/ExternalFeedFetcher.java
@@ -0,0 +1,51 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <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 the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <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>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.webFeed;
+
+import java.util.List;
+
+/**
+ * An ExternalFeedFetcher allows to retrieve feeds and items from 
+ * external web sites. It is responsible for the http communication
+ * and the convertation to the internal feed model.
+ * 
+ * Initial date: 12.05.2017<br>
+ * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
+ *
+ */
+public interface ExternalFeedFetcher {
+	
+	/**
+	 * Fetches an feed from an external web sites and updates the feed with
+	 * the current attributes
+	 * 
+	 * @param feed
+	 * @return the updated feed
+	 */
+	public Feed fetchFeed(Feed feed);
+	
+	/**
+	 * Fetches the items of a feed and converts it to the internal feed model.
+	 * @param feed
+	 * @return the fetched items
+	 */
+	public List<Item> fetchItems(Feed feed);
+
+}
diff --git a/src/main/java/org/olat/modules/webFeed/Feed.java b/src/main/java/org/olat/modules/webFeed/Feed.java
new file mode 100644
index 00000000000..aea3f64fbec
--- /dev/null
+++ b/src/main/java/org/olat/modules/webFeed/Feed.java
@@ -0,0 +1,110 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <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 the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <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>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.webFeed;
+
+import java.util.Date;
+
+import org.olat.core.id.OLATResourceable;
+
+/**
+ * This is an OpenOLAT feed (or web/news feed) model. It stores all necessary
+ * information of a feed including items.
+ * 
+ * Initial date: 02.05.2017<br>
+ * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
+ *
+ */
+public interface Feed extends OLATResourceable {
+
+	public Long getKey();
+	
+	public void setKey(Long key);
+
+	/**
+	 * @see org.olat.core.id.OLATResourceable#getResourceableId()
+	 */
+	@Override
+	public Long getResourceableId();
+	
+	public void setResourceableId(Long resourceableId);
+
+	/**
+	 * @see org.olat.core.id.OLATResourceable#getResourceableTypeName()
+	 */
+	@Override
+	public String getResourceableTypeName();
+
+	public Date getCreationDate();
+
+	public void setCreationDate(Date creationDate);
+
+	public Date getLastModified();
+
+	public void setLastModified(Date date);
+
+	public String getTitle();
+
+	public void setTitle(String title);
+
+	public String getDescription();
+
+	public void setDescription(String description);
+
+	public String getAuthor();
+
+	public void setAuthor(String initialAuthor);
+
+	public String getImageName();
+
+	public void setImageName(String name);
+
+	public String getExternalImageURL();
+
+	public void setExternalImageURL(String externalImageURL);
+
+	public String getExternalFeedUrl();
+
+	public void setExternalFeedUrl(String externalFeedUrl);
+
+	/**
+	 * Set whether the Feed is external (true), internal (false) or undefined (null).
+	 * This method should not be called directly, instead use
+	 * @see org.olat.modules.webFeed.FeedManager#method(java.lang.Boolean external, org.olat.modules.webFeed.Feed) .
+	 * 
+	 * @param isExternal
+	 */
+	public void setExternal(Boolean isExternal);
+	
+	public Boolean getExternal();
+	
+	public boolean isExternal();
+
+	public boolean isInternal();
+
+	/**
+	 * @return true if it is still undefined whether it is an internal or an external feed.
+	 */
+	public boolean isUndefined();
+
+	public int getModelVersion();
+	
+	public void setModelVersion(int version);
+
+}
\ No newline at end of file
diff --git a/src/main/java/org/olat/modules/webFeed/FeedViewHelper.java b/src/main/java/org/olat/modules/webFeed/FeedViewHelper.java
index c8bc1cda49b..23afeb033b0 100644
--- a/src/main/java/org/olat/modules/webFeed/FeedViewHelper.java
+++ b/src/main/java/org/olat/modules/webFeed/FeedViewHelper.java
@@ -42,11 +42,8 @@ import org.olat.core.util.filter.FilterFactory;
 import org.olat.core.util.resource.OresHelper;
 import org.olat.course.CourseModule;
 import org.olat.modules.webFeed.dispatching.Path;
-import org.olat.modules.webFeed.managers.FeedManager;
-import org.olat.modules.webFeed.models.Enclosure;
-import org.olat.modules.webFeed.models.Feed;
-import org.olat.modules.webFeed.models.Item;
-import org.olat.modules.webFeed.models.ItemPublishDateComparator;
+import org.olat.modules.webFeed.manager.FeedManager;
+import org.olat.modules.webFeed.model.ItemPublishDateComparator;
 import org.olat.modules.webFeed.portfolio.LiveBlogArtefactHandler;
 import org.olat.repository.RepositoryEntry;
 import org.olat.repository.RepositoryManager;
@@ -67,8 +64,7 @@ public class FeedViewHelper {
 	
 	// display 5 items per default
 	private int itemsPerPage = 5;
-	private Feed feed;
-	private String feedAuthor;
+	private Feed helperFeed;
 	private Identity identity;
 	private Translator translator;
 	private Locale locale;
@@ -77,27 +73,22 @@ public class FeedViewHelper {
 	private static final String MEDIA_DIR = Path.MEDIA_DIR;
 	// Per default show the first page
 	private int page = 0;
-	private List<Item> cachedItems;
-	//
+
 	private FeedManager feedManager = FeedManager.getInstance();
 
 	/**
 	 * Use this constructor for localized content (like e.g. date formats)
-	 * 
-	 * @param feed
 	 * @param identity
-	 * @param feedAuthor The full name's of the author
+	 * @param feed
 	 * @param locale
 	 */
-	public FeedViewHelper(Feed feed, Identity identity, String feedAuthor, Translator translator, Long courseId, String nodeId, FeedSecurityCallback callback) {
-		this.feed = feed;
+	public FeedViewHelper(Feed feed, Identity identity, Translator translator, Long courseId, String nodeId) {
+		this.helperFeed = feed;
 		this.identity = identity;
-		this.feedAuthor = feedAuthor;
 		this.translator = translator;
 		this.locale = translator.getLocale();
 		this.courseId = courseId;
 		this.nodeId = nodeId;
-		this.cachedItems = feed.getFilteredItems(callback, identity);
 		this.setURIs();
 	}
 
@@ -108,7 +99,7 @@ public class FeedViewHelper {
 	 * @param identityKey
 	 */
 	FeedViewHelper(Feed feed, Identity identity, Long courseId, String nodeId) {
-		this.feed = feed;
+		this.helperFeed = feed;
 		this.identity = identity;
 		
 		this.courseId = courseId;
@@ -116,29 +107,25 @@ public class FeedViewHelper {
 		this.setURIs();
 	}
 
-	public String getFeedAuthor() {
-		return feedAuthor;
-	}
-
 	/**
 	 * Set the base uri of an internal feed. <br>
 	 * E.g http://my.olat.org/olat/feed/ident/[IDKEY]/token/[TOKEN]/id/[ORESID]
 	 */
 	public void setURIs() {
 		// Set feed base URI for internal feeds
-		if (feed.isInternal()) {
-			baseUri = FeedManager.getInstance().getFeedBaseUri(feed, identity, courseId, nodeId);
+		if (helperFeed.isInternal()) {
+			baseUri = FeedManager.getInstance().getFeedBaseUri(helperFeed, identity, courseId, nodeId);
 			feedUrl = baseUri + "/" + FeedManager.RSS_FEED_NAME;
-		} else if (feed.isExternal()) {
+		} else if (helperFeed.isExternal()) {
 			// The base uri is needed for dispatching the picture
-			baseUri = FeedManager.getInstance().getFeedBaseUri(feed, identity, courseId, nodeId);
-			feedUrl = feed.getExternalFeedUrl();
+			baseUri = FeedManager.getInstance().getFeedBaseUri(helperFeed, identity, courseId, nodeId);
+			feedUrl = helperFeed.getExternalFeedUrl();
 		} else {
 			// feed is undefined
 			// The base uri is needed for dispatching the picture
-			baseUri = FeedManager.getInstance().getFeedBaseUri(feed, identity, courseId, nodeId);
+			baseUri = FeedManager.getInstance().getFeedBaseUri(helperFeed, identity, courseId, nodeId);
 			feedUrl = null;
-			feed.setExternalImageURL(null);
+			helperFeed.setExternalImageURL(null);
 		}
 	}
 
@@ -183,49 +170,58 @@ public class FeedViewHelper {
 	}
 
 	/**
-	 * @param item
-	 * @return The media url of the item
+	 * Format the lastModified of the feed.
+	 * @param feed
+	 * @return
 	 */
-	public String getMediaUrl(Item item) {
-		// Reload item to prevent displaying of stale content
-		feed = feedManager.getFeed(feed);
-		item = feedManager.getItem(feed, item.getGuid());
-		if(item == null) {
-			return null;
-		}
-
-		String file = null;
-		Enclosure enclosure = item.getEnclosure();
-		if (enclosure != null) {
-			if (feed.isExternal()) {
-				file = item.getEnclosure().getExternalUrl();
-			} else if (feed.isInternal()) {
-				file = this.baseUri + "/" + item.getGuid() + "/" + MEDIA_DIR + "/" + enclosure.getFileName();
-			}
+	public String getLastModified(Feed feed) {
+		String lastModified = null;
+		
+		Date date = feed.getLastModified();
+		if (date != null) {
+			lastModified = DateFormat.getDateInstance(DateFormat.MEDIUM, this.locale).format(date);
 		}
-		return file;
+		
+		return lastModified;
 	}
 
 	/**
-	 * @return The feed image url
+	 * Get the concatenated URL of the Image of an internal feed or the URL of
+	 * the Image of an external Feed.
+	 * 
+	 * @param feed
+	 * @return
 	 */
-	public String getImageUrl() {
+	public String getImageUrl(Feed feed) {
 		String imageUrl = null;
+		
 		if (feed.getImageName() != null) {
 			imageUrl = baseUri + "/" + MEDIA_DIR + "/" + feed.getImageName();
 		} else if (feed.getExternalImageURL() != null) {
 			// If there's no custom image and the feed contains an image, use it!
 			imageUrl = feed.getExternalImageURL();
 		}
+		
 		return imageUrl;
 	}
 
+	/**
+	 * The feed description with dispatchable media file paths
+	 * @param feed
+	 * @return
+	 */
+	public String getFeedDescriptionForBrowser(Feed feed) {
+		Filter mediaUrlFilter = FilterFactory.getBaseURLToMediaRelativeURLFilter(baseUri);
+		return mediaUrlFilter.filter(feed.getDescription());
+	}
+
 	/**
 	 * @param enclosure
 	 * @return The media type (audio or video)
 	 */
 	public String getMediaType(Enclosure enclosure) {
 		String mediaType = null;
+		
 		if (enclosure != null) {
 			// type is like 'video/mpeg' or 'audio/mpeg'
 			String type = enclosure.getType();
@@ -236,160 +232,252 @@ public class FeedViewHelper {
 				}
 			}
 		}
+		
 		return mediaType;
 	}
 
 	/**
+	 * Information about the item mode. 
+	 * 
 	 * @param item
-	 * @return The formatted last modified date string of the item
+	 * @return Is it draft, scheduled or published?
 	 */
-	public String getLastModified(Item item) {
-		// Reload item to prevent displaying of stale content
-		feed = feedManager.getFeed(feed);
-		item = feedManager.getItem(feed, item.getGuid());
-
-		String lastModified = null;
-		Date date = item == null ? null : item.getLastModified();
-		if (date != null) {
-			lastModified = DateFormat.getDateInstance(DateFormat.MEDIUM, this.locale).format(date);
+	public String getInfo(Item item) {
+		String info = null;
+		
+		if (item == null) {
+			info = "";
+		} else if (item.isDraft()) {
+			info = translator.translate("feed.item.draft");
+		} else if (item.isScheduled()) {
+			info = translator.translate("feed.item.scheduled.for", new String[] { getPublishDate(item) });
+		} else if (item.isPublished()) {
+			info = getPublishInfo(item);
 		}
-		return lastModified;
+		
+		return info;
 	}
 
 	/**
+	 * Get information about publication date and author.
+	 * 
 	 * @param item
-	 * @return The formatted last modified date string of the item
+	 * @return
 	 */
-	private String getPublishDate(Item item) {
-		// Reload item to prevent displaying of stale content
-		feed = feedManager.getFeed(feed);
-		item = feedManager.getItem(feed, item.getGuid());
-		if(item == null) {
-			return "";
+	private String getPublishInfo(Item item) {
+		String publishInfo = "";
+		
+		if (item != null) {
+			String date = getPublishDate(item);
+			String author = StringHelper.escapeHtml(item.getAuthor());
+			if (author != null) {
+				if (date != null) {
+					publishInfo = translator.translate("feed.published.by.on", new String[] { author, date });
+				} else {
+					publishInfo = translator.translate("feed.published.by", new String[] { author });
+				}
+			} else if (date != null) {
+				publishInfo = translator.translate("feed.published.on", new String[] { date });
+			}
 		}
 
-		String publishDate = null;
-		Date date = item.getPublishDate();
-		if (date != null) {
-			publishDate = DateFormat.getDateInstance(DateFormat.MEDIUM, this.locale).format(date);
-		}
-		return publishDate;
+		return publishInfo;
 	}
 
 	/**
+	 * Get the formatted last modified date string of the item.
+	 * 
 	 * @param item
-	 * @return Information about publication date and author
+	 * @return
 	 */
-	private String getPublishInfo(Item item) {
-		// Reload item to prevent displaying of stale content
-		feed = feedManager.getFeed(feed);
-		item = feedManager.getItem(feed, item.getGuid());
-		if(item == null) {
-			return "";
-		}
-
-		String info = null;
-		String date = getPublishDate(item);
-		String author = StringHelper.escapeHtml(item.getAuthor());
-		if (author != null) {
-			if (date != null) {
-				info = translator.translate("feed.published.by.on", new String[] { author, date });
-			} else {
-				info = translator.translate("feed.published.by", new String[] { author });
-			}
-		} else {
+	private String getPublishDate(Item item) {
+		String publishDate = "";
+		
+		if (item != null) {
+			Date date = item.getPublishDate();
 			if (date != null) {
-				info = translator.translate("feed.published.on", new String[] { date });
-			} else {
-				// no publication info available
+				publishDate = DateFormat.getDateInstance(DateFormat.MEDIUM, this.locale).format(date);
 			}
 		}
-		return info;
+		
+		return publishDate;
 	}
 
 	/**
+	 * Get Information about the modifier.
+	 * 
 	 * @param item
-	 * @return Information about the item. Is it draft, scheduled or published?
+	 * @return 
 	 */
-	public String getInfo(Item item) {
-		// Reload item to prevent displaying of stale content
-		feed = feedManager.getFeed(feed);
-		item = feedManager.getItem(feed, item.getGuid());
-
-		String info = null;
-		if(item == null) {
-			//oops deleted
-			info = "";
-		} else if (item.isDraft()) {
-			info = translator.translate("feed.item.draft");
-		} else if (item.isScheduled()) {
-			info = translator.translate("feed.item.scheduled.for", new String[] { getPublishDate(item) });
-		} else if (item.isPublished()) {
-			info = getPublishInfo(item);
+	public String getModifierInfo(Item item) {
+		String modifierInfo = "";
+	
+		if (isModified(item)) {
+			String date = getLastModified(item);
+			String modifier = item.getModifier();
+			modifierInfo = translator.translate("feed.modified.by.on", new String[]{ modifier, date});
 		}
-		return info;
+		
+		return modifierInfo;
 	}
-	
+
+	/**
+	 * Check if the Item was modified at least once.
+	 * 
+	 * @param item
+	 * @return
+	 */
 	public boolean isModified(Item item) {
-		// Reload item to prevent displaying of stale content
-		feed = feedManager.getFeed(feed);
-		item = feedManager.getItem(feed, item.getGuid());
-		return item != null && item.getModifierKey() > 0 && StringHelper.containsNonWhitespace(item.getModifier());
+		boolean isModified = false;
+		
+		if (item != null) {
+			isModified = item.getModifierKey() != null && StringHelper.containsNonWhitespace(item.getModifier());
+		}
+		
+		return isModified;
 	}
-	
+
 	/**
+	 * Get the formatted last modified date of the Item.
+	 * 
 	 * @param item
-	 * @return Information about the item. Is it draft, scheduled or published?
+	 * @return
 	 */
-	public String getModifierInfo(Item item) {
-		// Reload item to prevent displaying of stale content
-		feed = feedManager.getFeed(feed);
-		item = feedManager.getItem(feed, item.getGuid());
-		if(item == null) {
-			return "";
+	public String getLastModified(Item item) {
+		String lastModified = null;
+		
+		if (item != null) {
+			Date date = item.getLastModified();
+			if (date != null) {
+				lastModified = DateFormat.getDateInstance(DateFormat.MEDIUM, this.locale).format(date);
+			}
 		}
+		
+		return lastModified;
+	}
 
-		if (isModified(item)) {
-			String date = getLastModified(item);
-			String modifier = item.getModifier();
-			return translator.translate("feed.modified.by.on", new String[]{ modifier, date});
+	/**
+	 * Get the item description with media file paths that are dispatchable by
+	 * a FeedMediaDispatcher.
+	 *         
+	 * @param item
+	 * @return 
+	 */
+	public String getItemDescriptionForBrowser(Item item) {
+		String itemDescription = "";
+		
+		if (item != null) {
+			String description = item.getDescription();
+			if (description != null) {
+				if (helperFeed.isExternal()) {
+					// Apply xss filter for security reasons. Only necessary for external
+					// feeds (e.g. to not let them execute JS code in our OLAT environment)
+					Filter xssFilter = FilterFactory.getXSSFilter(description.length() + 1);
+					itemDescription = xssFilter.filter(description);
+				} else {
+					// Add relative media base to media elements to display internal media
+					// files
+					String basePath = baseUri + "/" + item.getGuid();
+					Filter mediaUrlFilter = FilterFactory.getBaseURLToMediaRelativeURLFilter(basePath);
+					itemDescription = mediaUrlFilter.filter(description);
+				}
+			}
+			itemDescription = Formatter.formatLatexFormulas(itemDescription);
 		}
-		return null;
+
+		return itemDescription;
 	}
 
 	/**
-	 * @return The formatted last modified date string of the feed
+	 * The item content with media file paths that are dispatchable by
+	 * a FeedMediaDispatcher.
+	 *         
+	 * @param item
+	 * @return
 	 */
-	public String getLastModified() {
-		String lastModified = null;
-		Date date = feed.getLastModified();
-		if (date != null) {
-			lastModified = DateFormat.getDateInstance(DateFormat.MEDIUM, this.locale).format(date);
+	public String getItemContentForBrowser(Item item) {
+		String itemContent = "";
+		
+		if (item != null) {
+			String content = item.getContent();
+			if (content != null) {
+				if (helperFeed.isExternal()) {
+					// Apply xss filter for security reasons. Only necessary for external
+					// feeds (e.g. to not let them execute JS code in our OLAT environment)
+					Filter xssFilter = FilterFactory.getXSSFilter(content.length() + 1);
+					itemContent = xssFilter.filter(content);
+				} else {
+					// Add relative media base to media elements to display internal media
+					// files
+					String basePath = baseUri + "/" + item.getGuid();
+					Filter mediaUrlFilter = FilterFactory.getBaseURLToMediaRelativeURLFilter(basePath);
+					itemContent = mediaUrlFilter.filter(content);
+				}
+			}
 		}
-		return lastModified;
+		
+		return itemContent;
 	}
-	
+
+	/**
+	 * Get the width of the Item. The width is limited to a range of 0-2000.
+	 * 
+	 * @param item
+	 * @return
+	 */
 	public String getWidth(Item item) {
-		// Reload item to prevent displaying of stale content
-		feed = feedManager.getFeed(feed);
-		item = feedManager.getItem(feed, item.getGuid());
-		int width = item == null ? 0 : item.getWidth();
-		if(width > 0 && width < 2000) {
-			return Integer.toString(width);
+		String widthString = "400";
+		
+		if (item != null) {
+			Integer width = item.getWidth();
+			if (width != null && width > 0 && width < 2000) {
+				widthString = Integer.toString(width);
+			}
 		}
-		return "400";
+		
+		return widthString;
 	}
 	
+	/**
+	 * Get the height of the Item. The height is limited to a range of 0-2000.
+	 * 
+	 * @param item
+	 * @return
+	 */
 	public String getHeight(Item item) {
-		// Reload item to prevent displaying of stale content
-		feed = feedManager.getFeed(feed);
-		item = feedManager.getItem(feed, item.getGuid());
+		String heightString = "400";
+		
+		if (item != null) {
+			Integer height = item.getHeight();
+			if (height != null && height > 0 && height < 2000) {
+				heightString = Integer.toString(height);
+			}
+		}
+		
+		return heightString;
+	}
 
-		int height = item == null ? 0 : item.getHeight();
-		if(height > 0 && height < 2000) {
-			return Integer.toString(height);
+	/**
+	 * @param item
+	 * @return The media url of the item
+	 */
+	public String getMediaUrl(Item item) {
+		// Reload item to prevent displaying of stale content
+		item = feedManager.loadItem(item.getKey());
+		if(item == null) {
+			return null;
+		}
+	
+		String file = null;
+		Enclosure enclosure = item.getEnclosure();
+		if (enclosure != null) {
+			if (helperFeed.isExternal()) {
+				file = item.getEnclosure().getExternalUrl();
+			} else if (helperFeed.isInternal()) {
+				file = this.baseUri + "/" + item.getGuid() + "/" + MEDIA_DIR + "/" + enclosure.getFileName();
+			}
 		}
-		return "300";
+		return file;
 	}
 
 	/**
@@ -403,99 +491,32 @@ public class FeedViewHelper {
 			OLATResourceable oresCourse = OLATResourceManager.getInstance().findResourceable(courseId, CourseModule.getCourseTypeName());
 			OLATResourceable oresNode = OresHelper.createOLATResourceableInstance("CourseNode", Long.valueOf(nodeId));
 			RepositoryEntry repositoryEntry = resMgr.lookupRepositoryEntry(oresCourse, false);
-			List<ContextEntry> ces = new ArrayList<ContextEntry>();
+			List<ContextEntry> ces = new ArrayList<>();
 			ces.add(BusinessControlFactory.getInstance().createContextEntry(repositoryEntry));
 			ces.add(BusinessControlFactory.getInstance().createContextEntry(oresNode));
 			jumpInLink = BusinessControlFactory.getInstance().getAsURIString(ces, false);
 		} else {
-			RepositoryEntry repositoryEntry = resMgr.lookupRepositoryEntry(feed, false);
+			RepositoryEntry repositoryEntry = resMgr.lookupRepositoryEntry(helperFeed, false);
 			if (repositoryEntry != null){
 				ContextEntry ce = BusinessControlFactory.getInstance().createContextEntry(repositoryEntry);
 				jumpInLink = BusinessControlFactory.getInstance().getAsURIString(Collections.singletonList(ce), false);
 			} else {
-				// its a liveblog-feed
+				// its a liveblog-helperFeed
 				final BusinessControlFactory bCF = BusinessControlFactory.getInstance();
-				String feedBP = LiveBlogArtefactHandler.LIVEBLOG + feed.getResourceableId() + "]";
+				String feedBP = LiveBlogArtefactHandler.LIVEBLOG + helperFeed.getResourceableId() + "]";
 				final List<ContextEntry> ceList = bCF.createCEListFromString(feedBP);
 				jumpInLink = bCF.getAsURIString(ceList, true);
 			}
 		}
 		if(item != null && jumpInLink != null){
-			jumpInLink += "/item=" + item.getGuid() +"/0";
+			jumpInLink += "/item=" + item.getKey() +"/0";
 		}
 		return jumpInLink;
 	}
 
-	/**
-	 * @param item
-	 * @return The item description with media file paths that are dispatchable by
-	 *         the FeedMediaDispatcher
-	 */
-	public String getItemDescriptionForBrowser(Item item) {
-		// Reload item to prevent displaying of stale content
-		feed = feedManager.getFeed(feed);
-		item = feedManager.getItem(feed, item.getGuid());
-		if(item == null) {
-			return "";
-		}
-		
-		String itemDescription = item.getDescription();
-		if (itemDescription != null) {
-			if (feed.isExternal()) {
-				// Apply xss filter for security reasons. Only necessary for external
-				// feeds (e.g. to not let them execute JS code in our OLAT environment)
-				Filter xssFilter = FilterFactory.getXSSFilter(itemDescription.length() + 1);
-				itemDescription = xssFilter.filter(itemDescription);
-			} else {
-				// Add relative media base to media elements to display internal media
-				// files
-				String basePath = baseUri + "/" + item.getGuid();
-				Filter mediaUrlFilter = FilterFactory.getBaseURLToMediaRelativeURLFilter(basePath);
-				itemDescription = mediaUrlFilter.filter(itemDescription);
-			}
-		}
-		itemDescription = Formatter.formatLatexFormulas(itemDescription);
-		return itemDescription;
-	}
 	
-	/**
-	 * @param item
-	 * @return The item content with media file paths that are dispatchable by
-	 *         the FeedMediaDispatcher
-	 */
-	public String getItemContentForBrowser(Item item) {
-		// Reload item to prevent displaying of stale content
-		feed = feedManager.getFeed(feed);
-		item = feedManager.getItem(feed, item.getGuid());
-		if(item == null) {
-			return "";
-		}
-		
-		String itemContent = item.getContent();
-		if (itemContent != null) {
-			if (feed.isExternal()) {
-				// Apply xss filter for security reasons. Only necessary for external
-				// feeds (e.g. to not let them execute JS code in our OLAT environment)
-				Filter xssFilter = FilterFactory.getXSSFilter(itemContent.length() + 1);
-				itemContent = xssFilter.filter(itemContent);
-			} else {
-				// Add relative media base to media elements to display internal media
-				// files
-				String basePath = baseUri + "/" + item.getGuid();
-				Filter mediaUrlFilter = FilterFactory.getBaseURLToMediaRelativeURLFilter(basePath);
-				itemContent = mediaUrlFilter.filter(itemContent);
-			}
-		}
-		return itemContent;
-	}
 
-	/**
-	 * @return The feed description with dispatchable media file paths
-	 */
-	public String getFeedDescriptionForBrowser() {
-		Filter mediaUrlFilter = FilterFactory.getBaseURLToMediaRelativeURLFilter(baseUri);
-		return mediaUrlFilter.filter(feed.getDescription());
-	}
+	
 
 	/* Used for paging */
 
@@ -507,16 +528,7 @@ public class FeedViewHelper {
 	 * Show older items, meaning go to the next page.
 	 */
 	public void olderItems() {
-		if (hasOlderItems()) {
-			page++;
-		}
-	}
-
-	/**
-	 * @return True there are newer items to display
-	 */
-	public boolean hasOlderItems() {
-		return cachedItems.size() > itemsPerPage * (page + 1);
+		page++;
 	}
 
 	/**
@@ -530,113 +542,65 @@ public class FeedViewHelper {
 	}
 
 	/**
-	 * Go to the startpage
+	 * Go to the start page
+	 * 
 	 */
 	public void startpage() {
 		page = 0;
 	}
 
 	/**
-	 * @return True if there are newer items to display
+	 * Check if there are newer items to display
+	 * 
+	 * @return
 	 */
 	public boolean hasNewerItems() {
 		return page > 0;
 	}
 
 	/**
-	 * @param callback
-	 * @return The items count of all displayed (accessible) items
+	 * Check if there are older Items to display.
+	 * 
+	 * @param items
+	 * @return
 	 */
-	public int itemsCount(FeedSecurityCallback callback) {
-		if (cachedItems == null) {
-			cachedItems = feed.getFilteredItems(callback, identity);
-		}
-		return cachedItems.size();
+	public boolean hasOlderItems(List<Item> items) {
+		return items.size() > itemsPerPage * (page + 1);
 	}
 
 	/**
-	 * @return The items to be displayed on the current page
+	 * Get all displayed items inside the paged list of items.
+	 * 
+	 * @param items the already sorted items
+	 * @return
 	 */
-	public List<Item> getItems(FeedSecurityCallback callback) {
-		List<Item> itemsOnPage = new ArrayList<Item>(itemsPerPage);
-		if (cachedItems == null) {
-			cachedItems = feed.getFilteredItems(callback, identity);
-		}
+	public List<Item> getItemsOnPage(List<Item> items) {
+		List<Item> itemsOnPage = new ArrayList<>(itemsPerPage);
+		
 		final int start = page * itemsPerPage;
-		final int end = Math.min(cachedItems.size(), start + itemsPerPage);
+		final int end = Math.min(items.size(), start + itemsPerPage);
 		for (int i = start; i < end; i++) {
-			itemsOnPage.add(cachedItems.get(i));
+			itemsOnPage.add(items.get(i));
 		}
+		Collections.sort(itemsOnPage, new ItemPublishDateComparator());
+		
 		return itemsOnPage;
 	}
 
 	/**
-	 * @param selectedItems
-	 */
-	public void setSelectedItems(List<Item> selectedItems) {
-		this.cachedItems = selectedItems;
-		// go to the first page
-		page = 0;
-	}
-
-	/**
-	 * Removes the item from the current selection of items
-	 * 
-	 * @param item The item to remove
-	 */
-	public void removeItem(Item item) {
-		cachedItems.remove(item);
-	}
-
-	/**
-	 * Adds the item to the current selection of items.
-	 * 
-	 * @param item The item to add
-	 */
-	public void addItem(Item item) {
-		if (!cachedItems.contains(item)) {
-			cachedItems.add(item);
-		}
-		Collections.sort(cachedItems, new ItemPublishDateComparator());
-	}
-
-	/**
-	 * Update the given item in the current selection of items. The code will
-	 * replace the item with the same GUID in the current selection of items.
-	 * 
-	 * @param item The item to update
-	 */
-	public void updateItem(Item item) {
-		if (cachedItems.contains(item)) {
-			// Remove old version first. Not necessarily the same on object level
-			// since item overrides the equal method
-			cachedItems.remove(item);
-		}
-		addItem(item);
-	}
-
-	/**
-	 * Resets the item selection to all accessible items of the feed
-	 * 
-	 * @param callback
-	 */
-	public void resetItems(FeedSecurityCallback callback) {
-		feed = feedManager.getFeed(feed);
-		cachedItems = feed.getFilteredItems(callback, identity);
-	}
-
-	/**
-	 * Check if the current user is the author of this feed item
+	 * Check if the current user is the author of this helperFeed item
 	 * @param item
 	 * @return
 	 */
 	public boolean isAuthor(Item item) {
+		boolean isAuthor = false;
 		if (item != null) {
-			if (item.getAuthorKey() == identity.getKey().longValue()) {
-				return true;
+			Long authorKey = item.getAuthorKey();
+			if (authorKey != null && item.getAuthorKey() == identity.getKey().longValue()) {
+				isAuthor = true;
 			}
 		}
-		return false;
+		return isAuthor;
 	}
 
 }
\ No newline at end of file
diff --git a/src/main/java/org/olat/modules/webFeed/Item.java b/src/main/java/org/olat/modules/webFeed/Item.java
new file mode 100644
index 00000000000..86847195e97
--- /dev/null
+++ b/src/main/java/org/olat/modules/webFeed/Item.java
@@ -0,0 +1,158 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <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 the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <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>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.webFeed;
+
+import java.util.Date;
+
+import org.olat.core.commons.controllers.navigation.Dated;
+import org.olat.core.gui.components.form.flexible.elements.FileElement;
+
+/**
+ * 
+ * Initial date: 02.05.2017<br>
+ * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
+ *
+ */
+public interface Item extends Dated {
+	
+	public Long getKey();
+	
+	public void setKey(Long key);
+
+	@Override
+	public Date getDate();
+
+	public Date getCreationDate();
+
+	public void setCreationDate(Date date);
+
+	public Date getLastModified();
+
+	public void setLastModified(Date updatedDate);
+
+	public String getTitle();
+	
+	public void setTitle(String title);
+
+	public String getDescription();
+	
+	public void setDescription(String description);
+	
+	public String getContent();
+	
+	public void setContent(String string);
+
+	/**
+	 * The author key corresponds to the identity key of the user that created
+	 * this item. External items have no author key.
+	 * @return
+	 */
+	public Long getAuthorKey();
+	
+	public void setAuthorKey(Long key);
+
+	/**
+	 * The modifier key corresponds to the identity key of the user that
+	 * modified this item. External items have no modifier key.
+	 * @return
+	 */
+	public Long getModifierKey();
+	
+	public void setModifierKey(Long key);
+
+	/**
+	 * This is the display name of the author.
+	 * @return
+	 */
+	public String getAuthor();
+	
+	public void setAuthor(String author);
+
+	/**
+	 * This is the display name of the modifier.
+	 * External items have no modifier name.
+	 * @return
+	 */
+	public String getModifier();
+	
+	public String getGuid();
+	
+	public void setGuid(String uri);
+
+	/**
+	 * This is the link of an external item.
+	 * Internal items return null.
+	 * @return
+	 */
+	public String getExternalLink();
+	
+	public void setExternalLink(String link);
+
+	public void setDraft(boolean b);
+
+	/**
+	 * An item can either be in draft version or it is published
+	 * -> 'not draft' is equivalent to 'published'
+	 * @return
+	 */
+	public boolean isDraft();
+
+	public boolean isScheduled();
+
+	public boolean isPublished();
+
+	public Date getPublishDate();
+	
+	public void setPublishDate(Date date);
+
+	public Enclosure getEnclosure();
+	
+	public void setEnclosure(Enclosure media);
+
+	/**
+	 * Width for video podcast.
+	 * @return
+	 */
+	public Integer getWidth();
+	
+	public void setWidth(Integer width);
+
+	/**
+	 * Height for video podcast.
+	 * @return
+	 */
+	public Integer getHeight();
+	
+	public void setHeight(Integer height);
+
+	public FileElement getMediaFile();
+	
+	public void setMediaFile(FileElement file);
+
+	public boolean isAuthorFallbackSet();
+
+	public Feed getFeed();
+
+	/**
+	 * @return An extra CSS class for drafts and scheduled items
+	 */
+	public String extraCSSClass();
+	
+}
diff --git a/src/main/java/org/olat/modules/webFeed/RSSFeed.java b/src/main/java/org/olat/modules/webFeed/RSSFeed.java
index c8a780fb4d0..d91bb262f75 100644
--- a/src/main/java/org/olat/modules/webFeed/RSSFeed.java
+++ b/src/main/java/org/olat/modules/webFeed/RSSFeed.java
@@ -25,9 +25,7 @@ import java.util.List;
 import org.olat.commons.servlets.RSSServlet;
 import org.olat.core.id.Identity;
 import org.olat.core.util.filter.FilterFactory;
-import org.olat.modules.webFeed.models.Enclosure;
-import org.olat.modules.webFeed.models.Feed;
-import org.olat.modules.webFeed.models.Item;
+import org.olat.modules.webFeed.manager.FeedManager;
 
 import com.rometools.rome.feed.synd.SyndContent;
 import com.rometools.rome.feed.synd.SyndContentImpl;
@@ -80,12 +78,13 @@ public class RSSFeed extends SyndFeedImpl {
 			image.setDescription(feed.getDescription());
 			image.setTitle(feed.getTitle());
 			image.setLink(getLink());
-			image.setUrl(helper.getImageUrl());
+			image.setUrl(helper.getImageUrl(feed));
 			setImage(image);
 		}
 
-		List<SyndEntry> episodes = new ArrayList<SyndEntry>();
-		for (Item item : feed.getPublishedItems()) {
+		List<SyndEntry> episodes = new ArrayList<>();
+		List<Item> publishedItems = FeedManager.getInstance().loadPublishedItems(feed);
+		for (Item item : publishedItems) {
 			SyndEntry entry = new SyndEntryImpl();
 			entry.setTitle(item.getTitle());
 
@@ -111,7 +110,7 @@ public class RSSFeed extends SyndFeedImpl {
 				enclosure.setLength(media.getLength());
 				// Also set the item link to point to the enclosure
 				entry.setLink(helper.getMediaUrl(item));
-				List<SyndEnclosure> enclosures = new ArrayList<SyndEnclosure>();
+				List<SyndEnclosure> enclosures = new ArrayList<>();
 				enclosures.add(enclosure);
 				entry.setEnclosures(enclosures);
 			}
diff --git a/src/main/java/org/olat/modules/webFeed/_spring/webFeedContext.xml b/src/main/java/org/olat/modules/webFeed/_spring/webFeedContext.xml
index 50b7925f335..5d53ef16076 100644
--- a/src/main/java/org/olat/modules/webFeed/_spring/webFeedContext.xml
+++ b/src/main/java/org/olat/modules/webFeed/_spring/webFeedContext.xml
@@ -5,14 +5,13 @@
   http://www.springframework.org/schema/beans 
   http://www.springframework.org/schema/beans/spring-beans.xsd">
 
-<bean id="feedManager" class="org.olat.modules.webFeed.managers.FeedManagerImpl">
+<bean id="feedManager" class="org.olat.modules.webFeed.manager.FeedManagerImpl">
 	<constructor-arg index="0" ref="resourceManager"/>
 	<constructor-arg index="1" ref="fileresourceManager"/>
 	<constructor-arg index="2" ref="coordinatorManager"/>
 	<!-- repositoryManager is set by setter as we mock it for testing. 
 	-> To mock managers it is often more practical to have setters than constr. args! -->
 	<property name="repositoryManager" ref="repositoryManager" />
-	<property name="imageHelper" ref="imageHelper" />
 </bean>
 
 <bean id="liveBlogContextEntryControllerCreator" class="org.olat.modules.webFeed.portfolio.LiveBlogContextEntryControllerCreator">
diff --git a/src/main/java/org/olat/modules/webFeed/dispatching/FeedMediaDispatcher.java b/src/main/java/org/olat/modules/webFeed/dispatching/FeedMediaDispatcher.java
index 9f4eaaa0dd5..fcb58b9b072 100644
--- a/src/main/java/org/olat/modules/webFeed/dispatching/FeedMediaDispatcher.java
+++ b/src/main/java/org/olat/modules/webFeed/dispatching/FeedMediaDispatcher.java
@@ -57,8 +57,8 @@ import org.olat.course.run.userview.UserCourseEnvironmentImpl;
 import org.olat.course.run.userview.VisibleTreeFilter;
 import org.olat.fileresource.types.BlogFileResource;
 import org.olat.fileresource.types.PodcastFileResource;
-import org.olat.modules.webFeed.managers.FeedManager;
-import org.olat.modules.webFeed.models.Feed;
+import org.olat.modules.webFeed.Feed;
+import org.olat.modules.webFeed.manager.FeedManager;
 import org.olat.portfolio.manager.EPFrontendManager;
 import org.olat.repository.RepositoryEntry;
 import org.olat.repository.RepositoryManager;
@@ -172,7 +172,7 @@ public class FeedMediaDispatcher extends LogDelegator implements Dispatcher {
 			Identity identity = getIdentity(path.getIdentityKey());
 			long sinceModifiedMillis = request.getDateHeader("If-Modified-Since");
 			
-			Feed feedLight = manager.getFeed(feed);
+			Feed feedLight = manager.loadFeed(feed);
 			long lastModifiedMillis = -1;
 			if (feedLight != null) {
 				lastModifiedMillis = feedLight.getLastModified().getTime();
diff --git a/src/main/java/org/olat/modules/webFeed/dispatching/Path.java b/src/main/java/org/olat/modules/webFeed/dispatching/Path.java
index 94ca7f21a4c..70d47f492bc 100644
--- a/src/main/java/org/olat/modules/webFeed/dispatching/Path.java
+++ b/src/main/java/org/olat/modules/webFeed/dispatching/Path.java
@@ -34,8 +34,8 @@ import org.olat.core.id.OLATResourceable;
 import org.olat.core.util.cache.CacheWrapper;
 import org.olat.core.util.coordinate.CoordinatorManager;
 import org.olat.core.util.coordinate.SyncerExecutor;
-import org.olat.modules.webFeed.managers.FeedManager;
-import org.olat.modules.webFeed.models.Feed;
+import org.olat.modules.webFeed.Feed;
+import org.olat.modules.webFeed.manager.FeedManager;
 import org.olat.repository.RepositoryEntry;
 import org.olat.repository.RepositoryManager;
 
diff --git a/src/main/java/org/olat/modules/webFeed/managers/BlogNotificationsHandler.java b/src/main/java/org/olat/modules/webFeed/manager/BlogNotificationsHandler.java
similarity index 99%
rename from src/main/java/org/olat/modules/webFeed/managers/BlogNotificationsHandler.java
rename to src/main/java/org/olat/modules/webFeed/manager/BlogNotificationsHandler.java
index cc8537fb08b..350685b6265 100644
--- a/src/main/java/org/olat/modules/webFeed/managers/BlogNotificationsHandler.java
+++ b/src/main/java/org/olat/modules/webFeed/manager/BlogNotificationsHandler.java
@@ -17,7 +17,7 @@
  * frentix GmbH, http://www.frentix.com
  * <p>
  */
-package org.olat.modules.webFeed.managers;
+package org.olat.modules.webFeed.manager;
 
 import java.util.Date;
 import java.util.List;
diff --git a/src/main/java/org/olat/modules/webFeed/manager/FeedDAO.java b/src/main/java/org/olat/modules/webFeed/manager/FeedDAO.java
new file mode 100644
index 00000000000..34f56862d22
--- /dev/null
+++ b/src/main/java/org/olat/modules/webFeed/manager/FeedDAO.java
@@ -0,0 +1,110 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <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 the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <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>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.webFeed.manager;
+
+import java.util.Date;
+import java.util.List;
+
+import org.olat.core.commons.persistence.DB;
+import org.olat.core.id.OLATResourceable;
+import org.olat.modules.webFeed.Feed;
+import org.olat.modules.webFeed.model.FeedImpl;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * 
+ * Initial date: 02.05.2017<br>
+ * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
+ *
+ */
+@Service("feedDao")
+public class FeedDAO {
+	
+	@Autowired
+	private DB dbInstance;
+	
+	public Feed createFeedForResourcable(OLATResourceable ores) {
+		if (ores == null) return null;
+		
+		Feed feed = new FeedImpl(ores);
+		feed.setCreationDate(new Date());
+		feed.setLastModified(feed.getCreationDate());
+		dbInstance.getCurrentEntityManager().persist(feed);
+		return feed;
+	}
+	
+	public Feed createFeed(Feed feed) {
+		if (feed == null) return null;
+		
+		FeedImpl feedImpl = (FeedImpl) feed;
+		if (feedImpl.getCreationDate() == null) {
+			feedImpl.setCreationDate(new Date());
+		}
+		if (feedImpl.getLastModified() == null) {
+			feedImpl.setLastModified(feedImpl.getCreationDate());	
+		}
+		dbInstance.getCurrentEntityManager().persist(feedImpl);
+		return feed;
+	}
+	
+	public Feed copyFeed(OLATResourceable source, OLATResourceable target) {
+		if (source == null || target == null) return null;
+		
+		FeedImpl feed = (FeedImpl) loadFeed(source);
+		dbInstance.getCurrentEntityManager().detach(feed);
+		feed.setKey(null);
+		feed.setResourceableId(target.getResourceableId());
+		feed.setResourceableType(target.getResourceableTypeName());
+		dbInstance.getCurrentEntityManager().persist(feed);
+		return feed;
+	}
+	
+	public Feed loadFeed(Long key) {
+		FeedImpl feed = dbInstance.getCurrentEntityManager().find(FeedImpl.class, key);
+		return feed;
+	}
+	
+	public Feed loadFeed(OLATResourceable resourceable) {
+		List<FeedImpl> feeds = dbInstance.getCurrentEntityManager()
+				.createNamedQuery("loadFeedByRessourceable", FeedImpl.class)
+				.setParameter("key", resourceable.getResourceableId())
+				.setParameter("name", resourceable.getResourceableTypeName())
+				.getResultList();
+		return feeds.isEmpty() ? null : feeds.get(0);
+	}
+	
+	public Feed updateFeed(Feed feed) {
+		if (feed == null) return null;
+		
+		((FeedImpl)feed).setLastModified(new Date());
+		return dbInstance.getCurrentEntityManager().merge(feed);
+	}
+	
+	public void removeFeedForResourceable(OLATResourceable resourceable) {
+		if (resourceable == null) return;
+		
+		Feed feed = loadFeed(resourceable);
+		if (feed != null) {
+			dbInstance.getCurrentEntityManager().remove(feed);
+		}
+	}
+	
+}
diff --git a/src/main/java/org/olat/modules/webFeed/manager/FeedFileStorge.java b/src/main/java/org/olat/modules/webFeed/manager/FeedFileStorge.java
new file mode 100644
index 00000000000..7d9ff906825
--- /dev/null
+++ b/src/main/java/org/olat/modules/webFeed/manager/FeedFileStorge.java
@@ -0,0 +1,545 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <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 the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <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>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.webFeed.manager;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.olat.core.commons.modules.bc.vfs.OlatRootFolderImpl;
+import org.olat.core.commons.services.image.ImageService;
+import org.olat.core.gui.components.form.flexible.elements.FileElement;
+import org.olat.core.id.OLATResourceable;
+import org.olat.core.logging.OLog;
+import org.olat.core.logging.Tracing;
+import org.olat.core.util.CodeHelper;
+import org.olat.core.util.Formatter;
+import org.olat.core.util.StringHelper;
+import org.olat.core.util.vfs.LocalFileImpl;
+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.filters.VFSItemMetaFilter;
+import org.olat.core.util.xml.XStreamHelper;
+import org.olat.fileresource.FileResourceManager;
+import org.olat.modules.webFeed.Enclosure;
+import org.olat.modules.webFeed.Feed;
+import org.olat.modules.webFeed.Item;
+import org.olat.modules.webFeed.model.EnclosureImpl;
+import org.olat.modules.webFeed.model.FeedImpl;
+import org.olat.modules.webFeed.model.ItemImpl;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import com.thoughtworks.xstream.XStream;
+
+/**
+ * This class helps to store data like images and videos in the file systems
+ * and handles the storage of Feeds and Items as XML as well.
+ * 
+ * The structure of the files of a feed is:
+ * resource
+ *   feed
+ *   __feed.xml
+ *   __/items
+ *   ____/item
+ *   ______item.xml
+ *   ______/media
+ *   ________image.jpg
+ *   ____/item
+ *   ______...
+ * 
+ * Initial date: 22.05.2017<br>
+ * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
+ *
+ */
+@Service
+public class FeedFileStorge {
+	
+	private static final OLog log = Tracing.createLoggerFor(FeedFileStorge.class);
+	
+	private static final String MEDIA_DIR = "media";
+	private static final String ITEMS_DIR = "items";
+	public static final String FEED_FILE_NAME = "feed.xml";
+	public static final String ITEM_FILE_NAME = "item.xml";
+	 
+	// same as in repository metadata image upload
+	private static final int PICTUREWIDTH = 570;
+	
+	private FileResourceManager fileResourceManager;
+	private final XStream xstream;
+	
+	@Autowired
+	private ImageService imageHelper;
+	
+	public FeedFileStorge() {
+		fileResourceManager = FileResourceManager.getInstance();
+		xstream = new XStream();
+		xstream.alias("feed", FeedImpl.class);
+		xstream.aliasField("type", FeedImpl.class, "resourceableType");
+		xstream.omitField(FeedImpl.class, "id");
+		xstream.omitField(FeedImpl.class, "itemIds");
+		xstream.omitField(FeedImpl.class, "key");
+		xstream.alias("item", ItemImpl.class);
+		xstream.omitField(ItemImpl.class, "key");
+		xstream.omitField(ItemImpl.class, "feed");
+		xstream.alias("enclosure", Enclosure.class, EnclosureImpl.class);
+	}
+
+	/**
+	 * Get the resource (root) container of the feed.
+	 * 
+	 * @param ores
+	 * @return
+	 */
+	public OlatRootFolderImpl getResourceContainer(OLATResourceable ores) {
+		return fileResourceManager.getFileResourceRootImpl(ores);
+	}
+	
+	/**
+	 * Get the top most folder of a feed.
+	 * The container is created if it does not exist.
+	 * 
+	 * @param ores
+	 * @return the container or null
+	 */
+	public VFSContainer getOrCreateFeedContainer(OLATResourceable ores) {
+		VFSContainer feedContainer = null;
+
+		if (ores != null) {
+			VFSContainer resourceDir = getResourceContainer(ores);
+			String feedContainerName = FeedManager.getInstance().getFeedKind(ores);
+			feedContainer = (VFSContainer) resourceDir.resolve(feedContainerName);
+			if (feedContainer == null) {
+				feedContainer = resourceDir.createChildContainer(feedContainerName);
+			}
+		}
+		
+		return feedContainer;
+	}
+	
+	/**
+	 * Get the media container of a feed.
+	 * The container is created if it does not exist.
+	 * 
+	 * @param ores
+	 * @return the container or null
+	 */
+	public VFSContainer getOrCreateFeedMediaContainer(OLATResourceable ores) {
+		VFSContainer mediaContainer = null;
+		
+		if (ores != null) {
+			VFSContainer feedContainer = getOrCreateFeedContainer(ores);
+			mediaContainer = (VFSContainer) feedContainer.resolve(MEDIA_DIR);
+			if (mediaContainer == null) {
+				mediaContainer = feedContainer.createChildContainer(MEDIA_DIR);
+			}
+		}
+		
+		return mediaContainer;
+	}
+	
+	/**
+	 * Get the items container of a feed.
+	 * The container is created if it does not exist.
+	 * 
+	 * @param ores
+	 * @return the container or null
+	 */
+	public VFSContainer getOrCreateFeedItemsContainer(OLATResourceable ores) {
+		VFSContainer itemsContainer = null;
+		
+		if (ores != null) {
+			VFSContainer feedContainer = getOrCreateFeedContainer(ores);
+			itemsContainer = (VFSContainer) feedContainer.resolve(ITEMS_DIR);
+			if (itemsContainer == null) {
+				itemsContainer = feedContainer.createChildContainer(ITEMS_DIR);
+			}
+		}
+		
+		return itemsContainer;
+	}
+	
+	/**
+	 * Get the container of an item.
+	 * The container is created if it does not exist.
+	 * 
+	 * @param ores
+	 * @return the container or null
+	 */
+	public VFSContainer getOrCreateItemContainer(Item item) {
+		VFSContainer itemContainer = null;
+		
+		if (item != null) {
+			Feed feed = item.getFeed();
+			String guid = item.getGuid();
+			itemContainer = getOrCreateItemContainer(feed, guid);
+		}
+		
+		return itemContainer;
+	}
+	
+	/**
+	 * Delete the container of the item.
+	 * 
+	 * @param item
+	 */
+	public void deleteItemContainer(Item item) {
+		VFSContainer itemContainer = getOrCreateItemContainer(item);
+		if (itemContainer != null) {
+			itemContainer.delete();
+		}
+	}
+	
+	
+	/**
+	 * Get the container for the guid of an item.
+	 * The container is created if it does not exist.
+
+	 * @param feed
+	 * @param guid
+	 * @return
+	 */
+	public VFSContainer getOrCreateItemContainer(Feed feed, String guid) {
+		VFSContainer itemContainer = null;
+		
+		if (feed != null && StringHelper.containsNonWhitespace(guid)) {
+			VFSContainer feedContainer = getOrCreateFeedItemsContainer(feed);
+			itemContainer = (VFSContainer) feedContainer.resolve(guid);
+			if (itemContainer == null) {
+				itemContainer = feedContainer.createChildContainer(guid);
+			}
+		}
+		
+		return itemContainer;
+	}
+	
+	/**
+	 * Get the media container of an item.
+	 * The container is created if it does not exist.
+	 * 
+	 * @param ores
+	 * @return the container or null
+	 */
+	public VFSContainer getOrCreateItemMediaContainer(Item item) {
+		VFSContainer mediaContainer = null;
+		
+		if (item != null) {
+			VFSContainer itemContainer = getOrCreateItemContainer(item);
+			if (itemContainer != null) {
+				mediaContainer = (VFSContainer) itemContainer.resolve(MEDIA_DIR);
+				if (mediaContainer == null) {
+					mediaContainer = itemContainer.createChildContainer(MEDIA_DIR);
+				}
+			}
+		}
+		
+		return mediaContainer;
+	}
+	
+	/**
+	 * Save the feed as XML into the feed container.
+	 * 
+	 * @param feed
+	 */
+	public void saveFeedAsXML(Feed feed) {
+		VFSContainer feedContainer = getOrCreateFeedContainer(feed);
+		if (feedContainer != null) {
+			VFSLeaf leaf = (VFSLeaf) feedContainer.resolve(FEED_FILE_NAME);
+			if (leaf == null) {
+				leaf = feedContainer.createChildLeaf(FEED_FILE_NAME);
+			}
+			XStreamHelper.writeObject(xstream, leaf, feed);
+		}
+	}
+	
+	/**
+	 * Load the XML file of the feed from the feed container and convert it to
+	 * a feed.
+	 * 
+	 * @param ores
+	 * @return the feed or null
+	 */
+	public Feed loadFeedFromXML(OLATResourceable ores) {
+		Feed feed = null;
+		
+		VFSContainer feedContainer = getOrCreateFeedContainer(ores);
+		if (feedContainer != null) {
+			VFSLeaf leaf = (VFSLeaf) feedContainer.resolve(FEED_FILE_NAME);
+			if (leaf != null) {
+				feed = (FeedImpl) XStreamHelper.readObject(xstream, leaf.getInputStream());
+			}
+		} else {
+			log.error("Feed XML-File could not be found on file system. Feed container: " + feedContainer);
+		}
+		
+		return feed;
+	}
+	
+	/**
+	 * Load the XML file of the feed from a Path and convert it to
+	 * a feed.
+	 * 
+	 * @param feedDir the directory which contains the feed file
+	 * @return the feed or null
+	 */
+	public Feed loadFeedFromXML(Path feedDir) {
+		Feed feed = null;
+		
+		if (feedDir != null) {
+			Path feedPath = feedDir.resolve(FeedFileStorge.FEED_FILE_NAME);
+			try (InputStream in = Files.newInputStream(feedPath)) {
+				feed = (FeedImpl) XStreamHelper.readObject(xstream, in);
+			} catch (IOException e) {
+				log.error("Feed XML-File could not be found on file system. Feed path: " + feedPath, e);
+			}
+		}
+		
+		return feed;
+	}
+	
+	/**
+	 * Delete the XML file of the feed from the feed container
+	 * 
+	 * @param feed
+	 */
+	public void deleteFeedXML(Feed feed) {
+		VFSContainer feedContainer = getOrCreateFeedContainer(feed);
+		if (feedContainer != null) {
+			VFSLeaf leaf = (VFSLeaf) feedContainer.resolve(FEED_FILE_NAME);
+			if (leaf != null) {
+				leaf.delete();
+			}
+		}
+	}
+	
+	/**
+	 * Save the item as XML into the item container.
+	 * 
+	 * @param item
+	 */
+	public void saveItemAsXML(Item item) {
+		VFSContainer itemContainer = getOrCreateItemContainer(item);
+		if (itemContainer != null) {
+			VFSLeaf leaf = (VFSLeaf) itemContainer.resolve(ITEM_FILE_NAME);
+			if (leaf == null) {
+				leaf = itemContainer.createChildLeaf(ITEM_FILE_NAME);
+			}
+			XStreamHelper.writeObject(xstream, leaf, item);
+		}
+	}
+	
+	/**
+	 * Load the XML file of the item from the item container and convert it to
+	 * an item.
+	 * 
+	 * @param feed
+	 * @param guid
+	 * @return
+	 */
+	Item loadItemFromXML(VFSContainer itemContainer) {
+		Item item = null;
+		
+		if (itemContainer != null) {
+			VFSLeaf leaf = (VFSLeaf) itemContainer.resolve(ITEM_FILE_NAME);
+			if (leaf != null) {
+				item = (ItemImpl) XStreamHelper.readObject(xstream, leaf.getInputStream());
+			} else {
+				log.error("Item XML-File could not be found on file system."
+						+ " Item container: " + itemContainer.getName());
+			}
+		}
+		
+		return item;
+	}
+	
+	/**
+	 * Load the XML file of all items of a feed and convert them to items.
+	 * 
+	 * @param ores
+	 * @return
+	 */
+	public List<Item> loadItemsFromXML(OLATResourceable ores) {
+		List<Item> items = new ArrayList<>();
+		
+		VFSContainer itemsContainer = getOrCreateFeedItemsContainer(ores);
+		if (itemsContainer != null) {
+			List<VFSItem>  itemContainers = itemsContainer.getItems(new VFSItemMetaFilter());
+			if (itemContainers != null && !itemContainers.isEmpty()) {
+				for (VFSItem itemContainer : itemContainers) {
+					Item item = loadItemFromXML((VFSContainer) itemContainer);
+					items.add(item);
+				}
+			}
+		}
+		
+		return items;
+	}
+	
+	/**
+	 * Delete the XML file of the item from the item container
+	 * 
+	 * @param item
+	 */
+	public void deleteItemXML(Item item) {
+		VFSContainer itemContainer = getOrCreateItemContainer(item);
+		if (itemContainer != null) {
+			VFSLeaf leaf = (VFSLeaf) itemContainer.resolve(ITEM_FILE_NAME);
+			if (leaf != null) {
+				leaf.delete();
+			}
+		}
+	}
+	
+	/**
+	 * Save the the media element of the feed. If allready a file is in
+	 * the media container, that file is previously deleted.
+	 * If the media is null, this method will do nothing. It does not delete
+	 * the existing media.
+	 * 
+	 * @param feed
+	 * @param media
+	 * @return the file name which is save for the file system
+	 */
+	public String saveFeedMedia(Feed feed, FileElement media) {
+		String saveFileName = null;
+		
+		if (media != null) {
+			VFSContainer feedMediaContainer = getOrCreateFeedMediaContainer(feed);
+			if (feedMediaContainer != null) {
+				deleteFeedMedia(feed);
+				VFSLeaf imageLeaf = media.moveUploadFileTo(feedMediaContainer);
+				// Resize to same dimension box as with repo meta image
+				VFSLeaf tmpImage = feedMediaContainer.createChildLeaf(Long.toString(CodeHelper.getRAMUniqueID()));
+				imageHelper.scaleImage(imageLeaf, tmpImage, PICTUREWIDTH, PICTUREWIDTH, false);
+				imageLeaf.delete();
+				imageLeaf = tmpImage;
+				// Make file system save
+				saveFileName = Formatter.makeStringFilesystemSave(media.getUploadFileName());
+				imageLeaf.rename(saveFileName);
+			}
+		}
+		
+		return saveFileName;
+	}
+	
+	/**
+	 * Load the the media element of the feed.
+	 * 
+	 * @param feed
+	 * @return the media alement or null
+	 */
+	public VFSLeaf loadFeedMedia(Feed feed) {
+		VFSLeaf mediaFile = null;
+		
+		if (feed != null) {
+			String feedImage = feed.getImageName();
+			if (feedImage != null) {
+				mediaFile = (VFSLeaf) getOrCreateFeedMediaContainer(feed).resolve(feedImage);
+			}
+		}
+		
+		return mediaFile;
+	}
+	
+	/**
+	 * Delete the the media of the feed.
+	 * 
+	 * @param feed
+	 */
+	public void deleteFeedMedia(Feed feed) {
+		VFSContainer feedMediaContainer = getOrCreateFeedMediaContainer(feed);
+		if (feedMediaContainer != null) {
+			for (VFSItem fileItem : feedMediaContainer.getItems()) {
+				if (!fileItem.getName().startsWith(".")) {
+					fileItem.delete();
+				}
+			}
+		}
+	}
+	
+	/**
+	 * Save the the media (video/audio) of the item. If allready a file is in
+	 * the media container, that file is previously deleted.
+	 * If the media is null, this method will do nothing. It does not delete
+	 * the existing media.
+	 * 
+	 * @param item
+	 * @param media
+	 * @return the file name which is save for the file system
+	 */
+	public String saveItemMedia(Item item, FileElement media) {
+		String saveFileName = null;
+		
+		if (media != null) {
+			VFSContainer itemMediaContainer = getOrCreateItemMediaContainer(item);
+			if (itemMediaContainer != null) {
+				deleteItemMedia(item);
+				VFSItem movedItem = media.moveUploadFileTo(itemMediaContainer);
+				saveFileName = Formatter.makeStringFilesystemSave(media.getUploadFileName());
+				movedItem.rename(saveFileName);
+			}
+		}
+		
+		return saveFileName;
+	}
+	
+	/**
+	 * Load the media file of the item.
+	 * 
+	 * @param item
+	 * @return
+	 */
+	public File loadItemMedia(Item item) {
+		File file = null;
+		
+		Enclosure enclosure = item.getEnclosure();
+		VFSContainer mediaDir = getOrCreateItemMediaContainer(item);
+		if (mediaDir != null && enclosure != null) {
+			VFSLeaf mediaFile = (VFSLeaf) mediaDir.resolve(enclosure.getFileName());
+			if (mediaFile != null && mediaFile instanceof LocalFileImpl) {
+				file = ((LocalFileImpl) mediaFile).getBasefile();
+			}
+		}
+		
+		return file;
+	}
+	
+	/**
+	 * Delete the the media (video/audio) of the item.
+	 * 
+	 * @param item
+	 */
+	public void deleteItemMedia(Item item) {
+		VFSContainer itemMediaContainer = getOrCreateItemMediaContainer(item);
+		if (itemMediaContainer != null) {
+			for (VFSItem fileItem : itemMediaContainer.getItems()) {
+				if (!fileItem.getName().startsWith(".")) {
+					fileItem.delete();
+				}
+			}
+		}
+	}
+
+}
diff --git a/src/main/java/org/olat/modules/webFeed/managers/FeedManager.java b/src/main/java/org/olat/modules/webFeed/manager/FeedManager.java
similarity index 63%
rename from src/main/java/org/olat/modules/webFeed/managers/FeedManager.java
rename to src/main/java/org/olat/modules/webFeed/manager/FeedManager.java
index 1d359ecf5bd..bf139bc924c 100644
--- a/src/main/java/org/olat/modules/webFeed/managers/FeedManager.java
+++ b/src/main/java/org/olat/modules/webFeed/manager/FeedManager.java
@@ -9,7 +9,7 @@
  * <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>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or ied. <br>
  * See the License for the specific language governing permissions and <br>
  * limitations under the License.
  * <p>
@@ -17,29 +17,25 @@
  * frentix GmbH, http://www.frentix.com
  * <p>
  */
-package org.olat.modules.webFeed.managers;
+package org.olat.modules.webFeed.manager;
 
 import java.io.File;
 import java.nio.file.Path;
 import java.util.List;
 
-import org.olat.core.commons.modules.bc.vfs.OlatRootFolderImpl;
 import org.olat.core.commons.services.image.Size;
 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.manager.BasicManager;
 import org.olat.core.util.coordinate.CoordinatorManager;
 import org.olat.core.util.coordinate.LockResult;
 import org.olat.core.util.vfs.Quota;
 import org.olat.core.util.vfs.VFSContainer;
-import org.olat.core.util.vfs.VFSItem;
 import org.olat.core.util.vfs.VFSLeaf;
-import org.olat.fileresource.types.BlogFileResource;
-import org.olat.fileresource.types.PodcastFileResource;
-import org.olat.modules.webFeed.models.Feed;
-import org.olat.modules.webFeed.models.Item;
+import org.olat.modules.webFeed.Feed;
+import org.olat.modules.webFeed.FeedSecurityCallback;
+import org.olat.modules.webFeed.Item;
 import org.olat.resource.OLATResource;
 
 /**
@@ -51,14 +47,16 @@ import org.olat.resource.OLATResource;
  * 
  * @author gwassmann
  */
-public abstract class FeedManager extends BasicManager {
+public abstract class FeedManager {
 
 	protected static FeedManager INSTANCE;
 
+	//TODO delete moved to FeedFileStorage
 	public static final String ITEMS_DIR = "items";
 	public static final String FEED_FILE_NAME = "feed.xml";
 	public static final String ITEM_FILE_NAME = "item.xml";
 	public static final String MEDIA_DIR = "media";
+	
 	public static final String RSS_FEED_NAME = "feed.rss";
 	public static final String RESOURCE_NAME = "feed";
 
@@ -80,25 +78,33 @@ public abstract class FeedManager extends BasicManager {
 	}
 
 	/**
-	 * Creates an OLAT podcast resource
+	 * Creates a blank OLAT podcast resource 
 	 * 
 	 * @return The resource
 	 */
 	public abstract OLATResourceable createPodcastResource();
+	
+	/**
+	 * Check if a feed has items
+	 * 
+	 * @param feed
+	 * @return
+	 */
+	public abstract boolean hasItems(Feed feed);
 
 	/**
-	 * Creates an OLAT blog resource
+	 * Creates a blank OLAT blog resource
 	 * 
 	 * @return The resource
 	 */
 	public abstract OLATResourceable createBlogResource();
 
 	/**
-	 * Deletes a given feed.
+	 * Deletes a feed.
 	 * 
 	 * @param feed
 	 */
-	public abstract void delete(OLATResourceable feed);
+	public abstract void deleteFeed(OLATResourceable feed);
 
 	/**
 	 * Copies a given feed resourceable
@@ -108,72 +114,132 @@ public abstract class FeedManager extends BasicManager {
 	public abstract boolean copy(OLATResource source, OLATResource target);
 
 	/**
-	 * Adds the given <code>Item</code> to the <code>Feed</code>.
+	 * Create the given Item and saves the appropriate file (podcast, video etc.)
+	 * on the file system.
 	 * 
-	 * @param item
-	 * @param feed
+	 * @param feed the item will be added to this feed
+	 * @param item the item to add
+	 * @param file the file of the item
+	 * @return
 	 */
-	public abstract Feed addItem(Item item, FileElement file, Feed feed);
+	public abstract Feed createItem(Feed feed, Item item, FileElement file);
 
+	
 	/**
-	 * Removes the given <code>Item</code> from the <code>Feed</code>. Its content
-	 * will be deleted.
+	 * Removes the given Item from the feed and delete the item from the
+	 * database. Additionally the content on the file system (podcast etc.)
+	 * and the comments and the ratings of the item are deleted.
 	 * 
-	 * @param item
-	 * @param feed
+	 * @param item the item to remove
+	 * @return the feed without the removed item
 	 */
-	public abstract Feed remove(Item item, Feed feed);
-
+	public abstract Feed deleteItem(Item item);
+	
 	/**
+	 * Update the Item in the database and save the file element in the file
+	 * system.
+	 * 
 	 * @param modifiedItem
-	 * @param feed
+	 * @param file
+	 * @return the updated feed
 	 */
-	public abstract Feed updateItem(Item modifiedItem, FileElement file, Feed feed);
+	public abstract Item updateItem(Item modifiedItem, FileElement file);
 
 	/**
-	 * Update the feed source mode
+	 * Update the feed source mode. Additionally it deleted all Items of the Feed
+	 * if the mode of the Feed changes.
 	 * 
-	 * @param external True: set to be an external feed; false: this is an
-	 *          internal feed; null=undefined
+	 * @param external true: set to be an external feed; false: this is an
+	 *          internal feed; null: undefined
 	 * @param feed
 	 * @return Feed the updated feed object
 	 */
 	public abstract Feed updateFeedMode(Boolean external, Feed feed);
 
 	/**
-	 * Update the feed metadata from the given feed object
+	 * Update the feed from the given feed object
 	 * 
 	 * @param feed
 	 * @return
 	 */
-	public abstract Feed updateFeedMetadata(Feed feed);
-
+	public abstract Feed updateFeed(Feed feed);
+	
+	/**
+	 * Load the Item with the given key from the database or NULL if no such
+	 * item exists.
+	 * 
+	 * @param key the key of the Item
+	 * @return the loaded Item or NULL
+	 */
+	public abstract Item loadItem(Long key);
+	
+	/**
+	 * Load the Item with the given guid from the database or NULL if no such
+	 * item exists.
+	 * @param feedKey the key of the feed
+	 * @param guid the guid of the Item
+	 * 
+	 * @return the loaded Item or NULL
+	 */
+	public abstract Item loadItemByGuid(Long feedKey, String guid);
+	
 	/**
 	 * Load all items of the feed (from file system or the external feed)
 	 * 
 	 * @param feed
 	 */
-	public abstract List<Item> loadItems(final Feed feed);
-
+	public abstract List<Item> loadItems(Feed feed);
+	
 	/**
-	 * Get the item from the feed with the given GUID or NULL if no such item
-	 * exists. Make sure you did load the feed before executing this!
+	 * Load the guid of all Items of the feed
 	 * 
 	 * @param feed
-	 * @param GUID
-	 * @return the Item or NULL
 	 */
-	public abstract Item getItem(Feed feed, String GUID);
+	public abstract List<String> loadItemsGuid(Feed feed);
+	
+	/**
+	 * Load all published Items
+	 * 
+	 * @param feed
+	 */
+	public abstract List<Item> loadPublishedItems(Feed feed);
+	
+	/**
+	 * Load all Items of a feed and filter them in relation to the identity rights.
+	 * 
+	 * @param feed
+	 * @param callback
+	 * @param identity
+	 * @return
+	 */
+	public abstract List<Item> loadFilteredAndSortedItems(Feed feed, FeedSecurityCallback callback, Identity identity);
 	
 	/**
 	 * Returns the feed with the provided id or null if not found.
 	 * 
 	 * @param feed The feed to be re-read
-	 * @return The newly read feed (without items)
+	 * @return The newly read feed
+	 */
+	public abstract Feed loadFeed(OLATResourceable feed);
+	
+	/**
+	 * Returns the feed from the XML file inside the directory or null if not
+	 * found.
+	 * 
+	 * @param feedDir the directory which contains the feed file
+	 * @return the feed or null
 	 */
-	public abstract Feed getFeed(OLATResourceable feed);
+	public abstract Feed loadFeedFromXML(Path feedDir);
 	
-	public abstract OlatRootFolderImpl getResourceContainer(OLATResourceable ores);
+	/**
+	 * Import the feed and all items from an feed XML file.<br>
+	 * The XML File is read and the feed is stored in the database. All XML
+	 * files of the items are read and the items stored in the database.<br>
+	 * At the end the XML Files are deleted.
+	 * 
+	 * @param ores
+	 */
+	public abstract void importFeedFromXML(OLATResource ores);
 
 	/**
 	 * Returns the media file of the item
@@ -236,36 +302,32 @@ public abstract class FeedManager extends BasicManager {
 	 * Returns the container of the item which belongs to the feed
 	 * 
 	 * @param item
-	 * @param feed
 	 * @return The container of the item
 	 */
-	public abstract VFSContainer getItemContainer(Item item, Feed feed);
-
+	public abstract VFSContainer getItemContainer(Item item);
+	
 	/**
-	 * Returns the media container of the item of feed
+	 * Save the item in an XML file in the item container.
 	 * 
 	 * @param item
-	 * @param feed
-	 * @return The media container of the item
 	 */
-	public abstract VFSContainer getItemMediaContainer(Item item, Feed feed);
-
+	public abstract void saveItemAsXML(Item item);
+	
 	/**
-	 * Returns the File of the item's enclosure if it exists or null
+	 * Delete the item XML file from the item container.
 	 * 
 	 * @param item
-	 * @param feed
-	 * @return The enclosure media file
 	 */
-	public abstract File getItemEnclosureFile(Item item, Feed feed);
+	public abstract void deleteItemXML(Item item);
 
 	/**
-	 * Returns the container of the feed
+	 * Returns the File of the item's enclosure if it exists or null
 	 * 
-	 * @param feed
-	 * @return The feed container
+	 * @param item
+	 * @return The enclosure media file
 	 */
-	public abstract VFSContainer getFeedContainer(OLATResourceable feed);
+	public abstract File loadItemEnclosureFile(Item item);
+
 	
 	public abstract Quota getQuota(OLATResourceable feed);
 
@@ -320,48 +382,24 @@ public abstract class FeedManager extends BasicManager {
 	 * @param ores
 	 * @return The kind of the resource type
 	 */
-	public String getFeedKind(OLATResourceable ores) {
-		String feedKind = null;
-		String typeName = ores.getResourceableTypeName();
-		if (PodcastFileResource.TYPE_NAME.equals(typeName)) {
-			feedKind = KIND_PODCAST;
-		} else if (BlogFileResource.TYPE_NAME.equals(typeName)) {
-			feedKind = KIND_BLOG;
-		} else if ("LiveBlog".equals(typeName)) {
-			feedKind = KIND_BLOG;
-		}
-		return feedKind;
-	}
+	public abstract String getFeedKind(OLATResourceable ores);
 
 	/**
-	 * Set the image of the feed (update handled separately)
-	 * 
-	 * @param image
-	 * @param feed
-	 */
-	public abstract void setImage(FileElement image, Feed feed);
-
-	/**
-	 * Delete the image of the feed
+	 * Replace the image of the feed.
+	 * If the image is null, the existing image is kept.
 	 * 
 	 * @param feed
+	 * @param image
+	 * @return
 	 */
-	public abstract void deleteImage(Feed feed);
-
+	public abstract Feed replaceFeedImage(Feed feed, FileElement image);
+	
 	/**
-	 * Prepare the filesystem for a new item, create the item container and all
-	 * necessary sub container, e.g. the media container
+	 * Delete the feed image.
 	 * 
 	 * @param feed
-	 * @param currentItem
-	 * @return the container for the item
+	 * @return
 	 */
-	public abstract VFSContainer createItemContainer(Feed feed, Item currentItem);
-
-	public abstract Feed readFeedFile(VFSContainer root);
-	
-	public abstract Feed readFeedFile(Path feedPath);
-
-	public abstract Item loadItem(VFSItem itemContainer);
+	public abstract Feed deleteFeedImage(Feed feed);
 
 }
diff --git a/src/main/java/org/olat/modules/webFeed/manager/FeedManagerImpl.java b/src/main/java/org/olat/modules/webFeed/manager/FeedManagerImpl.java
new file mode 100644
index 00000000000..e4dea97f92d
--- /dev/null
+++ b/src/main/java/org/olat/modules/webFeed/manager/FeedManagerImpl.java
@@ -0,0 +1,955 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <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 the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <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>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.webFeed.manager;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+
+import org.olat.admin.quota.QuotaConstants;
+import org.olat.basesecurity.BaseSecurityManager;
+import org.olat.core.CoreSpringFactory;
+import org.olat.core.commons.modules.bc.meta.tagged.MetaTagged;
+import org.olat.core.commons.modules.bc.vfs.OlatRootFolderImpl;
+import org.olat.core.commons.persistence.PersistenceHelper;
+import org.olat.core.commons.services.commentAndRating.CommentAndRatingService;
+import org.olat.core.commons.services.image.Size;
+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.id.Roles;
+import org.olat.core.logging.OLog;
+import org.olat.core.logging.Tracing;
+import org.olat.core.util.Encoder;
+import org.olat.core.util.FileUtils;
+import org.olat.core.util.StringHelper;
+import org.olat.core.util.ZipUtil;
+import org.olat.core.util.coordinate.Coordinator;
+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.resource.OresHelper;
+import org.olat.core.util.vfs.Quota;
+import org.olat.core.util.vfs.QuotaManager;
+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.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.Enclosure;
+import org.olat.modules.webFeed.ExternalFeedFetcher;
+import org.olat.modules.webFeed.Feed;
+import org.olat.modules.webFeed.FeedSecurityCallback;
+import org.olat.modules.webFeed.Item;
+import org.olat.modules.webFeed.RSSFeed;
+import org.olat.modules.webFeed.SyndFeedMediaResource;
+import org.olat.modules.webFeed.dispatching.FeedMediaDispatcher;
+import org.olat.modules.webFeed.model.EnclosureImpl;
+import org.olat.modules.webFeed.model.FeedImpl;
+import org.olat.modules.webFeed.model.ItemPublishDateComparator;
+import org.olat.repository.RepositoryEntry;
+import org.olat.repository.RepositoryManager;
+import org.olat.resource.OLATResource;
+import org.olat.resource.OLATResourceManager;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import com.rometools.rome.feed.synd.SyndEntry;
+import com.rometools.rome.feed.synd.SyndFeed;
+import com.rometools.rome.io.FeedException;
+import com.rometools.rome.io.ParsingFeedException;
+import com.rometools.rome.io.SyndFeedInput;
+import com.rometools.rome.io.XmlReader;
+
+/**
+ * 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 {
+	
+	private static final OLog log = Tracing.createLoggerFor(FeedManagerImpl.class);
+	
+	// 10 minutes
+	private static final int EXTERNAL_FEED_ACTUALIZATION_MILLIS = 10*60*1000;
+	
+	public static final String KIND_PODCAST = "podcast";
+	public static final String KIND_BLOG = "blog";
+
+	private RepositoryManager repositoryManager;
+	private Coordinator coordinator;
+	private OLATResourceManager resourceManager;
+	private FileResourceManager fileResourceManager;
+	
+	@Autowired
+	private FeedDAO feedDAO;
+	@Autowired
+	private ItemDAO itemDAO;
+	@Autowired
+	private FeedFileStorge feedFileStorage;
+	@Autowired
+	private ExternalFeedFetcher externalFeedFetcher;
+
+	/**
+	 * spring only
+	 */
+	protected FeedManagerImpl(OLATResourceManager resourceManager, FileResourceManager fileResourceManager,
+			CoordinatorManager coordinatorManager) {
+
+		this.resourceManager = resourceManager;
+		this.fileResourceManager = fileResourceManager;
+		INSTANCE = this;
+		this.coordinator = coordinatorManager.getCoordinator();
+	}
+	
+	FeedManagerImpl() {
+		
+	}
+
+	/**
+	 * 
+	 * @param repositoryManager
+	 */
+	public void setRepositoryManager(RepositoryManager repositoryManager) {
+		this.repositoryManager = repositoryManager;
+	}
+
+	/**
+	 * 
+	 * @see org.olat.modules.webFeed.manager.FeedManager#createPodcastResource()
+	 */
+	@Override
+	public OLATResourceable createPodcastResource() {
+		FeedFileResource podcastResource = new PodcastFileResource();
+		return createFeedResource(podcastResource);
+	}
+
+	/**
+	 * 
+	 * @see org.olat.modules.webFeed.manager.FeedManager#createPodcastResource()
+	 */
+	@Override
+	public OLATResourceable createBlogResource() {
+		FeedFileResource blogResource = new BlogFileResource();
+		return createFeedResource(blogResource);
+	}
+
+	/**
+	 * This method creates an OLATResource in the database and
+	 * initializes the container on the file system.
+	 * 
+	 * @param feedResource
+	 * @return The feed resourcable
+	 */
+	private OLATResourceable createFeedResource(FeedFileResource feedResource) {
+		// save the resource in the database
+		OLATResource ores = resourceManager.createOLATResourceInstance(feedResource);
+		resourceManager.saveOLATResource(ores);
+		
+		// create a feed and save it in the database
+		feedDAO.createFeedForResourcable(feedResource);
+		
+		// Create a resource folder for storing the images
+		feedFileStorage.getOrCreateFeedContainer(feedResource);
+		
+		return feedResource;
+	}
+	
+	/**
+	 * Load the Feed from the database.
+	 * 
+	 * Additionally this method triggers the actualization of the external feed
+	 * and his items. The download starts is the last modified time is 
+	 */
+	@Override
+	public Feed loadFeed(OLATResourceable ores) {
+		migrateFeedFromXmlToDb(ores);
+
+		Feed feed = feedDAO.loadFeed(ores);
+		
+		feed = enrichFeedByRepositoryEntryInfromation(feed);
+		
+		// Update the external feed and the items
+		if (feed != null && feed.isExternal() && StringHelper.containsNonWhitespace(feed.getExternalFeedUrl()) ) {
+			Calendar cal = Calendar.getInstance();
+			cal.setTime(feed.getLastModified());
+			long lastModifiedMillis = cal.getTimeInMillis();
+			Date nextUpdateDate = new Date(lastModifiedMillis + EXTERNAL_FEED_ACTUALIZATION_MILLIS);
+			Date now = new Date();
+			if (now.after(nextUpdateDate) || loadItems(feed).isEmpty()) {
+				// time to update or first load after creation of the feed
+				saveExternalItems(feed);
+				saveExternalFeed(feed);
+			}
+			
+			feed.setLastModified(new Date());
+			feedDAO.updateFeed(feed);
+		}
+		
+		return feed;
+	}
+
+	/**
+	 * In the early days all information about a feed where stored in XML files.
+	 * This method migrates that old feeds from the XML files to the database.
+	 * It first checks it the feed has to be migrated. If it has to the XML
+	 * files are read, the feed and the items are saved to the database and at
+	 * the end the XML files are deleted. 
+	 * 
+	 * @param ores
+	 */
+	private void migrateFeedFromXmlToDb(OLATResourceable ores) {
+		OLATResource resource = resourceManager.findResourceable(ores);
+		if (resource != null) {
+			importFeedFromXML(resource);
+		}
+	}
+
+	@Override
+	public boolean hasItems(Feed feed) {
+		return itemDAO.hasItems(feed);
+	}
+
+	@Override
+	public Feed updateFeed(Feed feed) {
+		enrichRepositoryEntryByFeedInformation(feed);
+		return feedDAO.updateFeed(feed);
+	}
+
+	@Override
+	public Feed updateFeedMode(Boolean external, Feed feed) {
+		if (feed == null) return null;
+		
+		// first, reload actual version of the feed
+		Feed reloaded = feedDAO.loadFeed(feed);
+		if (reloaded == null) return null;
+		
+		// delete all items if the mode changes
+		if (external == null
+				|| feed.isUndefined() 
+				|| external.booleanValue() != feed.getExternal().booleanValue()) {
+			itemDAO.removeItems(feed);
+		}
+		
+		reloaded.setExternal(external);
+		return updateFeed(reloaded);
+	}
+
+	@Override
+	public Feed replaceFeedImage(Feed feed, FileElement image) {
+		String saveFileName = null;
+		
+		if (image != null) {
+			saveFileName = feedFileStorage.saveFeedMedia(feed, image);
+			feed = feedDAO.loadFeed(feed.getKey());
+			if (feed != null) {
+				feed.setImageName(saveFileName);
+				feed = feedDAO.updateFeed(feed);
+			}
+		}
+		
+		return feed;
+	}
+	
+	@Override
+	public Feed deleteFeedImage(Feed feed) {
+		String saveFileName = null;
+		
+		feedFileStorage.deleteFeedMedia(feed);
+		Feed reloadedFeed = feedDAO.loadFeed(feed.getKey());
+		if (reloadedFeed != null) {
+			reloadedFeed.setImageName(saveFileName);
+			reloadedFeed = feedDAO.updateFeed(reloadedFeed);
+		}
+		
+		return reloadedFeed;
+	}
+
+	@Override
+	public void deleteFeed(OLATResourceable ores) {
+		// delete the container on the file system
+		fileResourceManager.deleteFileResource(ores);
+		
+		// delete comments and ratings
+		CommentAndRatingService commentAndRatingService = CoreSpringFactory.getImpl(CommentAndRatingService.class);
+		commentAndRatingService.deleteAllIgnoringSubPath(ores);
+		
+		// delete the feed and all items from the database
+		Feed feed = feedDAO.loadFeed(ores);
+		itemDAO.removeItems(feed);
+		feedDAO.removeFeedForResourceable(ores);
+		resourceManager.deleteOLATResourceable(ores);
+		feed = null;
+	}
+		
+	@Override
+	public Feed createItem(Feed feed, Item item, FileElement file) {
+		Feed reloadedFeed = null;
+		
+		if (feed != null && feed.isInternal()) {
+	
+			// Set the current date as published date.
+			if (item.getPublishDate() == null) {
+				item.setPublishDate(new Date());
+			}
+			
+			// Save the Enclosure
+			Enclosure enclosure = replaceEnclosure(item, file);
+			item.setEnclosure(enclosure);
+			
+			// Save the Item
+			itemDAO.createItem(feed, item);
+			
+			// Set the modification date of the feed
+			reloadedFeed = feedDAO.loadFeed(feed.getKey());
+			reloadedFeed.setLastModified(new Date());
+			reloadedFeed = feedDAO.updateFeed(reloadedFeed);
+		}
+		return reloadedFeed;
+	}
+
+
+	@Override
+	public Item loadItem(Long key) {
+		return itemDAO.loadItem(key);
+	}
+	
+	@Override
+	public Item loadItemByGuid(Long feedKey, String guid) {
+		return itemDAO.loadItemByGuid(feedKey, guid);
+	}
+
+	@Override
+	public List<Item> loadItems(Feed feed) {
+		return itemDAO.loadItems(feed);
+	}
+	
+	@Override
+	public List<String> loadItemsGuid(Feed feed) {
+		return itemDAO.loadItemsGuid(feed);
+	}
+	
+	@Override
+	public List<Item> loadPublishedItems(Feed feed) {
+		return itemDAO.loadPublishedItems(feed);
+	}
+
+	@Override
+	public List<Item> loadFilteredAndSortedItems(Feed feed, FeedSecurityCallback callback, Identity identity) {
+		List<Item> items = loadItems(feed);
+		List<Item> filteredItems = new ArrayList<>();
+		final Roles roles = BaseSecurityManager.getInstance().getRoles(identity);
+		if (roles != null && (roles.isOLATAdmin() || feed.isExternal())) {
+			// show all items
+			filteredItems = items;
+		} else {
+			for (Item item : items) {
+				if (item.isPublished()) {
+					// everybody can see published items
+					filteredItems.add(item);
+				} else if (item.isScheduled() && callback.mayEditItems()) {
+					// scheduled items can be seen by everybody who can edit items
+					// (moderators)
+					filteredItems.add(item);
+				} else if (identity.getKey() == item.getAuthorKey()) {
+					// scheduled items and drafts of oneself are shown
+					filteredItems.add(item);
+				} else if (item.isDraft()) {
+					if(callback.mayViewAllDrafts()) {
+						filteredItems.add(item);
+					} else if (identity.getKey() == item.getModifierKey()) {
+						filteredItems.add(item);
+					}
+				}
+			}
+		}
+		Collections.sort(filteredItems, new ItemPublishDateComparator());
+		return filteredItems;
+	}
+
+	@Override
+	public Item updateItem(Item item, FileElement file) {
+		Item updatedItem = itemDAO.loadItem(item.getKey());
+		
+		if (updatedItem != null) {
+			Enclosure enclosure = replaceEnclosure(item, file);
+			item.setEnclosure(enclosure);
+			updatedItem = itemDAO.updateItem(item);
+		}
+		
+		return updatedItem;
+	}
+
+	/**
+	 * Save the media file to the file system and get the appropriate Enclosure.
+	 * 
+	 * @param item
+	 * @param file
+	 * @return
+	 */
+	private Enclosure replaceEnclosure(Item item, FileElement file) {
+		Enclosure enclosure = item.getEnclosure();
+		
+		if (file != null) {
+			String saveFileName = feedFileStorage.saveItemMedia(item, file);
+			enclosure = new EnclosureImpl();
+			enclosure.setFileName(saveFileName);
+			enclosure.setLength(file.getUploadSize());
+			enclosure.setType(file.getUploadMimeType());
+		}
+		
+		return enclosure;
+	}
+
+
+	@Override
+	public Feed deleteItem(Item item) {
+		Feed feed = item.getFeed();
+		
+		// delete the item from the database
+		itemDAO.removeItem(item);
+		
+		// delete the item container from the file system
+		feedFileStorage.deleteItemContainer(item);
+		
+		// delete comments and ratings
+		CommentAndRatingService commentAndRatingService = CoreSpringFactory
+				.getImpl(CommentAndRatingService.class);
+		commentAndRatingService.deleteAll(feed, item.getGuid());
+		
+		// reload the Feed
+		Feed reloadedFeed = feedDAO.loadFeed(feed.getKey());
+		
+		// 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 (!hasItems(reloadedFeed)) {
+			// set undefined
+			reloadedFeed.setExternal(null);
+		}
+		reloadedFeed.setLastModified(new Date());
+		reloadedFeed = feedDAO.updateFeed(reloadedFeed);
+		
+		return reloadedFeed;
+	}
+
+	/**
+	 * Fetch the external feed and store it in the database.
+	 * 
+	 * @param feed
+	 */
+	private void saveExternalFeed(Feed feed) {
+		feed = externalFeedFetcher.fetchFeed(feed);
+		
+		if (feed != null) {
+			feed.setLastModified(new Date());
+			feedDAO.updateFeed(feed);
+		}
+	}
+
+	/**
+	 * Fetch all items of the external feed and store them in the database.
+	 * 
+	 * @param feed
+	 */
+	private void saveExternalItems(Feed feed) {
+		List<Item> externalItems = externalFeedFetcher.fetchItems(feed);
+		
+		for (Item externalItem : externalItems) {
+			Item reloaded = itemDAO.loadItemByGuid(feed.getKey(), externalItem.getGuid());
+			if (reloaded == null) {
+				externalItem.setCreationDate(new Date());
+				itemDAO.createItem(feed, externalItem);
+			} else {
+				reloaded.setAuthor(externalItem.getAuthor());
+				reloaded.setExternalLink(externalItem.getExternalLink());
+				reloaded.setPublishDate(externalItem.getPublishDate());
+				reloaded.setTitle(externalItem.getTitle());
+				reloaded.setDescription(externalItem.getDescription());
+				reloaded.setContent(externalItem.getContent());
+				reloaded.setEnclosure(externalItem.getEnclosure());
+				
+				// last modified should not be null
+				Date lastModified = externalItem.getLastModified();
+				if (lastModified != null) {
+					reloaded.setLastModified(lastModified);
+				} else {
+					reloaded.setLastModified(new Date());
+				}
+				
+				itemDAO.updateItem(reloaded);
+			}
+		}
+	}
+
+	/**
+	 * 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
+	 */
+	private void enrichRepositoryEntryByFeedInformation(Feed feed) {
+		RepositoryEntry entry = repositoryManager.lookupRepositoryEntry(feed, false);
+		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
+				String saveTitle = PersistenceHelper.truncateStringDbSave(feed.getTitle(), 100, true);
+				entry.setDisplayname(saveTitle);
+				String saveDesc = PersistenceHelper.truncateStringDbSave(feed.getDescription(), 16777210, true);
+				entry.setDescription(saveDesc);
+				// Update the image
+				VFSLeaf oldEntryImage = repositoryManager.getImage(entry);
+				if (oldEntryImage != null) {
+					// Delete the old File
+					oldEntryImage.delete();
+				}
+				// Copy the feed image to the repository home folder
+				VFSItem newImage = feedFileStorage.loadFeedMedia(feed);
+				if (newImage == null) {
+					// huh? image defined but not found on disk - remove
+					// image from feed
+					feed.setImageName(null);
+				} else {
+					repositoryManager.setImage((VFSLeaf) newImage, entry);
+				}
+			}
+		}
+	}
+	
+	/**
+	 * Update the feed resource with the latest set properties in the repository
+	 * entry.
+	 * <p>
+	 * Properties are:
+	 * <ul>
+	 * <li>Title (if not already set)
+	 * <li>Author
+	 * </ul>
+	 * 
+	 * @param feed
+	 */
+	private Feed enrichFeedByRepositoryEntryInfromation(Feed feed) {
+		RepositoryEntry entry = repositoryManager.lookupRepositoryEntry(feed, false);
+		if (entry != null && feed != null) {
+			Date whenTheFeedWasLastModified = feed.getLastModified();
+			if (whenTheFeedWasLastModified == null || entry.getLastModified().after(whenTheFeedWasLastModified)) {
+				// Copy the title (only) initially
+				String feedTitle = feed.getTitle();
+				if (feedTitle == null) {
+					feed.setTitle(entry.getDisplayname());
+				}
+				if (StringHelper.containsNonWhitespace(entry.getAuthors())) {
+					feed.setAuthor(entry.getAuthors());
+				} else {
+					feed.setAuthor(null);
+				}
+				feed = updateFeed(feed);
+			}
+		}
+		return feed;
+	}
+
+	/**
+	 * A unique key for the item of the feed. Can be used e.g. for locking and
+	 * caching.
+	 * 
+	 * @param string
+	 * @param string2
+	 * @return A unique key for the item of the feed
+	 */
+	private String itemKey(String string, String string2) {
+		final StringBuffer key = new StringBuffer();
+		key.append("feed").append(string2);
+		key.append("_item_").append(string);
+		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;
+	}
+
+	@Override
+	public VFSContainer getItemContainer(Item item) {
+		return feedFileStorage.getOrCreateItemContainer(item);
+	}
+	
+	@Override
+	public void saveItemAsXML(Item item) {
+		feedFileStorage.saveItemAsXML(item);
+	}
+	
+	@Override
+	public void deleteItemXML(Item item) {
+		feedFileStorage.deleteItemXML(item);
+	}
+
+	@Override
+	public File loadItemEnclosureFile(Item item) {
+		return feedFileStorage.loadItemMedia(item);
+	}
+
+	@Override
+	public MediaResource createItemMediaFile(OLATResourceable feed, String itemId, String fileName) {
+		VFSMediaResource mediaResource = null;
+		// Brute force method for fast delivery
+		try {
+			VFSItem item = feedFileStorage.getOrCreateFeedItemsContainer(feed);
+			item = item.resolve(itemId);
+			item = item.resolve(MEDIA_DIR);
+			item = item.resolve(fileName);
+			if (item instanceof VFSLeaf) {
+				mediaResource = new VFSMediaResource((VFSLeaf) item);
+			}
+		} catch (NullPointerException e) {
+			log.debug("Media resource could not be created from file: ", fileName);
+		}
+		return mediaResource;
+	}
+
+	@Override
+	public VFSLeaf createFeedMediaFile(OLATResourceable feed, String fileName, Size thumbnailSize) {
+		VFSLeaf mediaResource = null;
+		// Brute force method for fast delivery
+		try {
+			VFSItem item =feedFileStorage.getOrCreateFeedMediaContainer(feed);
+			item = item.resolve(fileName);
+			if (thumbnailSize != null && thumbnailSize.getHeight() > 0 && thumbnailSize.getWidth() > 0
+					&& item instanceof MetaTagged) {
+				item = ((MetaTagged) item).getMetaInfo().getThumbnail(thumbnailSize.getWidth(),
+						thumbnailSize.getHeight(), false);
+			}
+			if (item instanceof VFSLeaf) {
+				mediaResource = (VFSLeaf) item;
+			}
+		} catch (NullPointerException e) {
+			log.debug("Media resource could not be created from file: ", fileName);
+		}
+		return mediaResource;
+	}
+
+	@Override
+	public String getFeedBaseUri(Feed feed, Identity identity, Long courseId, String nodeId) {
+		return FeedMediaDispatcher.getFeedBaseUri(feed, identity, courseId, nodeId);
+	}
+
+	@Override
+	public MediaResource createFeedFile(OLATResourceable ores, Identity identity, Long courseId, String nodeId) {
+		MediaResource media = null;
+		Feed feed = loadFeed(ores);
+
+		if (feed != null) {
+			SyndFeed rssFeed = new RSSFeed(feed, identity, courseId, nodeId);
+			media = new SyndFeedMediaResource(rssFeed);
+		}
+		return media;
+	}
+
+	@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);
+	}
+
+	@Override
+	public boolean copy(OLATResource sourceResource, OLATResource targetResource) {
+		// copy the folders and files
+		File sourceFileroot = fileResourceManager.getFileResourceRootImpl(sourceResource).getBasefile();
+		File sourceFeedRoot = new File(sourceFileroot, getFeedKind(sourceResource));
+		File targetFileroot = fileResourceManager.getFileResourceRootImpl(targetResource).getBasefile();
+		FileUtils.copyFileToDir(sourceFeedRoot, targetFileroot, "add file resource");
+
+		// load the feed and the items from the database
+		Feed sourceFeed = feedDAO.loadFeed(sourceResource);
+		List<Item> items = itemDAO.loadItems(sourceFeed);
+		
+		// copy the feed in the database
+		Feed targetFeed = feedDAO.copyFeed(sourceResource, targetResource);
+		
+		// copy the items in the database
+		for (Item item : items) {
+			itemDAO.copyItem(targetFeed, item);
+		}
+
+		return true;
+	}
+
+	@Override
+	public VFSMediaResource getFeedArchiveMediaResource(OLATResourceable resource) {
+		VFSLeaf zip = getFeedArchive(resource);
+		return new VFSMediaResource(zip);
+	}
+
+	@Override
+	public VFSLeaf getFeedArchive(OLATResourceable resource) {
+		VFSContainer rootContainer = feedFileStorage.getResourceContainer(resource);
+		VFSContainer feedContainer = feedFileStorage.getOrCreateFeedContainer(resource);
+		
+		// Load the feed from database an store it to the XML file.
+		Feed feed = feedDAO.loadFeed(resource);
+		feed.setModelVersion(FeedImpl.CURRENT_MODEL_VERSION);
+		feedFileStorage.saveFeedAsXML(feed);
+		
+		// Load the items from the database, make it export safe and store them
+		// to XML files.
+		List<Item> items = loadItems(feed);
+		for (Item item : items) {
+			if (feed.isInternal()) {
+				// Because in internal feed only the author key is stored and this
+				// key won't be the same in an other installation. We need a
+				// fallback in this case.
+				if (!item.isAuthorFallbackSet()) {
+					String author = item.getAuthor();
+					if (StringHelper.containsNonWhitespace(author)) {
+						item.setAuthor(author);
+					}
+				}
+			}
+			feedFileStorage.saveItemAsXML(item);
+		}
+
+		// synchronize all zip processes for this feed
+		// o_clusterOK by:fg
+		VFSLeaf zip = coordinator.getSyncer().doInSync(resource, new SyncerCallback<VFSLeaf>() {
+			@Override
+			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), false);
+				return (VFSLeaf) rootContainer.resolve(zipFileName);
+			}
+		});
+		
+		// delete the XML files again. They are only needed for the export.
+		for (Item item : items) {
+			feedFileStorage.deleteItemXML(item);
+		}
+		feedFileStorage.deleteFeedXML(feed);
+		
+		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
+	 */
+	private String getZipFileName(OLATResourceable resource) {
+		return getFeedKind(resource) + ".zip";
+	}
+
+	@Override
+	public Feed loadFeedFromXML(Path feedDir) {
+		return feedFileStorage.loadFeedFromXML(feedDir);
+	}
+
+	@Override
+	public void importFeedFromXML(OLATResource ores) {
+		Feed feedFromXml = feedFileStorage.loadFeedFromXML(ores);
+		if (feedFromXml == null) return;
+		
+		// Check if the feed already exits or create it. The feed exists
+		// possibly, if a previous migration from an XML feed was not 
+		// successful.
+		Feed feed = feedDAO.loadFeed(ores);
+		if (feed == null) {
+			feedFromXml.setResourceableId(ores.getResourceableId());
+			feed = feedDAO.createFeed(feedFromXml);
+		}
+		
+		List<Item> itemsFromXml = feedFileStorage.loadItemsFromXML(ores);
+		itemsFromXml = fixFeedVersionIssues(feedFromXml, itemsFromXml);
+		for (Item itemFromXml : itemsFromXml) {
+			// Check if the feed already exits or create it. 
+			Item item = itemDAO.loadItemByGuid(feed.getKey(), itemFromXml.getGuid());
+			if (item == null) {
+				itemFromXml.setAuthorKey(null);
+				itemFromXml.setModifierKey(null);
+				itemDAO.createItem(feed, itemFromXml);
+			}
+			feedFileStorage.deleteItemXML(itemFromXml);
+		}
+		
+		feedFileStorage.deleteFeedXML(feed);
+	}
+
+	/**
+	 * Method that checks the current feed data model version and applies
+	 * necessary fixes to the model. Since feeds can be exported and imported
+	 * this fixes must apply on the fly and can't be implemented with the system
+	 * upgrade mechanism.
+	 * 
+	 * @param feed
+	 * @return the fixed items
+	 */
+	private List<Item> fixFeedVersionIssues(Feed feed, List<Item> items) {
+		if (feed != null) {
+			if (feed.getModelVersion() < 2
+					&& feed.isInternal()
+					&& PodcastFileResource.TYPE_NAME.equals(feed.getResourceableTypeName())) {
+				// In model 1 the podcast episode items were set as drafts
+				// which resulted in invisible episodes. They have to be
+				// set to published. (OLAT-5767)
+				for (Item episode : items) {
+					// Mark episode as published
+					episode.setDraft(false);
+				}
+			}
+		}
+		return items;
+	}
+
+	@Override
+	public void releaseLock(LockResult lock) {
+		if (lock != null) {
+			coordinator.getLocker().releaseLock(lock);
+		}
+	}
+
+	@Override
+	public LockResult acquireLock(OLATResourceable feed, Identity identity) {
+		// OLATResourceable itemLock =
+		// OresHelper.createOLATResourceableInstance("podcastlock_" +
+		// feed.getResourceableId() + "_meta", item.getId())
+		LockResult lockResult = coordinator.getLocker().acquireLock(feed, identity, null);
+		return lockResult;
+	}
+
+	@Override
+	public LockResult acquireLock(OLATResourceable feed, Item item, Identity identity) {
+		String key = itemKey(item, feed);
+		if (key.length() >= OresHelper.ORES_TYPE_LENGTH) {
+			key = Encoder.md5hash(key);
+		}
+		OLATResourceable itemResource = OresHelper.createOLATResourceableType(key);
+		LockResult lockResult = coordinator.getLocker().acquireLock(itemResource, identity, key);
+		return lockResult;
+	}
+
+	@Override
+	public Quota getQuota(OLATResourceable feed) {
+		OlatRootFolderImpl container = feedFileStorage.getResourceContainer(feed);
+
+		Quota quota = QuotaManager.getInstance().getCustomQuota(container.getRelPath());
+		if (quota == null) {
+			Quota defQuota = QuotaManager.getInstance().getDefaultQuota(QuotaConstants.IDENTIFIER_DEFAULT_FEEDS);
+			quota = QuotaManager.getInstance().createQuota(container.getRelPath(), defQuota.getQuotaKB(),
+					defQuota.getUlLimitKB());
+		}
+
+		return quota;
+	}
+
+	@Override
+	public String getFeedKind(OLATResourceable ores) {
+		String feedKind = null;
+		String typeName = ores.getResourceableTypeName();
+		if (PodcastFileResource.TYPE_NAME.equals(typeName)) {
+			feedKind = KIND_PODCAST;
+		} else if (BlogFileResource.TYPE_NAME.equals(typeName)) {
+			feedKind = KIND_BLOG;
+		} else if ("LiveBlog".equals(typeName)) {
+			feedKind = KIND_BLOG;
+		}
+		return feedKind;
+	}
+
+}
diff --git a/src/main/java/org/olat/modules/webFeed/managers/FeedNotifications.java b/src/main/java/org/olat/modules/webFeed/manager/FeedNotifications.java
similarity index 95%
rename from src/main/java/org/olat/modules/webFeed/managers/FeedNotifications.java
rename to src/main/java/org/olat/modules/webFeed/manager/FeedNotifications.java
index 822c075489c..27c5a6143b5 100644
--- a/src/main/java/org/olat/modules/webFeed/managers/FeedNotifications.java
+++ b/src/main/java/org/olat/modules/webFeed/manager/FeedNotifications.java
@@ -17,7 +17,7 @@
  * frentix GmbH, http://www.frentix.com
  * <p>
  */
-package org.olat.modules.webFeed.managers;
+package org.olat.modules.webFeed.manager;
 
 import java.util.ArrayList;
 import java.util.Date;
@@ -35,8 +35,8 @@ import org.olat.core.logging.OLog;
 import org.olat.core.logging.Tracing;
 import org.olat.core.util.Util;
 import org.olat.core.util.resource.OresHelper;
-import org.olat.modules.webFeed.models.Feed;
-import org.olat.modules.webFeed.models.Item;
+import org.olat.modules.webFeed.Feed;
+import org.olat.modules.webFeed.Item;
 import org.olat.modules.webFeed.ui.FeedMainController;
 /**
 *
@@ -80,10 +80,10 @@ public class FeedNotifications {
 				Feed feed;
 				if ("CourseModule".equals(resName)) {
 					OLATResourceable ores = OresHelper.createOLATResourceableInstance(resName, Long.parseLong(data));
-					feed = feedManager.getFeed(ores);
+					feed = feedManager.loadFeed(ores);
 				} else {
 					OLATResourceable ores = OresHelper.createOLATResourceableInstance(resName, resId);
-					feed = feedManager.getFeed(ores);
+					feed = feedManager.loadFeed(ores);
 				}
 				List<Item> listItems = feedManager.loadItems(feed);
 				for (Item item : listItems) {
diff --git a/src/main/java/org/olat/modules/webFeed/manager/ItemDAO.java b/src/main/java/org/olat/modules/webFeed/manager/ItemDAO.java
new file mode 100644
index 00000000000..d4dd7b70207
--- /dev/null
+++ b/src/main/java/org/olat/modules/webFeed/manager/ItemDAO.java
@@ -0,0 +1,275 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <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 the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <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>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.webFeed.manager;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import javax.persistence.NoResultException;
+
+import org.olat.core.commons.persistence.DB;
+import org.olat.modules.webFeed.Feed;
+import org.olat.modules.webFeed.Item;
+import org.olat.modules.webFeed.model.ItemImpl;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * 
+ * Initial date: 02.05.2017<br>
+ * 
+ * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
+ *
+ */
+@Service("itemDao")
+public class ItemDAO {
+
+	@Autowired
+	private DB dbInstance;
+
+	/**
+	 * Create a new Item and connect it with the feed.
+	 * 
+	 * @param feed
+	 * @return the created item
+	 */
+	public Item createItem(Feed feed) {
+		if (feed == null) return null;
+		
+		Item item = new ItemImpl(feed);
+		item.setCreationDate(new Date());
+		item.setLastModified(item.getCreationDate());
+		dbInstance.getCurrentEntityManager().persist(item);
+		return item;
+	}
+	
+	/**
+	 * Saves a new Item in the database. The Item has to have a Feed. Otherwise
+	 * the Item is not saved an the method returns null.
+	 * 
+	 * @param feed
+	 * @param item
+	 * @return the created item or null
+	 */
+	public Item createItem(Feed feed, Item item) {
+		if (feed == null) return null;
+		
+		ItemImpl itemImpl = (ItemImpl)item;
+		itemImpl.setFeed(feed);
+		if (itemImpl.getCreationDate() == null) {
+			itemImpl.setCreationDate(new Date());
+		}
+		if (itemImpl.getLastModified() == null) {
+			itemImpl.setLastModified(itemImpl.getCreationDate());
+		}
+		dbInstance.getCurrentEntityManager().persist(itemImpl);
+		return itemImpl;
+	}
+	
+	/**
+	 * Copy an Item to an other (or the same) feed.<br>
+	 * The attributes creationDate and lastModified are mandatory. If one of
+	 * these is null, no new item is created and the method returns null.
+	 * 
+	 * @param feed
+	 * @param item
+	 * @return the created item or null
+	 */
+	public Item copyItem(Feed feed, Item item) {
+		if (item == null) {
+			return null;
+		}
+		
+		Long key = item.getKey();
+		Date creationDate = item.getCreationDate();
+		Date lastModified = item.getLastModified();
+		if (feed == null || key == null || creationDate == null || lastModified == null) {
+			return null;
+		}
+		
+		ItemImpl itemImpl = (ItemImpl) loadItem(item.getKey());
+		dbInstance.getCurrentEntityManager().detach(itemImpl);
+		itemImpl.setKey(null);
+		itemImpl.setFeed(feed);
+		dbInstance.getCurrentEntityManager().persist(itemImpl);
+		return itemImpl;
+	}
+
+	/**
+	 * Loads an item.
+	 * 
+	 * @param key the key of the item
+	 * @return the loaded item
+	 */
+	public Item loadItem(Long key) {
+		if (key == null) return null;
+		
+		return dbInstance.getCurrentEntityManager().find(ItemImpl.class, key);
+	}
+
+	/**
+	 * Loads an item by GUID.
+	 * The GUID is only unique inside a feed because when a external feed is
+	 * stored two times the guid are stores two times as well.
+	 * 
+	 * @param feedKey the key of the feed
+	 * @param guid the guid of the item
+	 * @return the loaded item or null
+	 */
+	public Item loadItemByGuid(Long feedKey, String guid) {
+		if (feedKey == null) return null;
+		
+		Item item = null;
+		try {
+			item = dbInstance.getCurrentEntityManager()
+					.createNamedQuery("loadItemByGuid", ItemImpl.class)
+					.setParameter("feedKey", feedKey)
+					.setParameter("guid", guid)
+					.getSingleResult();
+		} catch (NoResultException nre) {
+			// nothing to do, return null
+		}
+		
+		return item;
+	}
+
+	/**
+	 * Loads all items of a feed.
+	 * 
+	 * @param feed
+	 * @return the list of Items or null
+	 */
+	public List<Item> loadItems(Feed feed) {
+		if (feed == null) return null;
+		
+		List<ItemImpl> items = dbInstance.getCurrentEntityManager()
+				.createNamedQuery("loadItemsByFeed", ItemImpl.class)
+				.setParameter("feed", feed)
+				.getResultList();
+		return new ArrayList<>(items);
+	}
+	
+	/**
+	 * Loads all items of a feed.
+	 * 
+	 * @param feed
+	 * @return the list of the guids or null
+	 */
+	public List<String> loadItemsGuid(Feed feed) {
+		if (feed == null) return null;
+		
+		return dbInstance.getCurrentEntityManager()
+				.createNamedQuery("loadItemsGuidByFeed", String.class)
+				.setParameter("feed", feed)
+				.getResultList();
+	}
+
+	/**
+	 * Loads all published items of a feed.
+	 * 
+	 * @param feed
+	 * @return the list of published Items or null
+	 */
+	public List<Item> loadPublishedItems(Feed feed) {
+		if (feed == null) return null;
+		
+		// Load all items and filter in a second step instead of push the 
+		// criteria to the query for the reason to use the same definition
+		// of published everywhere.
+		List<ItemImpl> items = dbInstance.getCurrentEntityManager()
+				.createNamedQuery("loadItemsByFeed", ItemImpl.class)
+				.setParameter("feed", feed)
+				.getResultList()
+				.stream()
+				.filter((item) -> item.isPublished())
+				.collect(Collectors.toList());
+		return new ArrayList<>(items);
+	}
+	
+	/**
+	 * Check if the are items for a feed.
+	 * 
+	 * @param feed
+	 * @return
+	 */
+	public boolean hasItems(Feed feed) {
+		boolean hasItems = false;
+		
+		List<ItemImpl> items = dbInstance.getCurrentEntityManager()
+				.createNamedQuery("loadItemsByFeed", ItemImpl.class)
+				.setParameter("feed", feed)
+				.setMaxResults(1)
+				.getResultList();
+		
+		if (items != null && !items.isEmpty()) {
+			hasItems = true;
+		}
+		
+		return hasItems;
+	}
+	
+	/**
+	 * Update an item.
+	 * 
+	 * If it is an item of an internal feed, the last modified date is set.
+	 * Items of external feeds get the last modified date from the external
+	 * item.
+	 * 
+	 * @param item
+	 * @return the updated item
+	 */
+	public Item updateItem(Item item) {
+		if (item == null) return null;
+		
+		if (!item.getFeed().isExternal()) {
+			((ItemImpl)item).setLastModified(new Date());
+		}
+		
+		return dbInstance.getCurrentEntityManager().merge(item);
+	}
+
+	/**
+	 * Deletes an item.
+	 * 
+	 * @param item
+	 * @return the number of deleted items
+	 */
+	public int removeItem(Item item) {
+		return dbInstance.getCurrentEntityManager()
+				.createNamedQuery("removeItem").setParameter("key", item.getKey())
+				.executeUpdate();
+	}
+
+	/**
+	 * Deletes all Items of a Feed.
+	 * 
+	 * @param feed
+	 */
+	public int removeItems(Feed feed) {
+		if (feed == null) return 0;
+		
+		return dbInstance.getCurrentEntityManager()
+				.createNamedQuery("removeItemsForFeed").setParameter("feedKey", feed.getKey())
+				.executeUpdate();
+	}
+
+}
diff --git a/src/main/java/org/olat/modules/webFeed/managers/PodcastNotificationsHandler.java b/src/main/java/org/olat/modules/webFeed/manager/PodcastNotificationsHandler.java
similarity index 99%
rename from src/main/java/org/olat/modules/webFeed/managers/PodcastNotificationsHandler.java
rename to src/main/java/org/olat/modules/webFeed/manager/PodcastNotificationsHandler.java
index ce3d407da86..cae52b7a64d 100644
--- a/src/main/java/org/olat/modules/webFeed/managers/PodcastNotificationsHandler.java
+++ b/src/main/java/org/olat/modules/webFeed/manager/PodcastNotificationsHandler.java
@@ -17,7 +17,7 @@
  * frentix GmbH, http://www.frentix.com
  * <p>
  */
-package org.olat.modules.webFeed.managers;
+package org.olat.modules.webFeed.manager;
 
 import java.util.Date;
 import java.util.List;
diff --git a/src/main/java/org/olat/modules/webFeed/manager/RomeFeedFetcher.java b/src/main/java/org/olat/modules/webFeed/manager/RomeFeedFetcher.java
new file mode 100644
index 00000000000..e1960311e12
--- /dev/null
+++ b/src/main/java/org/olat/modules/webFeed/manager/RomeFeedFetcher.java
@@ -0,0 +1,185 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <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 the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <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>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.webFeed.manager;
+
+import java.io.Reader;
+import java.net.URL;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.olat.core.logging.OLog;
+import org.olat.core.logging.Tracing;
+import org.olat.core.util.StringHelper;
+import org.olat.modules.webFeed.Enclosure;
+import org.olat.modules.webFeed.ExternalFeedFetcher;
+import org.olat.modules.webFeed.Feed;
+import org.olat.modules.webFeed.Item;
+import org.olat.modules.webFeed.model.EnclosureImpl;
+import org.olat.modules.webFeed.model.ItemImpl;
+import org.springframework.stereotype.Service;
+
+import com.rometools.rome.feed.synd.SyndContent;
+import com.rometools.rome.feed.synd.SyndEnclosure;
+import com.rometools.rome.feed.synd.SyndEntry;
+import com.rometools.rome.feed.synd.SyndFeed;
+import com.rometools.rome.io.SyndFeedInput;
+import com.rometools.rome.io.XmlReader;
+
+/**
+ * This implementation of an ExternalFeedFetcher uses the library Rome to fetch
+ * feeds form an external web site.<br>
+ * 
+ * Initial date: 12.05.2017<br>
+ * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
+ *
+ */
+@Service
+public class RomeFeedFetcher implements ExternalFeedFetcher {
+
+	private static final OLog log = Tracing.createLoggerFor(RomeFeedFetcher.class);
+	
+	private SyndFeedInput syndFeedInput = new SyndFeedInput();
+	
+	@Override
+	public Feed fetchFeed(Feed feed) {
+		SyndFeed syndFeed = fetchSyndFeed(feed.getExternalFeedUrl());
+		return justifyFeed(feed, syndFeed);
+	}
+	
+	/**
+	 * Takes the values from the SyndFeed and pass them to the Feed.
+	 * @param feed
+	 * @param syndFeed
+	 * @return the justified feed or null
+	 */
+	protected Feed justifyFeed(Feed feed, SyndFeed syndFeed) {
+		if (feed == null) return null;
+		
+		String imageUrl = null;
+		if (syndFeed == null) {
+			// keep the old image
+			imageUrl = feed.getExternalImageURL();
+		} else if (syndFeed.getImage() != null) {
+			// take the new image
+			imageUrl = syndFeed.getImage().getUrl();
+		}
+		
+		feed.setExternalImageURL(imageUrl);
+		
+		return feed;
+	}
+
+	@Override
+	public List<Item> fetchItems(Feed feed) {
+		SyndFeed syndFeed = fetchSyndFeed(feed.getExternalFeedUrl());
+		return syndFeed.getEntries().stream()
+				.map(entry -> convertEntry(feed, entry))
+				.collect(Collectors.toList());
+	}
+	
+	/**
+	 * Fetches the SyndFeed of an URL.
+	 * @param feedURL
+	 * @return
+	 */
+	protected SyndFeed fetchSyndFeed(String feedURL) {
+		SyndFeed syndFeed = null;
+
+		try(Reader xmlReader = new XmlReader(new URL(feedURL))) {
+			syndFeed = syndFeedInput.build(xmlReader);
+			log.info("Read external feed: " + feedURL);
+		} catch (Exception e) {
+			log.warn("Cannot read external feed: : " + feedURL, e);
+		}
+
+		return syndFeed;
+	}
+	
+	
+	/**
+	 * Converts a <code>SyndEntry</code> into an <code>Item</code>
+	 * 
+	 * @param entry
+	 * @return
+	 */
+	protected Item convertEntry(Feed feed, SyndEntry entry) {
+		Item item = new ItemImpl(feed);
+		
+		item.setAuthor(entry.getAuthor());
+		item.setExternalLink(entry.getLink());
+		item.setGuid(entry.getUri());
+		item.setLastModified(entry.getUpdatedDate());
+		item.setPublishDate(entry.getPublishedDate());
+		item.setTitle(entry.getTitle());
+		
+		if (entry.getDescription() != null) {
+			item.setDescription(entry.getDescription().getValue());
+		}
+		
+		List<SyndContent> contents = entry.getContents();
+		item.setContent(joinContents(contents));
+
+		List<SyndEnclosure> enclosures = entry.getEnclosures();
+		item.setEnclosure(convertEnclosures(enclosures));
+		
+		return item;
+	}
+
+	/**
+	 * Converts a List of <code>SyndEnclosures</code> into an <code>Enclosure</code>.
+	 * Only one media file is supported. If the List has more than one entry, the
+	 * first entry is taken.
+	 * SyndEnclosures without an URL are not converted, because it is necessary to
+	 * fetch the enclosure.
+	 * 
+	 * @param enclosures
+	 * @return the enclosure or null
+	 */
+	protected Enclosure convertEnclosures(List<SyndEnclosure> enclosures) {
+		if (enclosures == null || enclosures.isEmpty()) return null;
+		
+		SyndEnclosure syndEnclosure = enclosures.get(0);
+		Enclosure enclosure = null;
+		
+		if (StringHelper.containsNonWhitespace(syndEnclosure.getUrl())) {
+			enclosure = new EnclosureImpl();
+			enclosure.setExternalUrl(syndEnclosure.getUrl());
+			enclosure.setLength(syndEnclosure.getLength());
+			enclosure.setType(syndEnclosure.getType());
+		}
+		
+		return enclosure;
+	}
+
+	/**
+	 * Joins the values of all SyndContent by a html p.
+	 * 
+	 * @param contents
+	 * @return the joined values or null
+	 */
+	protected String joinContents(List<SyndContent> contents) {
+		if (contents == null || contents.isEmpty()) return null;
+
+		return contents.stream()
+				 .map(SyndContent::getValue)
+				 .collect(Collectors.joining("<p />"));
+	}
+
+}
diff --git a/src/main/java/org/olat/modules/webFeed/managers/ValidatedURL.java b/src/main/java/org/olat/modules/webFeed/manager/ValidatedURL.java
similarity index 96%
rename from src/main/java/org/olat/modules/webFeed/managers/ValidatedURL.java
rename to src/main/java/org/olat/modules/webFeed/manager/ValidatedURL.java
index 00ebb9d71d8..34acc3ad2c6 100644
--- a/src/main/java/org/olat/modules/webFeed/managers/ValidatedURL.java
+++ b/src/main/java/org/olat/modules/webFeed/manager/ValidatedURL.java
@@ -17,7 +17,7 @@
  * frentix GmbH, http://www.frentix.com
  * <p>
  */
-package org.olat.modules.webFeed.managers;
+package org.olat.modules.webFeed.manager;
 
 /**
  * <P>
diff --git a/src/main/java/org/olat/modules/webFeed/managers/package.html b/src/main/java/org/olat/modules/webFeed/manager/package.html
similarity index 100%
rename from src/main/java/org/olat/modules/webFeed/managers/package.html
rename to src/main/java/org/olat/modules/webFeed/manager/package.html
diff --git a/src/main/java/org/olat/modules/webFeed/managers/FeedManagerImpl.java b/src/main/java/org/olat/modules/webFeed/managers/FeedManagerImpl.java
deleted file mode 100644
index 1c6f81b60a7..00000000000
--- a/src/main/java/org/olat/modules/webFeed/managers/FeedManagerImpl.java
+++ /dev/null
@@ -1,1339 +0,0 @@
-/**
- * <a href="http://www.openolat.org">
- * OpenOLAT - Online Learning and Training</a><br>
- * <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 the
- * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
- * <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>
- * Initial code contributed and copyrighted by<br>
- * frentix GmbH, http://www.frentix.com
- * <p>
- */
-package org.olat.modules.webFeed.managers;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-
-import org.apache.commons.io.IOUtils;
-import org.olat.admin.quota.QuotaConstants;
-import org.olat.core.CoreSpringFactory;
-import org.olat.core.commons.modules.bc.meta.tagged.MetaTagged;
-import org.olat.core.commons.modules.bc.vfs.OlatRootFolderImpl;
-import org.olat.core.commons.persistence.PersistenceHelper;
-import org.olat.core.commons.services.commentAndRating.CommentAndRatingService;
-import org.olat.core.commons.services.image.ImageService;
-import org.olat.core.commons.services.image.Size;
-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.util.CodeHelper;
-import org.olat.core.util.Encoder;
-import org.olat.core.util.FileUtils;
-import org.olat.core.util.Formatter;
-import org.olat.core.util.StringHelper;
-import org.olat.core.util.ZipUtil;
-import org.olat.core.util.cache.CacheWrapper;
-import org.olat.core.util.coordinate.Coordinator;
-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.Quota;
-import org.olat.core.util.vfs.QuotaManager;
-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.resource.OLATResource;
-import org.olat.resource.OLATResourceManager;
-
-import com.rometools.rome.feed.synd.SyndContent;
-import com.rometools.rome.feed.synd.SyndEnclosure;
-import com.rometools.rome.feed.synd.SyndEntry;
-import com.rometools.rome.feed.synd.SyndFeed;
-import com.rometools.rome.feed.synd.SyndImage;
-import com.rometools.rome.io.FeedException;
-import com.rometools.rome.io.ParsingFeedException;
-import com.rometools.rome.io.SyndFeedInput;
-import com.rometools.rome.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 {
-	private static final int PICTUREWIDTH = 570; // same as in repository metadata image upload
-	
-	private RepositoryManager repositoryManager;
-	private Coordinator coordinator;
-	private OLATResourceManager resourceManager;
-	private FileResourceManager fileResourceManager;
-	private ImageService imageHelper;
-	
-	private final XStream xstream;
-	
-	// Better performance when protected (apparently)
-	protected CacheWrapper<String,Feed> feedCache;
-	private OLog log;
-
-
-	/**
-	 * spring only
-	 */
-	protected FeedManagerImpl(OLATResourceManager resourceManager, 
-			FileResourceManager fileResourceManager, CoordinatorManager coordinatorManager) {
-		
-		this.resourceManager = resourceManager;
-		this.fileResourceManager = fileResourceManager;
-		INSTANCE = this;
-		this.log = getLogger();
-		xstream = new XStream();
-		xstream.alias("feed", Feed.class);
-		xstream.alias("item", Item.class);
-		this.coordinator = coordinatorManager.getCoordinator();
-	}
-	
-	/**
-	 * [used by Spring]
-	 * @param imageHelper
-	 */
-	public void setImageHelper(ImageService imageHelper) {
-		this.imageHelper = imageHelper;
-	}
-
-	/**
-	 * 
-	 * @param repositoryManager
-	 */
-	public void setRepositoryManager(RepositoryManager repositoryManager) {
-		this.repositoryManager = repositoryManager;
-	}
-	
-	/**
-	 * 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) {
-		OLATResource ores = resourceManager.createOLATResourceInstance(feedResource);
-		resourceManager.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<String,Feed> initFeedCache() {
-		if (feedCache == null) {
-			OLATResourceable ores = OresHelper.createOLATResourceableType(Feed.class);
-			coordinator.getSyncer().doInSync(ores, new SyncerExecutor() {
-				@SuppressWarnings("synthetic-access")
-				public void execute() {
-					if (feedCache == null) {
-						feedCache = coordinator.getCacher().getCache(FeedManager.class.getSimpleName(), "feed");
-					}
-				}
-			});
-		}
-		return feedCache;
-	}
-
-	/**
-	 * @see org.olat.modules.webFeed.managers.FeedManager#delete(org.olat.core.id.OLATResourceable)
-	 */
-	@Override
-	public void delete(OLATResourceable feed) {
-		fileResourceManager.deleteFileResource(feed);
-		// Delete comments and ratings
-		CommentAndRatingService commentAndRatingService = CoreSpringFactory.getImpl(CommentAndRatingService.class);
-		commentAndRatingService.deleteAllIgnoringSubPath(feed);
-		feed = null;
-	}
-
-	/**
-	 * @see org.olat.modules.webFeed.managers.FeedManager#getFeed(org.olat.core.id.OLATResourceable)
-	 */
-	public Feed getFeed(OLATResourceable ores) {
-		return getFeed(ores, true);
-	}
-
-	/**
-	 * Load the feed and all the items and put it into the cache. If it's already
-	 * in the cache, use the version from the cache. The feed object is shared
-	 * between users.
-	 * 
-	 * @param ores
-	 * @param inSync
-	 * @return
-	 */
-	private Feed getFeed(OLATResourceable ores, boolean inSync) {
-		// Attempt to fetch the feed from the cache
-		Feed myFeed = 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());
-				// Load all items
-				getItems(myFeed);
-				// See if there are some version issues that need to be fixed now
-				fixFeedVersionIssues(myFeed);
-				// Get repository entry information
-				enrichFeedByRepositoryEntryInfromation(myFeed);
-				// must be final for sync
-				final Feed feed = myFeed;
-				syncedFeedCacheUpdate(feed, inSync);
-			}
-		}
-		return myFeed;
-	}
-
-	/**
-	 * @see org.olat.modules.webFeed.managers.FeedManager#getItem(org.olat.modules.webFeed.models.Feed, java.lang.String)
-	 */
-	public Item getItem(Feed feed, String GUID) {
-		for (Item item : feed.getItems()) {
-			if (item.getGuid().equals(GUID)) {
-				return item;
-			}
-		}
-		return null;
-	}
-
-	
-	/**
-	 * Puts the feed to the feedCache in a synchronized manner.
-	 * 
-	 * @param ores
-	 * @param feed
-	 */
-	private void syncedFeedCacheUpdate(final Feed feed, boolean inSync) {
-		initFeedCache();
-		if(inSync) {
-			coordinator.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 from the feed or load them from the files system.
-	 * 
-	 * @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) {
-			if (feed.getItems() != null && feed.getItems().size() == feed.getItemIds().size()) {
-				// items already loaded, use the loaded items
-				items = feed.getItems();
-			} else {
-				// reload all items
-				items = loadItems(feed);
-			}
-		}
-		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());
-				feed.setDescription(entry.getDescription());
-				feed.setAuthor(entry.getInitialAuthor());
-				// Update the image
-
-				VFSLeaf repoEntryImage = repositoryManager.getImage(entry);
-				if (repoEntryImage != null) {
-					getFeedMediaContainer(feed).copyFrom(repoEntryImage);
-					VFSItem newImage = getFeedMediaContainer(feed).resolve(repoEntryImage.getName());
-					if (newImage != null) {
-						feed.setImageName(newImage.getName());
-					}
-				} else {
-					// There's no repo entry image -> delete the feed image as well.
-					//fxdiff:  FXOLAT-271  (we don't want to delete image)
-					//deleteImage(feed);
-				}
-			}
-		}
-	}
-
-	/**
-	 * @param ores
-	 * @return The repository entry of ores or null
-	 */
-	private RepositoryEntry getRepositoryEntry(OLATResourceable ores) {
-		return repositoryManager.lookupRepositoryEntry(ores, false);
-	}
-
-	/**
-	 * Returns the feed in the given container. It is public to be accessible by
-	 * PodcastFileResource.
-	 * <p>
-	 * Note: this does ONLY read the file from disk, it does NOT put the feed into
-	 * the feed cache nor does it load the associated items. Do not use this
-	 * method generally, use getFeedLight() instead!
-	 * 
-	 * @param feedContainer
-	 * @return The Feed upon success
-	 */
-	public 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;
-	}
-	
-	public Feed readFeedFile(Path feedPath) {
-		Feed feed = null;
-		try (InputStream in = Files.newInputStream(feedPath)) {
-			feed = (Feed)XStreamHelper.readObject(xstream, in);
-		} catch(IOException e) {
-			log.error("", e);
-		}
-		return feed;
-	}
-
-	/**
-	 * Method that checks the current feed data model version and applies necessary
-	 * fixes to the model. Since feeds can be exported and imported this fixes
-	 * must apply on the fly and can't be implemented with the system upgrade mechanism. 
-	 * 
-	 * @param feed
-	 */
-	private void fixFeedVersionIssues(Feed feed) {
-		if (feed == null) return;
-		if (feed.getModelVersion() < 2) {
-			// The model version of models before the introduction of the model version 
-			// will have a model version=0 (set by xstream)
-			if (PodcastFileResource.TYPE_NAME.equals(feed.getResourceableTypeName())) {
-				if (feed.isInternal()) {
-					// In model 1 the podcast episode items were set as drafts which resulted
-					// in invisible episodes. They have to be set to published. (OLAT-5767)
-					for (Item episode : feed.getItems()) {
-						// Mark episode as published and persist the item file on disk
-						episode.setDraft(false);
-						updateItemFileWithoutDoInSync(episode, feed);
-					}
-				}
-			}
-			// Set feed model to newest version and persist feed file on disk
-			feed.setModelVersion(Feed.CURRENT_MODEL_VERSION);
-			VFSContainer container = getFeedContainer(feed);
-			VFSLeaf leaf = (VFSLeaf) container.resolve(FEED_FILE_NAME);
-			XStreamHelper.writeObject(xstream, leaf, feed);
-			//
-			log.info("Updated feed::" + feed.getResourceableTypeName() + "::" + feed.getResourceableId() + " to version::" + Feed.CURRENT_MODEL_VERSION);
-		}
-	}
-
-	/**
-	 * 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();
-		
-		XmlReader reader = null;
-		try {
-			URL url = new URL(feedURL);
-			reader = new XmlReader(url);
-			feed = input.build(reader);
-			// also add the external image url just in case we'll need it later
-			addExternalImageURL(feed, extFeed);
-		} catch(IllegalArgumentException e) {
-			log.warn("The external feed is invalid: " + feedURL, e);
-			IOUtils.closeQuietly(reader);
-		} catch (MalformedURLException e) {
-			log.info("The externalFeedUrl is invalid: " + feedURL);
-		} catch (FeedException e) {
-			log.info("The read feed is invalid: " + feedURL);
-		} catch (IOException e) {
-			log.info("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
-		StringBuilder sb = new StringBuilder();
-		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 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 Feed 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
-		return coordinator.getSyncer().doInSync(feed, new SyncerCallback<Feed>() {
-			public Feed execute() {
-				// reload feed to prevent stale feed overwriting
-				@SuppressWarnings("synthetic-access")
-				Feed reloadedFeed = getFeed(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 = CoreSpringFactory.getImpl(CommentAndRatingService.class);
-				commentAndRatingService.deleteAll(feed, item.getGuid());
-				return reloadedFeed;
-			}
-		});
-	}
-
-	/**
-	 * @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 Feed addItem(final Item item, final FileElement file, final Feed feed) {
-		if (feed.isInternal()) {
-			// synchronize all feed item CUD operations on this feed to prevent
-			// overwriting of changes
-			// o_clusterOK by:fg
-			return coordinator.getSyncer().doInSync(feed, new SyncerCallback<Feed>() {
-				@SuppressWarnings("synthetic-access")
-				public Feed execute() {
-					// reload feed to prevent stale feed overwriting
-					Feed reloadedFeed = getFeed(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 reloadedFeed;
-				}
-			});
-		}
-		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 = getFeed(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
-				String saveTitle = PersistenceHelper.truncateStringDbSave(feed.getTitle(), 100, true);
-				entry.setDisplayname(saveTitle);
-				String saveDesc = PersistenceHelper.truncateStringDbSave(feed.getDescription(), 16777210, true);
-				entry.setDescription(saveDesc);
-				// Update the image
-				VFSLeaf oldEntryImage = repositoryManager.getImage(entry);
-				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);
-					if (newImage == null) {
-						// huh? image defined but not found on disk - remove image from feed
-						deleteImage(feed);
-					} else {
-						repositoryManager.setImage((VFSLeaf)newImage, entry);					
-					}
-				}
-			}
-		}
-	}
-
-	/**
-	 * @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
-	 */
-	public OlatRootFolderImpl getResourceContainer(OLATResourceable ores) {
-		return fileResourceManager.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();
-				}
-			}
-			// Move uploaded file to our container
-			VFSItem movedItem = file.moveUploadFileTo(itemMediaContainer);
-			// Rename to something save for the file system
-			String saveFileName = Formatter.makeStringFilesystemSave(file.getUploadFileName());
-			
-			movedItem.rename(saveFileName);
-
-			// Update the enclosure meta data
-			Enclosure enclosure = new Enclosure();
-			enclosure.setFileName(saveFileName);
-			enclosure.setLength(file.getUploadSize());
-			enclosure.setType(file.getUploadMimeType());
-
-			item.setEnclosure(enclosure);
-		}
-	}
-
-	/**
-	 * @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 Feed updateItem(final Item item, final FileElement file, final Feed feed) {
-		if (feed.isInternal()) {
-			// synchronize all feed item CUD operations on this feed to prevent
-			// overwriting of changes
-			// o_clusterOK by:fg
-			return coordinator.getSyncer().doInSync(feed, new SyncerCallback<Feed>() {
-				@SuppressWarnings("synthetic-access")
-				public Feed execute() {
-					// reload feed to prevent stale feed overwriting
-					Feed reloadedFeed = getFeed(feed, false);
-					if (reloadedFeed.getItemIds().contains(item.getGuid())) {
-						if (file != null) {
-							setEnclosure(file, item, reloadedFeed);
-						}
-						updateItemFileWithoutDoInSync(item, reloadedFeed);
-						update(feed, false);						
-					} else {
-						// do nothing, item was deleted by someone in the meantime
-					}
-					return reloadedFeed;
-				}
-			});			
-		}
-		return null;
-	}
-
-	/**
-	 * Internal helper method to update an item without adding the necessary
-	 * do-in-sync wrapper. This should only be called from within a code block
-	 * that is cluster-synced!
-	 * 
-	 * @param item
-	 * @param feed
-	 */
-	private void updateItemFileWithoutDoInSync (final Item item, final Feed feed) {
-		// Write the item.xml file
-		VFSLeaf itemFile = (VFSLeaf) getItemContainer(item, feed).resolve(ITEM_FILE_NAME);
-		XStreamHelper.writeObject(xstream, itemFile, item);			
-	}
-	
-
-	/**
-	 * @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 coordinator.getSyncer().doInSync(feed, new SyncerCallback<Feed>() {
-			@SuppressWarnings("synthetic-access")
-			public Feed execute() {
-				// reload feed to prevent stale feed overwriting
-				Feed reloadedFeed = getFeed(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 = getFeed(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 coordinator.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);
-			if(item instanceof VFSLeaf) {
-				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 VFSLeaf createFeedMediaFile(OLATResourceable feed, String fileName, Size thumbnailSize) {
-		VFSLeaf mediaResource = null;
-		// Brute force method for fast delivery
-		try {
-			VFSItem item = getFeedMediaContainer(feed);
-			item = item.resolve(fileName);
-			if(thumbnailSize != null && thumbnailSize.getHeight() > 0 && thumbnailSize.getWidth() > 0
-					&& item instanceof MetaTagged) {
-				item = ((MetaTagged)item).getMetaInfo().getThumbnail(thumbnailSize.getWidth(), thumbnailSize.getHeight(), false);
-			}
-			if(item instanceof VFSLeaf) {
-				mediaResource = (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 = getFeed(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);
-	}
-
-	@Override
-	public boolean copy(OLATResource sourceResource, OLATResource targetResource) {
-		File sourceFileroot = FileResourceManager.getInstance().getFileResourceRootImpl(sourceResource).getBasefile();
-		File sourceBlogRoot = new File(sourceFileroot, FeedManager.getInstance().getFeedKind(sourceResource));
-		
-		File targetFileroot = FileResourceManager.getInstance().getFileResourceRootImpl(targetResource).getBasefile();
-		FileUtils.copyFileToDir(sourceBlogRoot, targetFileroot, "add file resource");
-		
-		VFSContainer copyContainer = FeedManager.getInstance().getFeedContainer(targetResource);
-		VFSLeaf leaf = (VFSLeaf) copyContainer.resolve(FeedManager.FEED_FILE_NAME);
-		if (leaf != null) {
-			Feed copyFeed = (Feed) XStreamHelper.readObject(xstream, leaf.getInputStream());
-			copyFeed.setId(targetResource.getResourceableId());
-			XStreamHelper.writeObject(xstream, leaf, copyFeed);
-		}
-		
-		return true;
-	}
-
-	/**
-	 * @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 = getFeed(resource, true);
-		if(feed.isInternal()) {
-			coordinator.getSyncer().doInSync(feed, new SyncerCallback<Boolean>() {
-				@SuppressWarnings("synthetic-access")
-				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 = coordinator.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), false);
-				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) {
-			coordinator.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 = coordinator.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);
-		if (key.length() >= OresHelper.ORES_TYPE_LENGTH) {
-			key = Encoder.md5hash(key);
-		}
-		OLATResourceable itemResource = OresHelper.createOLATResourceableType(key);
-		LockResult lockResult = coordinator.getLocker().acquireLock(itemResource, identity, key);
-		return lockResult;
-	}
-	
-	@Override
-	public Quota getQuota(OLATResourceable feed) {
-		OlatRootFolderImpl container = getResourceContainer(feed);
-
-		Quota quota = QuotaManager.getInstance().getCustomQuota(container.getRelPath());
-		if (quota == null) {
-			Quota defQuota = QuotaManager.getInstance().getDefaultQuota(QuotaConstants.IDENTIFIER_DEFAULT_FEEDS);
-			quota = QuotaManager.getInstance().createQuota(container.getRelPath(), defQuota.getQuotaKB(), defQuota.getUlLimitKB());
-		}
-		
-		return quota;	
-	}
-
-	/**
-	 * @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
-			VFSLeaf imageLeaf = image.moveUploadFileTo(getFeedMediaContainer(feed));
-			// Resize to same dimension box as with repo meta image
-			VFSLeaf tmp = getFeedMediaContainer(feed).createChildLeaf("" + CodeHelper.getRAMUniqueID());
-			imageHelper.scaleImage(imageLeaf, tmp, PICTUREWIDTH,PICTUREWIDTH, false);
-			imageLeaf.delete();
-			imageLeaf = tmp;
-			// Make file system save
-			String saveFileName = Formatter.makeStringFilesystemSave(image.getUploadFileName());
-			imageLeaf.rename(saveFileName);
-			// Update metadata
-			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;
-	}
-
-}
diff --git a/src/main/java/org/olat/modules/webFeed/models/Enclosure.java b/src/main/java/org/olat/modules/webFeed/model/EnclosureImpl.java
similarity index 58%
rename from src/main/java/org/olat/modules/webFeed/models/Enclosure.java
rename to src/main/java/org/olat/modules/webFeed/model/EnclosureImpl.java
index 1c74950a87c..4dd8ab03c8d 100644
--- a/src/main/java/org/olat/modules/webFeed/models/Enclosure.java
+++ b/src/main/java/org/olat/modules/webFeed/model/EnclosureImpl.java
@@ -1,5 +1,5 @@
 /**
- * OLAT - Online Learning and Training<br>
+ * <a href="http://www.openolat.org">
  * http://www.olat.org
  * <p>
  * Licensed under the Apache License, Version 2.0 (the "License"); <br>
@@ -18,79 +18,67 @@
  * http://www.frentix.com<br>
  * <p>
  */
-package org.olat.modules.webFeed.models;
+package org.olat.modules.webFeed.model;
+
+import java.io.Serializable;
+
+import javax.persistence.Embeddable;
+
+import org.olat.modules.webFeed.Enclosure;
 
 /**
- * Enclosure is a sub-element of an RSS item element. It can refer to a media
- * uploadedFile like an mp3-song or an mpeg-video.
- * 
- * <P>
- * Initial Date: Feb 25, 2009 <br>
  * 
- * @author Gregor Wassmann
+ * Initial date: 02.05.2017<br>
+ * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
  */
-public class Enclosure {
+@Embeddable
+public class EnclosureImpl implements Enclosure, Serializable {
+	
+	private static final long serialVersionUID = 4665522340896625643L;
+	
 	private String fileName;
 	private String type;
-	private long length;
+	private Long length;
 	private String externalUrl;
-
-	// This is needed to pass the uploaded file to the manager
-	// private FileElement uploadedFile;
-
-	/**
-	 * @param fileName The fileName to set.
-	 */
-	public void setFileName(String fileName) {
-		this.fileName = fileName;
-	}
-
-	/**
-	 * @return Returns the fileName.
-	 */
+	
+	@Override
 	public String getFileName() {
 		return fileName;
 	}
-
-	/**
-	 * @param type The type to set.
-	 */
-	public void setType(String type) {
-		this.type = type;
+	
+	@Override
+	public void setFileName(String fileName) {
+		this.fileName = fileName;
 	}
-
-	/**
-	 * @return Returns the type.
-	 */
+	
+	@Override
 	public String getType() {
 		return type;
 	}
-
-	/**
-	 * @param length The length to set.
-	 */
-	public void setLength(long length) {
-		this.length = length;
+	
+	@Override
+	public void setType(String type) {
+		this.type = type;
 	}
-
-	/**
-	 * @return Returns the length.
-	 */
-	public long getLength() {
+	
+	@Override
+	public Long getLength() {
 		return length;
 	}
-
-	/**
-	 * @param externalUrl The externalUrl to set.
-	 */
-	public void setExternalUrl(String externalUrl) {
-		this.externalUrl = externalUrl;
+	
+	@Override
+	public void setLength(Long length) {
+		this.length = length;
 	}
-
-	/**
-	 * @return Returns the externalUrl.
-	 */
+	
+	@Override
 	public String getExternalUrl() {
 		return externalUrl;
 	}
+	
+	@Override
+	public void setExternalUrl(String externalUrl) {
+		this.externalUrl = externalUrl;
+	}
+	
 }
diff --git a/src/main/java/org/olat/modules/webFeed/model/FeedImpl.java b/src/main/java/org/olat/modules/webFeed/model/FeedImpl.java
new file mode 100644
index 00000000000..e2a95a66894
--- /dev/null
+++ b/src/main/java/org/olat/modules/webFeed/model/FeedImpl.java
@@ -0,0 +1,276 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <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 the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <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>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+
+package org.olat.modules.webFeed.model;
+
+import java.io.Serializable;
+import java.util.Date;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+import javax.persistence.Transient;
+
+import org.olat.core.id.OLATResourceable;
+import org.olat.modules.webFeed.Feed;
+
+/**
+ *
+ * Initial date: 02.05.2017<br>
+ * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
+ *
+ */
+@Entity(name="feed")
+@Table(name="o_feed")
+@NamedQueries({
+	@NamedQuery(name="loadFeedByRessourceable",
+		query="select data from feed data where f_resourceable_id=:key and f_resourceable_type=:name")
+})
+public class FeedImpl implements Feed, Serializable {
+
+	private static final long serialVersionUID = 6005283969959964489L;
+	
+	@Id
+	@GeneratedValue(strategy = GenerationType.IDENTITY)
+	@Column(name="id", nullable=false, unique=true, insertable=false, updatable=false)
+	private Long key;
+	
+	@Temporal(TemporalType.TIMESTAMP)
+	@Column(name="creationdate", nullable=false, insertable=true, updatable=false)
+	private Date creationDate;
+	@Temporal(TemporalType.TIMESTAMP)
+	@Column(name="lastmodified", nullable=false, insertable=true, updatable=true)
+	private Date lastModified;
+	
+	@Column(name="f_resourceable_id", nullable=true, insertable=true, updatable=true)
+	private Long resourceableId;
+	@Column(name="f_resourceable_type", nullable=true, insertable=true, updatable=true)
+	private String resourceableType;
+	
+	@Column(name="f_title", nullable=true, insertable=true, updatable=true)
+	private String title;
+	@Column(name="f_description", nullable=true, insertable=true, updatable=true)
+	private String description;
+	@Column(name="f_author", nullable=true, insertable=true, updatable=true)
+	private String author;
+	@Column(name="f_image_name", nullable=true, insertable=true, updatable=true)
+	private String imageName;
+	@Column(name="f_external_feed_url", nullable=true, insertable=true, updatable=true)
+	private String externalFeedURL;
+	@Column(name="f_external_image_url", nullable=true, insertable=true, updatable=true)
+	private String externalImageURL;
+	
+	/**
+	 * A feed can either be internal, external or unspecified.
+	 * Internal means that items are created within OLAT.
+	 * External implies that the feed is read from an external URL.
+	 * In some situations the state can't be determined:
+	 * - it has just been created
+	 * - all items have been removed
+	 * - the feed url of an external feed has been set empty
+	 */
+	@Column(name="f_external", nullable=true, insertable=true, updatable=true)
+	private Boolean isExternal;
+
+	// Data model versioning
+	public static final int CURRENT_MODEL_VERSION = 3;
+	// Default is that model version is set to the initial value 1. This is 
+	// necessary to detect models previous to the introduction of this model
+	// version flag which was with version 2.
+	// Save it in the XML file but not in the database.
+	@Transient
+	private int modelVersion = 0; 
+
+	public FeedImpl(OLATResourceable ores) {
+		this.resourceableId = ores.getResourceableId();
+		this.resourceableType = ores.getResourceableTypeName();
+
+		// new model constructor, set to current version
+		this.modelVersion = CURRENT_MODEL_VERSION;
+	}
+	
+	private FeedImpl() {
+		// make Hibernate happy
+	}
+	
+	@Override
+	public Long getKey() {
+		return key;
+	}
+
+	@Override
+	public void setKey(Long key) {
+		this.key = key;
+	}
+
+	@Override
+	public Long getResourceableId() {
+		return resourceableId;
+	}
+
+	@Override
+	public void setResourceableId(Long resourceableId) {
+		this.resourceableId = resourceableId;
+	}
+
+	@Override
+	public String getResourceableTypeName() {
+		return resourceableType;
+	}
+
+	public void setResourceableType(String resourceableType) {
+		this.resourceableType = resourceableType;
+	}
+
+	@Override
+	public Date getCreationDate() {
+		return creationDate;
+	}
+
+	@Override
+	public void setCreationDate(Date creationDate) {
+		this.creationDate = creationDate;
+	}
+
+	@Override
+	public Date getLastModified() {
+		return lastModified;
+	}
+
+	@Override
+	public void setLastModified(Date lastModified) {
+		this.lastModified = lastModified;
+	}
+
+	@Override
+	public String getTitle() {
+		return title;
+	}
+
+	@Override
+	public void setTitle(String title) {
+		this.title = title;
+	}
+
+	@Override
+	public String getDescription() {
+		return description;
+	}
+
+	@Override
+	public void setDescription(String description) {
+		this.description = description;
+	}
+
+	@Override
+	public String getAuthor() {
+		return author;
+	}
+
+	@Override
+	public void setAuthor(String author) {
+		this.author = author;
+	}
+
+	@Override
+	public String getImageName() {
+		return imageName;
+	}
+
+	@Override
+	public void setImageName(String name) {
+		this.imageName = name;
+	}
+
+	@Override
+	public String getExternalImageURL() {
+		return externalImageURL;
+	}
+
+	@Override
+	public void setExternalImageURL(String externalImageURL) {
+		this.externalImageURL = externalImageURL;
+	}
+
+	@Override
+	public String getExternalFeedUrl() {
+		return externalFeedURL;
+	}
+
+	@Override
+	public void setExternalFeedUrl(String externalFeedURL) {
+		this.externalFeedURL = externalFeedURL;
+	}
+
+	@Override
+	public Boolean getExternal() {
+		return isExternal;
+	}
+
+	@Override
+	public void setExternal(Boolean isExternal) {
+		this.isExternal = isExternal;
+	}
+
+	@Override
+	public boolean isExternal() {
+		return isExternal != null && isExternal.booleanValue();
+	}
+
+	@Override
+	public boolean isInternal() {
+		return isExternal != null && !this.isExternal.booleanValue();
+	}
+
+	@Override
+	public boolean isUndefined() {
+		return isExternal == null;
+	}
+
+	@Override
+	public void setModelVersion(int modelVersion) {
+		this.modelVersion = modelVersion;
+	}
+
+	@Override
+	public int getModelVersion() {
+		return modelVersion;
+	}
+	
+	/**
+	 * Overwrite equals method so that different object that actually
+	 * represent the same item are recognized as such.
+	 */
+	@Override
+	public boolean equals(Object obj) {
+		if (!(obj instanceof FeedImpl)) return false;
+		FeedImpl otherFeed = (FeedImpl) obj;
+		return this.getResourceableId().equals(otherFeed.getResourceableId()) &&
+				this.getResourceableTypeName().equals(otherFeed.getResourceableTypeName());
+	}
+	
+}
diff --git a/src/main/java/org/olat/modules/webFeed/model/ItemImpl.java b/src/main/java/org/olat/modules/webFeed/model/ItemImpl.java
new file mode 100644
index 00000000000..e769d14d409
--- /dev/null
+++ b/src/main/java/org/olat/modules/webFeed/model/ItemImpl.java
@@ -0,0 +1,387 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <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 the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <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>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.webFeed.model;
+
+import java.io.Serializable;
+import java.util.Date;
+
+import javax.persistence.AttributeOverride;
+import javax.persistence.AttributeOverrides;
+import javax.persistence.Column;
+import javax.persistence.Embedded;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+
+import org.hibernate.annotations.Target;
+import org.olat.core.gui.components.form.flexible.elements.FileElement;
+import org.olat.core.util.StringHelper;
+import org.olat.modules.webFeed.Enclosure;
+import org.olat.modules.webFeed.Feed;
+import org.olat.modules.webFeed.Item;
+import org.olat.user.UserManager;
+
+/**
+ * 
+ * Initial date: 02.05.2017<br>
+ * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
+ *
+ */
+@Entity(name="item")
+@Table(name="o_feed_item")
+@NamedQueries({
+	@NamedQuery(name="loadItemByGuid",
+			query="select data from item data where data.feed.key=:feedKey and data.guid=:guid"),
+	@NamedQuery(name="loadItemsByFeed",
+		query="select data from item data where data.feed=:feed"),
+	@NamedQuery(name="loadItemsGuidByFeed",
+		query="select guid from item data where data.feed=:feed"),
+	@NamedQuery(name="removeItem",
+		query="delete from item data where data.key=:key"),
+	@NamedQuery(name="removeItemsForFeed",
+		query="delete from item data where data.feed.key=:feedKey")
+})
+public class ItemImpl implements Item, Serializable {
+
+	private static final long serialVersionUID = 4504634251127072211L;
+	
+	@Id
+	@GeneratedValue(strategy = GenerationType.IDENTITY)
+	@Column(name="id", nullable=false, unique=true, insertable=false, updatable=false)
+	private Long key;
+	
+	@ManyToOne(targetEntity=FeedImpl.class, fetch=FetchType.EAGER)
+	@JoinColumn(name="fk_feed_id", nullable=false, insertable=true, updatable=false)
+	private Feed feed;
+	
+	@Temporal(TemporalType.TIMESTAMP)
+	@Column(name="creationdate", nullable=false, insertable=true, updatable=false)
+	private Date creationDate;
+	@Temporal(TemporalType.TIMESTAMP)
+	@Column(name="lastmodified", nullable=false, insertable=true, updatable=true)
+	private Date lastModified;
+	
+	@Column(name="f_title", nullable=true, insertable=true, updatable=true)
+	private String title;
+	@Column(name="f_description", nullable=true, insertable=true, updatable=true)
+	private String description;
+	@Column(name="f_content", nullable=true, insertable=true, updatable=true)
+	private String content;
+	
+	@Column(name="fk_identity_author_id", nullable=true, insertable=true, updatable=false)
+	private Long authorKey;
+	@Column(name="fk_identity_modified_id", nullable=true, insertable=true, updatable=true)
+	private Long modifierKey;
+	@Column(name="f_author", nullable=true, insertable=true, updatable=true)
+	private String author;
+	
+	@Column(name="f_guid", nullable=true, insertable=true, updatable=true)
+	private String guid;
+	@Column(name="f_external_link", nullable=true, insertable=true, updatable=true)
+	private String externalLink;
+	
+	@Column(name="f_draft", nullable=true, insertable=true, updatable=true)
+	private boolean draft = false;
+	@Temporal(TemporalType.TIMESTAMP)
+	@Column(name="f_publish_date", nullable=true, insertable=true, updatable=true)
+	private Date publishDate;
+	
+	private transient FileElement mediaFile;
+	@Embedded
+	@Target(EnclosureImpl.class)
+    @AttributeOverrides( {
+    	@AttributeOverride(name="fileName", column = @Column(name="f_filename") ),
+    	@AttributeOverride(name="type", column = @Column(name="f_type") ),
+    	@AttributeOverride(name="length", column = @Column(name="f_length") ),
+    	@AttributeOverride(name="externalUrl", column = @Column(name="f_external_url") )
+    })
+	private Enclosure enclosure;
+	@Column(name="f_width", nullable=true, insertable=true, updatable=true)
+	private Integer width;
+	@Column(name="f_height", nullable=true, insertable=true, updatable=true)
+	private Integer	height;
+
+	public ItemImpl(Feed feed) {
+		this.feed = feed;
+	}
+	
+	private ItemImpl() {
+		// make Hibernate happy
+	}
+
+	@Override
+	public Long getKey() {
+		return key;
+	}
+
+	@Override
+	public void setKey(Long key) {
+		this.key = key;
+	}
+
+	@Override
+	public Date getCreationDate() {
+		return creationDate;
+	}
+
+	@Override
+	public void setCreationDate(Date creationDate) {
+		this.creationDate = creationDate;
+	}
+
+	@Override
+	public Date getLastModified() {
+		return lastModified;
+	}
+
+	@Override
+	public void setLastModified(Date lastModified) {
+		this.lastModified = lastModified;
+	}
+
+	@Override
+	public String getTitle() {
+		return title;
+	}
+
+	@Override
+	public void setTitle(String title) {
+		this.title = title;
+	}
+
+	@Override
+	public String getDescription() {
+		return description;
+	}
+
+	@Override
+	public void setDescription(String description) {
+		this.description = description;
+	}
+
+	@Override
+	public String getContent() {
+		return content;
+	}
+
+	@Override
+	public void setContent(String content) {
+		this.content = content;
+	}
+	
+	@Override
+	public boolean isAuthorFallbackSet() {
+		return StringHelper.containsNonWhitespace(author);
+	}
+
+	@Override
+	public Long getAuthorKey() {
+		return authorKey;
+	}
+
+	@Override
+	public void setAuthorKey(Long identityKey) {
+		this.authorKey = identityKey;
+	}
+
+	@Override
+	public String getAuthor() {
+		String authorName = null;
+		if(authorKey != null) {
+			authorName = UserManager.getInstance().getUserDisplayName(authorKey);
+		}
+		if (authorName == null && StringHelper.containsNonWhitespace(author)) {
+			authorName = author;
+		}
+		return authorName;
+	}
+
+	@Override
+	public void setAuthor(String author) {
+		this.author = author;
+	}
+
+	@Override
+	public Long getModifierKey() {
+		return modifierKey;
+	}
+	
+	@Override
+	public void setModifierKey(Long modifierKey) {
+		this.modifierKey = modifierKey;
+	}
+	
+	@Override
+	public String getModifier() {
+		String modifierName = null;
+		if(modifierKey != null) {
+			modifierName = UserManager.getInstance().getUserDisplayName(modifierKey);
+		}
+		return modifierName;
+	}
+
+	@Override
+	public String getGuid() {
+		return guid;
+	}
+
+	@Override
+	public void setGuid(String guid) {
+		this.guid = guid;
+	}
+
+	@Override
+	public Date getPublishDate() {
+		return publishDate;
+	}
+
+	@Override
+	public void setPublishDate(Date publishDate) {
+		this.publishDate = publishDate;
+	}
+
+	@Override
+	public void setDraft(boolean draft) {
+		this.draft = draft;
+	}
+
+	@Override
+	public boolean isDraft() {
+		return draft;
+	}
+
+	@Override
+	public boolean isScheduled() {
+		Date now = new Date();
+		return !draft && publishDate != null && now.before(publishDate);
+	}
+
+	@Override
+	public boolean isPublished() {
+		Date now = new Date();
+		return !draft && publishDate != null && now.after(publishDate);
+	}
+
+	@Override
+	public void setExternalLink(String externalLink) {
+		this.externalLink = externalLink;
+	}
+
+	@Override
+	public String getExternalLink() {
+		return externalLink;
+	}
+
+	@Override
+	public Enclosure getEnclosure() {
+		return enclosure;
+	}
+
+	@Override
+	public void setEnclosure(Enclosure enclosure) {
+		this.enclosure = enclosure;
+	}
+
+	@Override
+	public void setMediaFile(FileElement mediaFile) {
+		this.mediaFile = mediaFile;
+	}
+
+	@Override
+	public FileElement getMediaFile() {
+		return mediaFile;
+	}
+
+	@Override
+	public Integer getWidth() {
+		return width;
+	}
+
+	@Override
+	public void setWidth(Integer width) {
+		this.width = width;
+	}
+
+	@Override
+	public Integer getHeight() {
+		return height;
+	}
+
+	@Override
+	public void setHeight(Integer height) {
+		this.height = height;
+	}
+
+	@Override
+	public Feed getFeed() {
+		return feed;
+	}
+
+	public void setFeed(Feed feed) {
+		this.feed = feed;
+	}
+
+	@Override
+	public String extraCSSClass() {
+		String css = null;
+		if (isDraft()) {
+			css = "o_draft";
+		} else if (isScheduled()) {
+			css = "o_scheduled";
+		}
+		return css;
+	}
+
+	@Override
+	public Date getDate() {
+		return publishDate;
+	}
+	
+	@Override
+	public int hashCode() {
+		return guid == null ? 39745 : guid.hashCode();
+	}
+	
+	/**
+	 * Overwrite equals method so that different object that actually
+	 * represent the same item are recognized as such,
+	 * e.g. in the remove method of the feed.
+	 */
+	@Override
+	public boolean equals(Object obj) {
+		if(this == obj) {
+			return true;
+		} else if(obj instanceof ItemImpl) {
+			ItemImpl item = (ItemImpl)obj;
+			return guid != null && guid.equals(item.guid);
+		}
+		return false;
+	}
+
+}
diff --git a/src/main/java/org/olat/modules/webFeed/models/ItemPublishDateComparator.java b/src/main/java/org/olat/modules/webFeed/model/ItemPublishDateComparator.java
similarity index 93%
rename from src/main/java/org/olat/modules/webFeed/models/ItemPublishDateComparator.java
rename to src/main/java/org/olat/modules/webFeed/model/ItemPublishDateComparator.java
index 960ed6989b9..562654f6a85 100644
--- a/src/main/java/org/olat/modules/webFeed/models/ItemPublishDateComparator.java
+++ b/src/main/java/org/olat/modules/webFeed/model/ItemPublishDateComparator.java
@@ -17,11 +17,13 @@
  * frentix GmbH, http://www.frentix.com
  * <p>
  */
-package org.olat.modules.webFeed.models;
+package org.olat.modules.webFeed.model;
 
 import java.util.Comparator;
 import java.util.Date;
 
+import org.olat.modules.webFeed.Item;
+
 /**
  * Compares the publish date of two items.
  * <P>
@@ -34,6 +36,7 @@ public class ItemPublishDateComparator implements Comparator<Item> {
 	/**
 	 * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
 	 */
+	@Override
 	public int compare(Item a, Item b) {
 		// reverse chronological order
 		Date d1 = a.getPublishDate();
diff --git a/src/main/java/org/olat/modules/webFeed/models/package.html b/src/main/java/org/olat/modules/webFeed/model/package.html
similarity index 100%
rename from src/main/java/org/olat/modules/webFeed/models/package.html
rename to src/main/java/org/olat/modules/webFeed/model/package.html
diff --git a/src/main/java/org/olat/modules/webFeed/models/Feed.java b/src/main/java/org/olat/modules/webFeed/models/Feed.java
deleted file mode 100644
index 3e5f543b466..00000000000
--- a/src/main/java/org/olat/modules/webFeed/models/Feed.java
+++ /dev/null
@@ -1,447 +0,0 @@
-/**
- * <a href="http://www.openolat.org">
- * OpenOLAT - Online Learning and Training</a><br>
- * <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 the
- * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
- * <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>
- * Initial code contributed and copyrighted by<br>
- * frentix GmbH, http://www.frentix.com
- * <p>
- */
-package org.olat.modules.webFeed.models;
-
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Date;
-import java.util.List;
-
-import org.olat.basesecurity.BaseSecurityManager;
-import org.olat.core.id.Identity;
-import org.olat.core.id.OLATResourceable;
-import org.olat.core.id.Roles;
-import org.olat.core.util.resource.OresHelper;
-import org.olat.modules.webFeed.FeedSecurityCallback;
-
-/**
- * This is an OLAT feed (or web/news feed) model. It stores all necessary
- * information of a feed including items. Implements Serializable for caching.
- * 
- * <P>
- * Initial Date: Feb 16, 2009 <br>
- * 
- * @author Gregor Wassmann
- */
-public class Feed implements OLATResourceable, Serializable {
-	// Identification
-	private Long id;
-	private String type;
-	// Properties
-	private String title;
-	private String description;
-	private String author;
-	private String imageName;
-	private String externalFeedURL;
-	private String externalImageURL;
-	private Date lastModified;
-	// String copyright;
-	// String language;
-	
-	// Data model versioning
-	public static final int CURRENT_MODEL_VERSION = 2;
-	// Default is that model version is set to the initial value 1. This is 
-	// necessary to detect models previous to the introduction of this model
-	// version flag which was with version 2.
-	private int modelVersion = 0; 
-
-	// A feed can either be internal, external or unspecified.
-	// Internal means that items are created within OLAT. External implies
-	// that the feed is read from an external URL.
-	private Boolean isExternal;
-
-	// The items (saved separately from feed)
-	// @XStreamOmitField : didn't work
-	transient private List<Item> items = new ArrayList<Item>();
-
-	// This list enables us to save items separately and refer to them by id.
-	private List<String> itemIds = new ArrayList<String>();
-
-	/**
-	 * Constructor
-	 * 
-	 * @param resource
-	 */
-	public Feed(OLATResourceable resource) {
-		this.id = resource.getResourceableId();
-		this.type = resource.getResourceableTypeName();
-		// new model constructor, set to current version
-		this.modelVersion = CURRENT_MODEL_VERSION;
-	}
-	
-	public OLATResourceable getResource() {
-		return OresHelper.createOLATResourceableInstanceWithoutCheck(type, id);
-	}
-
-	/**
-	 * Setter for title
-	 * 
-	 * @param title
-	 */
-	public void setTitle(String title) {
-		this.title = title;
-	}
-
-	/**
-	 * Setter for description
-	 * 
-	 * @param description
-	 */
-	public void setDescription(String description) {
-		this.description = description;
-	}
-
-	/**
-	 * Setter for name
-	 * 
-	 * @param name
-	 */
-	public void setImageName(String name) {
-		this.imageName = name;
-	}
-
-	/**
-	 * Getter for title
-	 * 
-	 * @return title
-	 */
-	public String getTitle() {
-		return title;
-	}
-
-	/**
-	 * Getter for description
-	 * 
-	 * @return description
-	 */
-	public String getDescription() {
-		return description;
-	}
-
-	/**
-	 * Getter for imageName
-	 * 
-	 * @return imageName
-	 */
-	public String getImageName() {
-		return imageName;
-	}
-
-	/**
-	 * @param id The id to set.
-	 */
-	public void setId(Long id) {
-		this.id = id;
-	}
-
-	/**
-	 * @return The id
-	 */
-	public Long getId() {
-		return getResourceableId();
-	}
-
-	/**
-	 * @return <tt>true</tt> if the current object is an external feed.
-	 */
-	public boolean isExternal() {
-		return isExternal != null && isExternal.booleanValue();
-	}
-
-	/**
-	 * @return <tt>true</tt> if the current object is an internal feed.
-	 */
-	public boolean isInternal() {
-		return isExternal != null && !this.isExternal.booleanValue();
-	}
-
-	/**
-	 * @return <tt>true</tt> if the current object is an external feed.
-	 */
-	public boolean isUndefined() {
-		return isExternal == null;
-	}
-
-	/**
-	 * @param isExternal The isExternal to set. (Valid argument values are true,
-	 *          false (their corresponding boolean object) or null)
-	 */
-	public void setExternal(Boolean isExternal) {
-		// Clear all items
-		items.clear();
-		// Initializes item id's store
-		if (isExternal == null) {
-			// undefined state
-			this.itemIds = null;
-		} else if (isExternal.booleanValue()){
-			// external feed
-			this.itemIds = null;
-		} else {
-			this.itemIds = new ArrayList<String>();				
-		}
-		// Set new state
-		this.isExternal = isExternal;
-	}
-
-	/**
-	 * @return All items
-	 */
-	public List<Item> getItems() {
-		return items;
-	}
-	
-	/**
-	 * Return a copy of the list of items, but the items
-	 * are not copied. Use this method to mitigate concurrent
-	 * modifications issues.
-	 * 
-	 * @return
-	 */
-	public List<Item> getCopiedListOfItems() {
-		return items == null ? null : new ArrayList<>(items);
-	}
-
-	/**
-	 * @param identity
-	 * @return The filtered Items
-	 */
-	public List<Item> getFilteredItems(FeedSecurityCallback callback, Identity identity) {
-		final Roles roles = BaseSecurityManager.getInstance().getRoles(identity);
-		if (roles != null) {
-			boolean admin = roles.isOLATAdmin();
-			if (admin || isExternal()) {
-				// An admin can see all items and everybody can see all items of
-				// external feeds
-				return items;
-			}
-		}
-		List<Item> filteredItems = new ArrayList<Item>();
-		for (Item item : items) {
-			if (item.isPublished()) {
-				// everybody can see published items
-				filteredItems.add(item);
-			} else if (item.isScheduled() && callback.mayEditItems()) {
-				// scheduled items can be seen by everybody who can edit items
-				// (moderators)
-				filteredItems.add(item);
-			} else if (identity.getKey() == item.getAuthorKey()) {
-				// scheduled items and drafts of oneself are shown
-				filteredItems.add(item);
-			} else if (item.isDraft()) {
-				if(callback.mayViewAllDrafts()) {
-					filteredItems.add(item);
-				} else if (identity.getKey() == item.getModifierKey()) {
-					filteredItems.add(item);
-				}
-			}
-		}
-		return filteredItems;
-	}
-
-	/**
-	 * @return A list of all published items
-	 */
-	public List<Item> getPublishedItems() {
-		List<Item> publishedItems = new ArrayList<Item>();
-		for (Item item : items) {
-			if (item.isPublished()) {
-				publishedItems.add(item);
-			}
-		}
-		return publishedItems;
-	}
-
-	/**
-	 * Sorts the items by publish date in reverse chronological order
-	 */
-	public void sortItems() {
-		Collections.sort(items, new ItemPublishDateComparator());
-		// reset the itemIds
-		setItems(items);
-	}
-
-	/**
-	 * @return All items in an array
-	 */
-	public Item[] getItemsArray() {
-		Item[] itemsArray = null;
-		if (items != null) {
-			int size = items.size();
-			itemsArray = new Item[size];
-			for (int i = 0; i < size; i++) {
-				itemsArray[i] = items.get(i);
-			}
-		}
-		return itemsArray;
-	}
-
-	/**
-	 * @return All item ids
-	 */
-	public List<String> getItemIds() {
-		return itemIds;
-	}
-
-	/**
-	 * Add an item to the feed.<br>
-	 * 
-	 * @param item
-	 */
-	public void add(Item item) {
-		items.add(0, item);
-		if (isInternal()) {
-			itemIds.add(0, item.getGuid());
-		}
-	}
-
-	/**
-	 * Remove an item from the feed
-	 * 
-	 * @param item
-	 * @return <tt>true</tt> if this list contained the specified element.
-	 */
-	public boolean remove(Item item) {
-		itemIds.remove(item.getGuid());
-		// Remove below works also when object identity is not the same as
-		// item.equals has been overwritten to match on the GUID
-		return items.remove(item);
-	}
-
-	/**
-	 * @param items
-	 */
-	public void setItems(List<Item> items) {
-		this.items = items;
-		this.itemIds = new ArrayList<String>(items.size());
-		for (Item item : items) {
-			itemIds.add(item.getGuid());
-		}
-	}
-
-	/**
-	 * @param author The author to set.
-	 */
-	public void setAuthor(String author) {
-		this.author = author;
-	}
-
-	/**
-	 * @return Returns the author.
-	 */
-	public String getAuthor() {
-		return author;
-	}
-
-	/**
-	 * @param feedUrl The external feed URL to set.
-	 */
-	public void setExternalFeedUrl(String feedUrl) {
-		this.externalFeedURL = feedUrl;
-	}
-
-	/**
-	 * @return Returns the externalFeedURL.
-	 */
-	public String getExternalFeedUrl() {
-		return externalFeedURL;
-	}
-
-	/**
-	 * @param externalImageURL The externalImageURL to set.
-	 */
-	public void setExternalImageURL(String externalImageURL) {
-		this.externalImageURL = externalImageURL;
-	}
-
-	/**
-	 * @return Returns the externalImageURL.
-	 */
-	public String getExternalImageURL() {
-		return externalImageURL;
-	}
-
-	/**
-	 * @return True if there are items
-	 */
-	public boolean hasItems() {
-		boolean hasItems = false;
-		if (items != null && items.size() > 0) {
-			hasItems = true;
-		}
-		return hasItems;
-	}
-
-	/**
-	 * @param lastModified The lastModified to set.
-	 */
-	public void setLastModified(Date lastModified) {
-		this.lastModified = lastModified;
-	}
-
-	/**
-	 * @return Returns the lastModified.
-	 */
-	public Date getLastModified() {
-		return lastModified;
-	}
-
-	/**
-	 * @see org.olat.core.id.OLATResourceable#getResourceableId()
-	 */
-	public Long getResourceableId() {
-		return id;
-	}
-
-	/**
-	 * @see org.olat.core.id.OLATResourceable#getResourceableTypeName()
-	 */
-	public String getResourceableTypeName() {
-		return type;
-	}
-
-	/**
-	 * Set the resourcable type name. (Sometimes needed on reload, just to make
-	 * sure.)
-	 * 
-	 * @param type The resourcable type name
-	 */
-	public void setType(String type) {
-		this.type = type;
-	}
-
-	/**
-	 * Set the model version to the specific value;
-	 * @param modelVersion
-	 */
-	public void setModelVersion(int modelVersion) {
-		this.modelVersion = modelVersion;
-	}
-
-	/**
-	 * Get the version of the datamodel
-	 * 
-	 * @return
-	 */
-	public int getModelVersion() {
-		return modelVersion;
-	}
-	
-}
diff --git a/src/main/java/org/olat/modules/webFeed/models/Item.java b/src/main/java/org/olat/modules/webFeed/models/Item.java
deleted file mode 100644
index 4b3cf52fc62..00000000000
--- a/src/main/java/org/olat/modules/webFeed/models/Item.java
+++ /dev/null
@@ -1,344 +0,0 @@
-/**
- * <a href="http://www.openolat.org">
- * OpenOLAT - Online Learning and Training</a><br>
- * <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 the
- * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
- * <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>
- * Initial code contributed and copyrighted by<br>
- * frentix GmbH, http://www.frentix.com
- * <p>
- */
-package org.olat.modules.webFeed.models;
-
-import java.io.Serializable;
-import java.util.Date;
-
-import org.olat.core.commons.controllers.navigation.Dated;
-import org.olat.core.gui.components.form.flexible.elements.FileElement;
-import org.olat.core.util.StringHelper;
-import org.olat.user.UserManager;
-
-/**
- * This is the feed item class. A feed has many items. Implements Serializable
- * for caching, Dated for ordering.
- * 
- * <P>
- * Initial Date: Feb 16, 2009 <br>
- * 
- * @authorKey Gregor Wassmann
- */
-public class Item implements Serializable, Dated {
-
-	private static final long serialVersionUID = 8856863396847065934L;
-	private String title;
-	private String description, content;
-	// The authorKey corresponds to the olat identity key of the user that created
-	// this item.
-	private long authorKey;
-	private long modifierKey;
-	// author is used for external sources.
-	private String author;
-	private String modifier;
-	private String guid; // The global unique identifier
-	private String externalLink;
-	private Date lastModified;
-	private Date publishDate;
-	private Enclosure enclosure;
-	private int width, height;	//fxdiff FXOLAT-118: size for video podcast
-	private transient FileElement mediaFile;
-	// An item can either be in draft version or it is published
-	// -> 'not draft' is equivalent to 'published'
-	private boolean draft = false;
-	
-	public Item(){
-		//
-	}
-
-	/**
-	 * @param title The title to set.
-	 */
-	public void setTitle(String title) {
-		this.title = title;
-	}
-
-	/**
-	 * @return Returns the title.
-	 */
-	public String getTitle() {
-		return title;
-	}
-
-	/**
-	 * @param description The description to set.
-	 */
-	public void setDescription(String description) {
-		this.description = description;
-	}
-
-	/**
-	 * @return Returns the description.
-	 */
-	public String getDescription() {
-		return description;
-	}
-
-	/**
-	 * @param content The content to set.
-	 */
-	public void setContent(String content) {
-		this.content = content;
-	}
-	
-	/**
-	 * @return Returns the content.
-	 */
-	public String getContent() {
-		return content;
-	}
-	public boolean isAuthorFallbackSet() {
-		return StringHelper.containsNonWhitespace(author);
-	}
-
-	/**
-	 * @param authorKey The authorKey to set. Used for OLAT-made feeds.
-	 */
-	public void setAuthorKey(long identityKey) {
-		this.authorKey = identityKey;
-	}
-
-	/**
-	 * @return Returns the authorKey
-	 */
-	public long getAuthorKey() {
-		return authorKey;
-	}
-
-	/**
-	 * @param author The author to set. Used for external feeds.
-	 */
-	public void setAuthor(String author) {
-		this.author = author;
-	}
-
-	/**
-	 * @return The name of the author
-	 */
-	public String getAuthor() {
-		String authorName = null;
-		if(authorKey > 0) {
-			authorName = UserManager.getInstance().getUserDisplayName(authorKey);
-		}
-		if (authorName == null && StringHelper.containsNonWhitespace(author)) {
-			authorName = author;
-		}
-		return authorName;
-	}
-	
-	/**
-	 * @return the key of the modifier
-	 */
-	public long getModifierKey() {
-		return modifierKey;
-	}
-	
-	public void setModifierKey(long modifierKey) {
-		this.modifierKey = modifierKey;
-	}
-	
-	/**
-	 * @return The name of the modifier
-	 */
-	public String getModifier() {
-		String modifierName = null;
-		if(modifierKey > 0) {
-			modifierName = UserManager.getInstance().getUserDisplayName(modifierKey);
-		}
-		if (modifierName == null && StringHelper.containsNonWhitespace(modifier)) {
-			modifierName = modifier;
-		}
-		return modifierName;
-	}
-	
-	public void setModifier(String modifier) {
-		this.modifier = modifier;
-	}
-
-	/**
-	 * @param guid The guid to set.
-	 */
-	public void setGuid(String guid) {
-		this.guid = guid;
-	}
-
-	/**
-	 * @return Returns the guid.
-	 */
-	public String getGuid() {
-		return guid;
-	}
-
-	/**
-	 * @param enclosure The enclosure to set.
-	 */
-	public void setEnclosure(Enclosure enclosure) {
-		this.enclosure = enclosure;
-	}
-
-	/**
-	 * @return Returns the enclosure.
-	 */
-	public Enclosure getEnclosure() {
-		return enclosure;
-	}
-
-	/**
-	 * @param lastModified The lastModified to set.
-	 */
-	public void setLastModified(Date lastModified) {
-		this.lastModified = lastModified;
-	}
-
-	/**
-	 * @return Returns the lastModified.
-	 */
-	public Date getLastModified() {
-		return lastModified;
-	}
-
-	/**
-	 * @param publishDate The publishDate to set.
-	 */
-	public void setPublishDate(Date publishDate) {
-		this.publishDate = publishDate;
-	}
-
-	/**
-	 * @return Returns the publishDate.
-	 */
-	public Date getPublishDate() {
-		return publishDate;
-	}
-
-	/**
-	 * @param externalLink The externalLink to set.
-	 */
-	public void setExternalLink(String externalLink) {
-		this.externalLink = externalLink;
-	}
-
-	/**
-	 * @return Returns the externalLink.
-	 */
-	public String getExternalLink() {
-		return externalLink;
-	}
-
-	/**
-	 * @param mediaFile The mediaFile to set.
-	 */
-	public void setMediaFile(FileElement mediaFile) {
-		this.mediaFile = mediaFile;
-	}
-
-	/**
-	 * @return Returns the mediaFile.
-	 */
-	public FileElement getMediaFile() {
-		return mediaFile;
-	}
-	//fxdiff FXOLAT-118: size for video podcast
-	public int getWidth() {
-		return width;
-	}
-
-	public void setWidth(int width) {
-		this.width = width;
-	}
-
-	public int getHeight() {
-		return height;
-	}
-
-	public void setHeight(int height) {
-		this.height = height;
-	}
-
-	/**
-	 * @param draft The draft to set.
-	 */
-	public void setDraft(boolean draft) {
-		this.draft = draft;
-	}
-
-	/**
-	 * @return True if this is a draft
-	 */
-	public boolean isDraft() {
-		return draft;
-	}
-
-	/**
-	 * @return True if this is published
-	 */
-	public boolean isPublished() {
-		Date now = new Date();
-		return !draft && publishDate != null && now.after(publishDate);
-	}
-
-	/**
-	 * @return True if this is publication is scheduled
-	 */
-	public boolean isScheduled() {
-		Date now = new Date();
-		return !draft && publishDate != null && now.before(publishDate);
-	}
-
-	/**
-	 * @return An extra CSS class for drafts and scheduled items
-	 */
-	public String extraCSSClass() {
-		String css = null;
-		if (isDraft()) {
-			css = "o_draft";
-		} else if (isScheduled()) {
-			css = "o_scheduled";
-		}
-		return css;
-	}
-
-	/**
-	 * This date can be null.
-	 * @see org.olat.core.commons.controllers.navigation.Dated#getDate()
-	 */
-	public Date getDate() {
-		return publishDate;
-	}
-	
-	public int hashCode() {
-		return guid == null ? 39745 : guid.hashCode();
-	}
-	
-	/**
-	 * Overwrite equals method so that different object in the vm that actually
-	 * represent the same item are recognized as such. Eg in the remove method of the feed
-	 */
-	public boolean equals(Object obj) {
-		if(this == obj) {
-			return true;
-		} else if(obj instanceof Item) {
-			Item item = (Item)obj;
-			return guid != null && guid.equals(item.guid);
-		}
-		return false;
-		
-	}
-}
diff --git a/src/main/java/org/olat/modules/webFeed/portfolio/BlogArtefactDetailsController.java b/src/main/java/org/olat/modules/webFeed/portfolio/BlogArtefactDetailsController.java
index 4a81ff39157..32b029d71d7 100644
--- a/src/main/java/org/olat/modules/webFeed/portfolio/BlogArtefactDetailsController.java
+++ b/src/main/java/org/olat/modules/webFeed/portfolio/BlogArtefactDetailsController.java
@@ -39,7 +39,8 @@ import org.olat.core.util.vfs.VFSContainer;
 import org.olat.core.util.vfs.VFSContainerMapper;
 import org.olat.core.util.vfs.VFSLeaf;
 import org.olat.core.util.xml.XStreamHelper;
-import org.olat.modules.webFeed.models.Item;
+import org.olat.modules.webFeed.Item;
+import org.olat.modules.webFeed.model.ItemImpl;
 import org.olat.portfolio.manager.EPFrontendManager;
 
 import com.thoughtworks.xstream.XStream;
@@ -72,7 +73,7 @@ public class BlogArtefactDetailsController extends BasicController {
 			InputStream in = itemXml.getInputStream();
 			
 			XStream xstream = XStreamHelper.createXStreamInstance();
-			xstream.alias("item", Item.class);
+			xstream.alias("item", ItemImpl.class);
 			Item item = (Item)xstream.fromXML(in);
 			FileUtils.closeSafely(in);
 			
diff --git a/src/main/java/org/olat/modules/webFeed/portfolio/BlogArtefactHandler.java b/src/main/java/org/olat/modules/webFeed/portfolio/BlogArtefactHandler.java
index bae40b98f8c..4dbab0c691c 100644
--- a/src/main/java/org/olat/modules/webFeed/portfolio/BlogArtefactHandler.java
+++ b/src/main/java/org/olat/modules/webFeed/portfolio/BlogArtefactHandler.java
@@ -37,9 +37,10 @@ import org.olat.core.util.vfs.VFSContainer;
 import org.olat.core.util.vfs.VFSLeaf;
 import org.olat.core.util.xml.XStreamHelper;
 import org.olat.course.nodes.CourseNode;
-import org.olat.modules.webFeed.managers.FeedManager;
-import org.olat.modules.webFeed.models.Feed;
-import org.olat.modules.webFeed.models.Item;
+import org.olat.modules.webFeed.Feed;
+import org.olat.modules.webFeed.Item;
+import org.olat.modules.webFeed.manager.FeedManager;
+import org.olat.modules.webFeed.model.ItemImpl;
 import org.olat.portfolio.EPAbstractHandler;
 import org.olat.portfolio.manager.EPFrontendManager;
 import org.olat.portfolio.model.artefacts.AbstractArtefact;
@@ -61,7 +62,7 @@ import com.thoughtworks.xstream.XStream;
 public class BlogArtefactHandler extends EPAbstractHandler<BlogArtefact> {
 	
 	private static final OLog log = Tracing.createLoggerFor(BlogArtefactHandler.class);
-
+	
 	@Override
 	public String getType() {
 		return BlogArtefact.TYPE;
@@ -82,7 +83,7 @@ public class BlogArtefactHandler extends EPAbstractHandler<BlogArtefact> {
 		if (source instanceof Feed) {
 			feed = (Feed)source;
 			String subPath = getItemUUID(artefact.getBusinessPath());
-			for(Item item:feed.getItems()) {
+			for(Item item:FeedManager.getInstance().loadItems(feed)) {
 				if(subPath.equals(item.getGuid())) {
 					prefillBlogArtefact(artefact, feed, item);
 				}
@@ -115,11 +116,11 @@ public class BlogArtefactHandler extends EPAbstractHandler<BlogArtefact> {
 	}
 
 	private void prefillBlogArtefact(AbstractArtefact artefact, Feed feed, Item item) {
-		VFSContainer itemContainer = FeedManager.getInstance().getItemContainer(item, feed);
+		VFSContainer itemContainer = FeedManager.getInstance().getItemContainer(item);
 		artefact.setFileSourceContainer(itemContainer);
 		artefact.setTitle(item.getTitle());
 		artefact.setDescription(item.getDescription());
-
+		
 		VFSLeaf itemXml = (VFSLeaf)itemContainer.resolve(BlogArtefact.BLOG_FILE_NAME);
 		if(itemXml != null) {
 			InputStream in = itemXml.getInputStream();
@@ -164,15 +165,15 @@ public class BlogArtefactHandler extends EPAbstractHandler<BlogArtefact> {
 		if(content != null) {
 			try {
 				XStream xstream = XStreamHelper.createXStreamInstance();
-				xstream.alias("item", Item.class);
-				Item item = (Item)xstream.fromXML(content);
+				xstream.alias("item", ItemImpl.class);
+				ItemImpl item = (ItemImpl)xstream.fromXML(content);
 				
 				String mapperBaseURL = "";
 				Filter mediaUrlFilter = FilterFactory.getBaseURLToMediaRelativeURLFilter(mapperBaseURL);
 				sb.append(mediaUrlFilter.filter(item.getDescription())).append(" ")
 					.append(mediaUrlFilter.filter(item.getContent()));
 			} catch (Exception e) {
-				log.warn("Cannot read an artefact of type blog while idnexing", e);
+				log.warn("Cannot read an artefact of type blog while indexing", e);
 			}
 		}
 	}
diff --git a/src/main/java/org/olat/modules/webFeed/portfolio/BlogEntryMedia.java b/src/main/java/org/olat/modules/webFeed/portfolio/BlogEntryMedia.java
index a4ee70adb82..8a0ac23a6f0 100644
--- a/src/main/java/org/olat/modules/webFeed/portfolio/BlogEntryMedia.java
+++ b/src/main/java/org/olat/modules/webFeed/portfolio/BlogEntryMedia.java
@@ -19,8 +19,8 @@
  */
 package org.olat.modules.webFeed.portfolio;
 
-import org.olat.modules.webFeed.models.Feed;
-import org.olat.modules.webFeed.models.Item;
+import org.olat.modules.webFeed.Feed;
+import org.olat.modules.webFeed.Item;
 
 /**
  * 
diff --git a/src/main/java/org/olat/modules/webFeed/portfolio/BlogEntryMediaController.java b/src/main/java/org/olat/modules/webFeed/portfolio/BlogEntryMediaController.java
index ad044c7f85b..8999f480a5b 100644
--- a/src/main/java/org/olat/modules/webFeed/portfolio/BlogEntryMediaController.java
+++ b/src/main/java/org/olat/modules/webFeed/portfolio/BlogEntryMediaController.java
@@ -39,7 +39,8 @@ import org.olat.core.util.vfs.VFSLeaf;
 import org.olat.core.util.xml.XStreamHelper;
 import org.olat.modules.portfolio.Media;
 import org.olat.modules.portfolio.manager.PortfolioFileStorage;
-import org.olat.modules.webFeed.models.Item;
+import org.olat.modules.webFeed.Item;
+import org.olat.modules.webFeed.model.ItemImpl;
 import org.springframework.beans.factory.annotation.Autowired;
 
 import com.thoughtworks.xstream.XStream;
@@ -57,7 +58,7 @@ public class BlogEntryMediaController extends BasicController {
 	
 	private static final XStream xstream = XStreamHelper.createXStreamInstance();
 	static {
-		xstream.alias("item", Item.class);
+		xstream.alias("item", ItemImpl.class);
 	}
 	
 	@Autowired
@@ -72,7 +73,7 @@ public class BlogEntryMediaController extends BasicController {
 			if(item instanceof VFSLeaf) {
 				VFSLeaf itemLeaf = (VFSLeaf)item;
 				try(InputStream in = itemLeaf.getInputStream()) {
-					Item blogItem = (Item)xstream.fromXML(in);
+					Item blogItem = (ItemImpl)xstream.fromXML(in);
 					if(blogItem.getDate() != null) {
 						DateComponentFactory.createDateComponentWithYear("dateComp", blogItem.getDate(), mainVC);
 					}
diff --git a/src/main/java/org/olat/modules/webFeed/portfolio/BlogEntryMediaHandler.java b/src/main/java/org/olat/modules/webFeed/portfolio/BlogEntryMediaHandler.java
index 6f32e3537b6..a7c0a32a4e7 100644
--- a/src/main/java/org/olat/modules/webFeed/portfolio/BlogEntryMediaHandler.java
+++ b/src/main/java/org/olat/modules/webFeed/portfolio/BlogEntryMediaHandler.java
@@ -39,9 +39,9 @@ import org.olat.modules.portfolio.handler.AbstractMediaHandler;
 import org.olat.modules.portfolio.manager.MediaDAO;
 import org.olat.modules.portfolio.manager.PortfolioFileStorage;
 import org.olat.modules.portfolio.ui.media.StandardEditMediaController;
-import org.olat.modules.webFeed.managers.FeedManager;
-import org.olat.modules.webFeed.models.Feed;
-import org.olat.modules.webFeed.models.Item;
+import org.olat.modules.webFeed.Feed;
+import org.olat.modules.webFeed.Item;
+import org.olat.modules.webFeed.manager.FeedManager;
 import org.olat.portfolio.manager.EPFrontendManager;
 import org.olat.portfolio.model.artefacts.AbstractArtefact;
 import org.olat.util.logging.activity.LoggingResourceable;
@@ -105,9 +105,11 @@ public class BlogEntryMediaHandler extends AbstractMediaHandler {
 		String storagePath = fileStorage.getRelativePath(mediaDir);
 		media = mediaDao.updateStoragePath(media, storagePath, BlogArtefact.BLOG_FILE_NAME);
 		VFSContainer mediaContainer = fileStorage.getMediaContainer(media);
-		VFSContainer itemContainer = feedManager.getItemContainer(item, feed);
+		VFSContainer itemContainer = feedManager.getItemContainer(item);
+		FeedManager.getInstance().saveItemAsXML(item);
 		VFSManager.copyContent(itemContainer, mediaContainer);
-
+		FeedManager.getInstance().deleteItemXML(item);
+		
 		return media;
 	}
 
diff --git a/src/main/java/org/olat/modules/webFeed/portfolio/EPCreateLiveBlogArtefactStepForm00.java b/src/main/java/org/olat/modules/webFeed/portfolio/EPCreateLiveBlogArtefactStepForm00.java
index bbff58199b2..9f651910e74 100644
--- a/src/main/java/org/olat/modules/webFeed/portfolio/EPCreateLiveBlogArtefactStepForm00.java
+++ b/src/main/java/org/olat/modules/webFeed/portfolio/EPCreateLiveBlogArtefactStepForm00.java
@@ -23,8 +23,8 @@ import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.form.flexible.impl.Form;
 import org.olat.core.gui.control.WindowControl;
 import org.olat.core.gui.control.generic.wizard.StepsRunContext;
-import org.olat.modules.webFeed.managers.FeedManager;
-import org.olat.modules.webFeed.models.Feed;
+import org.olat.modules.webFeed.Feed;
+import org.olat.modules.webFeed.manager.FeedManager;
 import org.olat.portfolio.model.artefacts.AbstractArtefact;
 import org.olat.portfolio.ui.artefacts.collect.EPCollectStepForm00;
 
@@ -62,6 +62,6 @@ public class EPCreateLiveBlogArtefactStepForm00 extends EPCollectStepForm00 {
 		feed.setAuthor(blogArtefact.getAuthor().getName());
 		feed.setTitle(blogArtefact.getTitle());
 		feed.setDescription(blogArtefact.getDescription());
-		FeedManager.getInstance().updateFeedMetadata(feed);
+		FeedManager.getInstance().updateFeed(feed);
 	}
 }
diff --git a/src/main/java/org/olat/modules/webFeed/portfolio/LiveBlogArtefact.java b/src/main/java/org/olat/modules/webFeed/portfolio/LiveBlogArtefact.java
index c8d1d79a46a..a5836fd3f01 100644
--- a/src/main/java/org/olat/modules/webFeed/portfolio/LiveBlogArtefact.java
+++ b/src/main/java/org/olat/modules/webFeed/portfolio/LiveBlogArtefact.java
@@ -21,8 +21,8 @@
 package org.olat.modules.webFeed.portfolio;
 
 import org.olat.fileresource.types.BlogFileResource;
-import org.olat.modules.webFeed.managers.FeedManager;
-import org.olat.modules.webFeed.models.Feed;
+import org.olat.modules.webFeed.Feed;
+import org.olat.modules.webFeed.manager.FeedManager;
 import org.olat.portfolio.model.artefacts.AbstractArtefact;
 import org.olat.resource.OLATResource;
 import org.olat.resource.OLATResourceManager;
@@ -55,7 +55,7 @@ public class LiveBlogArtefact extends AbstractArtefact {
 		String businessPath = getBusinessPath();
 		Long resid = Long.parseLong(businessPath.substring(10, businessPath.length() - 1));
 		OLATResource ores = OLATResourceManager.getInstance().findResourceable(resid, BlogFileResource.TYPE_NAME);
-		return FeedManager.getInstance().getFeed(ores);
+		return FeedManager.getInstance().loadFeed(ores);
 	}
 	
 }
diff --git a/src/main/java/org/olat/modules/webFeed/portfolio/LiveBlogArtefactHandler.java b/src/main/java/org/olat/modules/webFeed/portfolio/LiveBlogArtefactHandler.java
index b0f283c5474..961c1269f1c 100644
--- a/src/main/java/org/olat/modules/webFeed/portfolio/LiveBlogArtefactHandler.java
+++ b/src/main/java/org/olat/modules/webFeed/portfolio/LiveBlogArtefactHandler.java
@@ -20,6 +20,8 @@
 
 package org.olat.modules.webFeed.portfolio;
 
+import java.util.List;
+
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.control.Controller;
 import org.olat.core.gui.control.WindowControl;
@@ -28,11 +30,11 @@ import org.olat.core.util.StringHelper;
 import org.olat.core.util.filter.Filter;
 import org.olat.core.util.resource.OresHelper;
 import org.olat.fileresource.types.BlogFileResource;
+import org.olat.modules.webFeed.Feed;
 import org.olat.modules.webFeed.FeedResourceSecurityCallback;
 import org.olat.modules.webFeed.FeedSecurityCallback;
-import org.olat.modules.webFeed.managers.FeedManager;
-import org.olat.modules.webFeed.models.Feed;
-import org.olat.modules.webFeed.models.Item;
+import org.olat.modules.webFeed.Item;
+import org.olat.modules.webFeed.manager.FeedManager;
 import org.olat.modules.webFeed.search.document.FeedItemDocument;
 import org.olat.modules.webFeed.ui.FeedItemDisplayConfig;
 import org.olat.modules.webFeed.ui.FeedMainController;
@@ -119,9 +121,10 @@ public class LiveBlogArtefactHandler extends EPAbstractHandler<LiveBlogArtefact>
 			manager = FeedManager.getInstance();
 			String oresId = businessPath.substring(LIVEBLOG.length(), businessPath.length() - 1);
 			OLATResourceable ores = OresHelper.createOLATResourceableInstance(BlogFileResource.TYPE_NAME, Long.parseLong(oresId));
-			Feed feed = manager.getFeed(ores);
+			Feed feed = manager.loadFeed(ores);
+			List<Item> publishedItems = manager.loadPublishedItems(feed);
 
-			for (Item item : feed.getPublishedItems()) {
+			for (Item item : publishedItems) {
 				OlatDocument itemDoc = new FeedItemDocument(item, context);
 				String content = itemDoc.getContent();
 				sb.append(content);
diff --git a/src/main/java/org/olat/modules/webFeed/portfolio/LiveBlogContextEntryControllerCreator.java b/src/main/java/org/olat/modules/webFeed/portfolio/LiveBlogContextEntryControllerCreator.java
index a10097a40dd..481bba4748f 100644
--- a/src/main/java/org/olat/modules/webFeed/portfolio/LiveBlogContextEntryControllerCreator.java
+++ b/src/main/java/org/olat/modules/webFeed/portfolio/LiveBlogContextEntryControllerCreator.java
@@ -33,12 +33,14 @@ import org.olat.core.id.context.ContextEntryControllerCreator;
 import org.olat.core.id.context.DefaultContextEntryControllerCreator;
 import org.olat.core.logging.OLog;
 import org.olat.core.logging.Tracing;
+import org.olat.fileresource.types.BlogFileResource;
+import org.olat.modules.webFeed.Feed;
 import org.olat.modules.webFeed.FeedResourceSecurityCallback;
 import org.olat.modules.webFeed.FeedSecurityCallback;
-import org.olat.modules.webFeed.managers.FeedManager;
-import org.olat.modules.webFeed.models.Feed;
+import org.olat.modules.webFeed.manager.FeedManager;
 import org.olat.modules.webFeed.ui.FeedMainController;
 import org.olat.modules.webFeed.ui.blog.BlogUIFactory;
+import org.olat.resource.OLATResourceManager;
 
 /**
  * 
@@ -74,7 +76,8 @@ public class LiveBlogContextEntryControllerCreator  {
 		@Override
 		public Controller createController(List<ContextEntry> ces, UserRequest ureq, WindowControl wControl) {
 			OLATResourceable ores = ces.get(0).getOLATResourceable();
-			Feed feed = feedManager.getFeed(ores);
+			ores = OLATResourceManager.getInstance().findResourceable(ores.getResourceableId(), BlogFileResource.TYPE_NAME);
+			Feed feed = feedManager.loadFeed(ores);
 			boolean isOwner = feed.getAuthor() != null && ureq.getIdentity() != null && feed.getAuthor().equals(ureq.getIdentity().getName());
 			FeedSecurityCallback secCallback = new FeedResourceSecurityCallback(isOwner, isOwner);
 			FeedMainController controller = new FeedMainController(ores, ureq, wControl, BlogUIFactory.getInstance(ureq.getLocale()), secCallback);
@@ -84,7 +87,8 @@ public class LiveBlogContextEntryControllerCreator  {
 		@Override
 		public String getTabName(ContextEntry ce, UserRequest ureq) {
 			OLATResourceable ores = ce.getOLATResourceable();
-			Feed feed = feedManager.getFeed(ores);
+			ores = OLATResourceManager.getInstance().findResourceable(ores.getResourceableId(), BlogFileResource.TYPE_NAME);
+			Feed feed = feedManager.loadFeed(ores);
 			return feed.getTitle();
 		}
 
@@ -92,7 +96,8 @@ public class LiveBlogContextEntryControllerCreator  {
 		public boolean validateContextEntryAndShowError(ContextEntry ce, UserRequest ureq, WindowControl wControl) {
 			try {
 				OLATResourceable ores = ce.getOLATResourceable();
-				Feed feed = feedManager.getFeed(ores);
+				ores = OLATResourceManager.getInstance().findResourceable(ores.getResourceableId(), BlogFileResource.TYPE_NAME);
+				Feed feed = feedManager.loadFeed(ores);
 				return feed != null;
 			} catch (Exception e) {
 				log.warn("Try to load a live blog with an invalid context entry: " + ce, e);
diff --git a/src/main/java/org/olat/modules/webFeed/search/document/FeedItemDocument.java b/src/main/java/org/olat/modules/webFeed/search/document/FeedItemDocument.java
index d485d34c585..b6cbec5e12f 100644
--- a/src/main/java/org/olat/modules/webFeed/search/document/FeedItemDocument.java
+++ b/src/main/java/org/olat/modules/webFeed/search/document/FeedItemDocument.java
@@ -21,7 +21,7 @@ package org.olat.modules.webFeed.search.document;
 
 import org.olat.core.util.filter.Filter;
 import org.olat.core.util.filter.FilterFactory;
-import org.olat.modules.webFeed.models.Item;
+import org.olat.modules.webFeed.Item;
 import org.olat.search.model.OlatDocument;
 import org.olat.search.service.SearchResourceContext;
 
diff --git a/src/main/java/org/olat/modules/webFeed/search/document/FeedNodeDocument.java b/src/main/java/org/olat/modules/webFeed/search/document/FeedNodeDocument.java
index bc9285e2a06..021dc88d92c 100644
--- a/src/main/java/org/olat/modules/webFeed/search/document/FeedNodeDocument.java
+++ b/src/main/java/org/olat/modules/webFeed/search/document/FeedNodeDocument.java
@@ -19,7 +19,7 @@
  */
 package org.olat.modules.webFeed.search.document;
 
-import org.olat.modules.webFeed.models.Feed;
+import org.olat.modules.webFeed.Feed;
 import org.olat.search.model.OlatDocument;
 import org.olat.search.service.SearchResourceContext;
 
diff --git a/src/main/java/org/olat/modules/webFeed/search/indexer/FeedCourseNodeIndexer.java b/src/main/java/org/olat/modules/webFeed/search/indexer/FeedCourseNodeIndexer.java
index 885def74501..24aea3831f7 100644
--- a/src/main/java/org/olat/modules/webFeed/search/indexer/FeedCourseNodeIndexer.java
+++ b/src/main/java/org/olat/modules/webFeed/search/indexer/FeedCourseNodeIndexer.java
@@ -20,15 +20,16 @@
 package org.olat.modules.webFeed.search.indexer;
 
 import java.io.IOException;
+import java.util.List;
 
 import org.apache.lucene.document.Document;
 import org.olat.core.logging.OLog;
 import org.olat.core.logging.Tracing;
 import org.olat.course.ICourse;
 import org.olat.course.nodes.CourseNode;
-import org.olat.modules.webFeed.managers.FeedManager;
-import org.olat.modules.webFeed.models.Feed;
-import org.olat.modules.webFeed.models.Item;
+import org.olat.modules.webFeed.Feed;
+import org.olat.modules.webFeed.Item;
+import org.olat.modules.webFeed.manager.FeedManager;
 import org.olat.modules.webFeed.search.document.FeedItemDocument;
 import org.olat.modules.webFeed.search.document.FeedNodeDocument;
 import org.olat.repository.RepositoryEntry;
@@ -78,14 +79,15 @@ public abstract class FeedCourseNodeIndexer extends DefaultIndexer implements Co
 				if (log.isDebug()) {
 					log.info("Indexing: " + repoEntryName);
 				}
-				Feed feed = FeedManager.getInstance().getFeed(repositoryEntry.getOlatResource());
+				Feed feed = FeedManager.getInstance().loadFeed(repositoryEntry.getOlatResource());
+				List<Item> publishedItems = FeedManager.getInstance().loadPublishedItems(feed);
 
 				// Create the olatDocument for the feed course node itself
 				OlatDocument feedNodeDoc = new FeedNodeDocument(feed, courseNodeResourceContext);
 				indexer.addDocument(feedNodeDoc.getLuceneDocument());
 				
-				// Only index items. Feed itself is indexed by RepositoryEntryIndexer.
-				for (Item item : feed.getPublishedItems()) {
+				// Only index items. FeedImpl itself is indexed by RepositoryEntryIndexer.
+				for (Item item : publishedItems) {
 					OlatDocument itemDoc = new FeedItemDocument(item, courseNodeResourceContext);
 					indexer.addDocument(itemDoc.getLuceneDocument());
 				}
diff --git a/src/main/java/org/olat/modules/webFeed/search/indexer/FeedRepositoryIndexer.java b/src/main/java/org/olat/modules/webFeed/search/indexer/FeedRepositoryIndexer.java
index 7a5e8f82041..2a2aac21332 100644
--- a/src/main/java/org/olat/modules/webFeed/search/indexer/FeedRepositoryIndexer.java
+++ b/src/main/java/org/olat/modules/webFeed/search/indexer/FeedRepositoryIndexer.java
@@ -20,10 +20,11 @@
 package org.olat.modules.webFeed.search.indexer;
 
 import java.io.IOException;
+import java.util.List;
 
-import org.olat.modules.webFeed.managers.FeedManager;
-import org.olat.modules.webFeed.models.Feed;
-import org.olat.modules.webFeed.models.Item;
+import org.olat.modules.webFeed.Feed;
+import org.olat.modules.webFeed.Item;
+import org.olat.modules.webFeed.manager.FeedManager;
 import org.olat.modules.webFeed.search.document.FeedItemDocument;
 import org.olat.repository.RepositoryEntry;
 import org.olat.search.model.OlatDocument;
@@ -56,13 +57,14 @@ public abstract class FeedRepositoryIndexer extends DefaultIndexer {
 			if (isLogDebugEnabled()) {
 				logDebug("Indexing: " + repoEntryName);
 			}
-			Feed feed = FeedManager.getInstance().getFeed(repositoryEntry.getOlatResource());
+			Feed feed = FeedManager.getInstance().loadFeed(repositoryEntry.getOlatResource());
 			if(feed != null) {
 				// Only index items. Feed itself is indexed by RepositoryEntryIndexer.
+				List<Item> publishedItems = FeedManager.getInstance().loadPublishedItems(feed);
 				if (isLogDebugEnabled()) {
-					logDebug("PublishedItems size=" + feed.getPublishedItems().size());
+					logDebug("PublishedItems size=" + publishedItems.size());
 				}
-				for (Item item : feed.getPublishedItems()) {
+				for (Item item : publishedItems) {
 					SearchResourceContext feedContext = new SearchResourceContext(searchResourceContext);
 					feedContext.setDocumentType(getDocumentType());
 					OlatDocument itemDoc = new FeedItemDocument(item, feedContext);
@@ -77,6 +79,7 @@ public abstract class FeedRepositoryIndexer extends DefaultIndexer {
 	/**
 	 * @see org.olat.search.service.indexer.Indexer#getSupportedTypeName()
 	 */
+	@Override
 	public abstract String getSupportedTypeName();
 
 	/**
diff --git a/src/main/java/org/olat/modules/webFeed/ui/DisplayFeedUrlController.java b/src/main/java/org/olat/modules/webFeed/ui/DisplayFeedUrlController.java
index 5dd585f0607..7f01449ce76 100644
--- a/src/main/java/org/olat/modules/webFeed/ui/DisplayFeedUrlController.java
+++ b/src/main/java/org/olat/modules/webFeed/ui/DisplayFeedUrlController.java
@@ -28,8 +28,8 @@ import org.olat.core.gui.components.form.flexible.impl.FormEvent;
 import org.olat.core.gui.control.Controller;
 import org.olat.core.gui.control.WindowControl;
 import org.olat.core.gui.translator.Translator;
+import org.olat.modules.webFeed.Feed;
 import org.olat.modules.webFeed.FeedViewHelper;
-import org.olat.modules.webFeed.models.Feed;
 
 /**
  * Controller for displaying the feed-url to the students. When the element is
diff --git a/src/main/java/org/olat/modules/webFeed/ui/FeedFormController.java b/src/main/java/org/olat/modules/webFeed/ui/FeedFormController.java
index 330dda30917..e77cbe9fe09 100644
--- a/src/main/java/org/olat/modules/webFeed/ui/FeedFormController.java
+++ b/src/main/java/org/olat/modules/webFeed/ui/FeedFormController.java
@@ -45,9 +45,9 @@ import org.olat.core.util.WebappHelper;
 import org.olat.core.util.vfs.LocalFileImpl;
 import org.olat.core.util.vfs.Quota;
 import org.olat.core.util.vfs.VFSLeaf;
-import org.olat.modules.webFeed.managers.FeedManager;
-import org.olat.modules.webFeed.managers.ValidatedURL;
-import org.olat.modules.webFeed.models.Feed;
+import org.olat.modules.webFeed.Feed;
+import org.olat.modules.webFeed.manager.FeedManager;
+import org.olat.modules.webFeed.manager.ValidatedURL;
 
 /**
  * This controller is responsible for editing feed information. <br />
@@ -84,7 +84,7 @@ class FeedFormController extends FormBasicController {
 	public FeedFormController(UserRequest ureq, WindowControl wControl, Feed feed, FeedUIFactory uiFactory) {
 		super(ureq, wControl);
 		this.feed = feed;
-		this.feedQuota = FeedManager.getInstance().getQuota(feed.getResource());
+		this.feedQuota = FeedManager.getInstance().getQuota(feed);
 		setTranslator(uiFactory.getTranslator());
 		initForm(ureq);
 	}
@@ -127,7 +127,7 @@ class FeedFormController extends FormBasicController {
 				} else {
 					file.clearError();
 				}
-				deleteImage.setVisible(true);	
+				deleteImage.setVisible(true);
 			}
 		} else if(source == deleteImage) {
 			VFSLeaf img = FeedManager.getInstance().createFeedMediaFile(feed, feed.getImageName(), null);
diff --git a/src/main/java/org/olat/modules/webFeed/ui/FeedMainController.java b/src/main/java/org/olat/modules/webFeed/ui/FeedMainController.java
index a0cfa7f741e..d587e348b6f 100644
--- a/src/main/java/org/olat/modules/webFeed/ui/FeedMainController.java
+++ b/src/main/java/org/olat/modules/webFeed/ui/FeedMainController.java
@@ -45,11 +45,11 @@ import org.olat.core.util.coordinate.CoordinatorManager;
 import org.olat.core.util.coordinate.LockResult;
 import org.olat.core.util.event.GenericEventListener;
 import org.olat.core.util.resource.OLATResourceableJustBeforeDeletedEvent;
+import org.olat.modules.webFeed.Feed;
 import org.olat.modules.webFeed.FeedSecurityCallback;
 import org.olat.modules.webFeed.FeedViewHelper;
-import org.olat.modules.webFeed.managers.FeedManager;
-import org.olat.modules.webFeed.models.Feed;
-import org.olat.modules.webFeed.models.Item;
+import org.olat.modules.webFeed.Item;
+import org.olat.modules.webFeed.manager.FeedManager;
 import org.olat.user.UserManager;
 import org.olat.util.logging.activity.LoggingResourceable;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -121,17 +121,13 @@ public class FeedMainController extends BasicController implements Activateable2
 		subsContext = callback.getSubscriptionContext();
 				
 		setTranslator(uiFactory.getTranslator());
-		feed = feedManager.getFeed(ores);
+		feed = feedManager.loadFeed(ores);
 		if(feed == null) {
 			vcMain = createVelocityContainer("feed_error");
 			vcMain.contextPut("errorMessage", translate("feed.error"));
 			putInitialPanel(vcMain);
 		} else {
-			String authorFullname = userManager.getUserDisplayName(feed.getAuthor());
-			if(authorFullname == null) {
-				authorFullname = "???";
-			}
-			helper = new FeedViewHelper(feed, getIdentity(), authorFullname, uiFactory.getTranslator(), courseId, nodeId, callback);
+			helper = new FeedViewHelper(feed, getIdentity(), uiFactory.getTranslator(), courseId, nodeId);
 			CoordinatorManager.getInstance().getCoordinator().getEventBus().registerFor(this, ureq.getIdentity(), feed);
 			display(ureq, wControl, displayConfig);
 			// do logging
@@ -204,7 +200,7 @@ public class FeedMainController extends BasicController implements Activateable2
 	@Override
 	protected void event(UserRequest ureq, Component source, Event event) {
 		// feed for this event and make sure the updated feed object is in the view
-		feed = feedManager.getFeed(feed);
+		feed = feedManager.loadFeed(feed);
 		vcInfo.contextPut("feed", feed);
 		
 		if (source == editFeedButton) {
@@ -261,27 +257,24 @@ public class FeedMainController extends BasicController implements Activateable2
 						if (newFeed == null) {
 							feed.setExternal(null);
 							itemsCtr.makeInternalAndExternalButtons();
-							// No more episodes to display
-							itemsCtr.resetItems(ureq, feed);
-						} else if (!newFeed.equals(oldFeedUrl)) {
-							// Set the episodes dirty since the feed url changed.							
-							itemsCtr.resetItems(ureq, feed);
 						}
-						// Set the URIs correctly
-						helper.setURIs();
-					} 
+					}
+					feed = feedManager.updateFeed(feed);
 					//handle image-changes if any
 					if (feedFormCtr.isImageDeleted()) {
-						feedManager.deleteImage(feed);
+						feed = feedManager.deleteFeedImage(feed);
 					} else {
 						// set the image
 						FileElement image = null;
+						// TODO hier wird image null!!!
 						image = feedFormCtr.getFile();
-						feedManager.setImage(image, feed);
-					}							
-					
-					// Eventually update the feed
-					feed = feedManager.updateFeedMetadata(feed);
+						feed = feedManager.replaceFeedImage(feed, image);
+					}
+
+					itemsCtr.resetItems(ureq, feed);	
+					// Set the URIs correctly
+					helper.setURIs();
+
 					// Dispose the feedFormCtr
 					removeAsListenerAndDispose(feedFormCtr);
 					feedFormCtr = null;
@@ -304,6 +297,7 @@ public class FeedMainController extends BasicController implements Activateable2
 				feedFormCtr = null;
 			}
 		} else if (source == itemsCtr && event.equals(ItemsController.HANDLE_NEW_EXTERNAL_FEED_DIALOG_EVENT)) {
+			feed = feedManager.loadFeed(feed);
 			oldFeedUrl = feed.getExternalFeedUrl();			
 			feedFormCtr = new FeedFormController(ureq, getWindowControl(), feed, uiFactory);
 			activateModalDialog(feedFormCtr, uiFactory.getTranslator().translate("feed.edit"));
@@ -327,13 +321,14 @@ public class FeedMainController extends BasicController implements Activateable2
 	public void activate(UserRequest ureq, List<ContextEntry> entries, StateEntry state) {
 		if(entries == null || entries.isEmpty()) return;
 		
+		Item item = null;
 		String itemId = entries.get(0).getOLATResourceable().getResourceableTypeName();
 		if(itemId != null && itemId.startsWith("item=")) {
 			itemId = itemId.substring(5, itemId.length());
+			Long itemKey = Long.parseLong(itemId);
+			item = FeedManager.getInstance().loadItem(itemKey);
 		}
-		int index = feed.getItemIds().indexOf(itemId);
-		if (index >= 0) {
-			Item item = feed.getItems().get(index);
+		if (item != null) {
 			itemsCtr.activate(ureq, item);
 		}
 	}
diff --git a/src/main/java/org/olat/modules/webFeed/ui/FeedUIFactory.java b/src/main/java/org/olat/modules/webFeed/ui/FeedUIFactory.java
index f610f04515f..f0a4ec6a8a0 100644
--- a/src/main/java/org/olat/modules/webFeed/ui/FeedUIFactory.java
+++ b/src/main/java/org/olat/modules/webFeed/ui/FeedUIFactory.java
@@ -32,9 +32,9 @@ import org.olat.core.id.OLATResourceable;
 import org.olat.course.ICourse;
 import org.olat.course.nodes.AbstractFeedCourseNode;
 import org.olat.course.run.userview.UserCourseEnvironment;
+import org.olat.modules.webFeed.Feed;
 import org.olat.modules.webFeed.FeedSecurityCallback;
-import org.olat.modules.webFeed.models.Feed;
-import org.olat.modules.webFeed.models.Item;
+import org.olat.modules.webFeed.Item;
 
 /**
  * Abstract Factory Pattern for the user interface of different feed types.
@@ -78,7 +78,7 @@ public abstract class FeedUIFactory {
 		return new FeedMainController(ores, ureq, wControl, null, null, this, callback, displayConfig);
 	}
 
-	public abstract FormBasicController createItemFormController(UserRequest ureq, WindowControl wControl, Item item, Feed feed);
+	public abstract FormBasicController createItemFormController(UserRequest ureq, WindowControl wControl, Item currentItem, Feed feed);
 
 	public abstract TabbableController createNodeEditController(AbstractFeedCourseNode courseNode, ICourse course, UserCourseEnvironment uce, UserRequest ureq,
 			WindowControl control);
diff --git a/src/main/java/org/olat/modules/webFeed/ui/ItemController.java b/src/main/java/org/olat/modules/webFeed/ui/ItemController.java
index 3578b30f235..003a0b6ae6b 100644
--- a/src/main/java/org/olat/modules/webFeed/ui/ItemController.java
+++ b/src/main/java/org/olat/modules/webFeed/ui/ItemController.java
@@ -38,10 +38,10 @@ import org.olat.core.gui.control.generic.dtabs.Activateable2;
 import org.olat.core.id.context.ContextEntry;
 import org.olat.core.id.context.StateEntry;
 import org.olat.core.logging.activity.ThreadLocalUserActivityLogger;
+import org.olat.modules.webFeed.Feed;
 import org.olat.modules.webFeed.FeedSecurityCallback;
 import org.olat.modules.webFeed.FeedViewHelper;
-import org.olat.modules.webFeed.models.Feed;
-import org.olat.modules.webFeed.models.Item;
+import org.olat.modules.webFeed.Item;
 import org.olat.util.logging.activity.LoggingResourceable;
 
 /**
@@ -100,6 +100,7 @@ public class ItemController extends BasicController implements Activateable2 {
 		putInitialPanel(vcItem);
 		// do logging
 		ThreadLocalUserActivityLogger.log(FeedLoggingAction.FEED_ITEM_READ, getClass(), LoggingResourceable.wrap(item));
+		 	
 	}
 
 	/**
diff --git a/src/main/java/org/olat/modules/webFeed/ui/ItemsController.java b/src/main/java/org/olat/modules/webFeed/ui/ItemsController.java
index 95c121430ed..bfd7e93fe21 100644
--- a/src/main/java/org/olat/modules/webFeed/ui/ItemsController.java
+++ b/src/main/java/org/olat/modules/webFeed/ui/ItemsController.java
@@ -20,7 +20,6 @@
 package org.olat.modules.webFeed.ui;
 
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
@@ -60,15 +59,14 @@ import org.olat.core.id.context.StateEntry;
 import org.olat.core.logging.activity.ThreadLocalUserActivityLogger;
 import org.olat.core.util.CodeHelper;
 import org.olat.core.util.coordinate.LockResult;
-import org.olat.core.util.vfs.VFSContainer;
 import org.olat.modules.portfolio.PortfolioV2Module;
 import org.olat.modules.portfolio.ui.component.MediaCollectorComponent;
+import org.olat.modules.webFeed.Feed;
 import org.olat.modules.webFeed.FeedSecurityCallback;
 import org.olat.modules.webFeed.FeedViewHelper;
-import org.olat.modules.webFeed.managers.FeedManager;
-import org.olat.modules.webFeed.models.Feed;
-import org.olat.modules.webFeed.models.Item;
-import org.olat.modules.webFeed.models.ItemPublishDateComparator;
+import org.olat.modules.webFeed.Item;
+import org.olat.modules.webFeed.manager.FeedManager;
+import org.olat.modules.webFeed.model.ItemImpl;
 import org.olat.modules.webFeed.portfolio.BlogEntryMedia;
 import org.olat.modules.webFeed.portfolio.BlogEntryMediaHandler;
 import org.olat.portfolio.EPUIFactory;
@@ -90,13 +88,14 @@ public class ItemsController extends BasicController implements Activateable2 {
 	private ArrayList<Link> editButtons;
 	private ArrayList<Link> deleteButtons;
 	private ArrayList<Link> itemLinks;
-	private Map<Item,Controller> artefactLinks;
-	private Map<Item,Controller> commentsLinks;
+	private Map<Item, Controller> artefactLinks;
+	private Map<Item, Controller> commentsLinks;
 	private Link addItemButton, makeInternalButton, makeExternalButton, olderItemsLink, newerItemsLink, startpageLink;
 	private FormBasicController itemFormCtr;
 	private CloseableModalController cmc;
 	private DialogBoxController confirmDialogCtr;
 	private Feed feedResource;
+	private List<Item> accessibleItems;
 	private Item currentItem;
 	private FeedViewHelper helper;
 	private FeedUIFactory uiFactory;
@@ -104,7 +103,7 @@ public class ItemsController extends BasicController implements Activateable2 {
 	private FeedSecurityCallback callback;
 	private Panel mainPanel;
 	private ItemController itemCtr;
-	//private int allItemsCount = 0;
+	// private int allItemsCount = 0;
 	private List<ItemId> allItemIds;
 	// Only one lock variable is needed, since only one item can be edited
 	// at a time.
@@ -112,7 +111,8 @@ public class ItemsController extends BasicController implements Activateable2 {
 	private FeedItemDisplayConfig displayConfig;
 	public static Event HANDLE_NEW_EXTERNAL_FEED_DIALOG_EVENT = new Event("cmd.handle.new.external.feed.dialog");
 	public static Event FEED_INFO_IS_DIRTY_EVENT = new Event("cmd.feed.info.is.dirty");
-	
+
+	FeedManager feedManager = FeedManager.getInstance();
 	@Autowired
 	private UserManager userManager;
 	@Autowired
@@ -122,6 +122,7 @@ public class ItemsController extends BasicController implements Activateable2 {
 
 	/**
 	 * default constructor, with full FeedItemDisplayConfig
+	 * 
 	 * @param ureq
 	 * @param wControl
 	 * @param feed
@@ -130,13 +131,15 @@ public class ItemsController extends BasicController implements Activateable2 {
 	 * @param callback
 	 * @param vcRightColumn
 	 */
-	public ItemsController(final UserRequest ureq, final WindowControl wControl, final Feed feed, final FeedViewHelper helper, final FeedUIFactory uiFactory,
-			final FeedSecurityCallback callback, final VelocityContainer vcRightColumn) {
+	public ItemsController(final UserRequest ureq, final WindowControl wControl, final Feed feed,
+			final FeedViewHelper helper, final FeedUIFactory uiFactory, final FeedSecurityCallback callback,
+			final VelocityContainer vcRightColumn) {
 		this(ureq, wControl, feed, helper, uiFactory, callback, vcRightColumn, null);
 	}
-	
+
 	/**
 	 * load items with a given displayconfig
+	 * 
 	 * @param ureq
 	 * @param wControl
 	 * @param feed
@@ -146,15 +149,17 @@ public class ItemsController extends BasicController implements Activateable2 {
 	 * @param vcRightColumn
 	 * @param displayConfig
 	 */
-	public ItemsController(final UserRequest ureq, final WindowControl wControl, final Feed feed, final FeedViewHelper helper, final FeedUIFactory uiFactory,
-			final FeedSecurityCallback callback, final VelocityContainer vcRightColumn, FeedItemDisplayConfig displayConfig) {
+	public ItemsController(final UserRequest ureq, final WindowControl wControl, final Feed feed,
+			final FeedViewHelper helper, final FeedUIFactory uiFactory, final FeedSecurityCallback callback,
+			final VelocityContainer vcRightColumn, FeedItemDisplayConfig displayConfig) {
 		super(ureq, wControl);
-		
+
 		if (displayConfig == null) {
 			displayConfig = new FeedItemDisplayConfig(true, true, true);
 		}
 		this.displayConfig = displayConfig;
 		this.feedResource = feed;
+		this.accessibleItems = feedManager.loadFilteredAndSortedItems(feed, callback, ureq.getIdentity());
 		this.helper = helper;
 		this.uiFactory = uiFactory;
 		this.callback = callback;
@@ -162,6 +167,7 @@ public class ItemsController extends BasicController implements Activateable2 {
 
 		vcItems = uiFactory.createItemsVelocityContainer(this);
 		vcItems.contextPut("feed", feed);
+		vcItems.contextPut("items", accessibleItems);
 		vcItems.contextPut("callback", callback);
 		vcItems.contextPut("helper", helper);
 
@@ -174,13 +180,12 @@ public class ItemsController extends BasicController implements Activateable2 {
 		newerItemsLink.setCustomEnabledLinkCSS("o_forward");
 		newerItemsLink.setCustomDisplayText("&raquo;");
 		newerItemsLink.setTitle("feed.newer.items");
-		
+
 		startpageLink = LinkFactory.createLink("feed.startpage", vcItems, this);
 		startpageLink.setCustomEnabledLinkCSS("o_first_page");
 
-		
 		createEditButtons(ureq, feed);
-	
+
 		// Add item details page link
 		createItemLinks(feed);
 		// Add item user comments link and rating
@@ -191,11 +196,10 @@ public class ItemsController extends BasicController implements Activateable2 {
 		createDateComponents(feed);
 
 		// The year/month navigation
-		List<Item> items = feed.getFilteredItems(callback, ureq.getIdentity());
-		setAllItemIds(items);
-		naviCtr = new YearNavigationController(ureq, wControl, getTranslator(), items);
+		setAllItemIds(accessibleItems);
+		naviCtr = new YearNavigationController(ureq, wControl, getTranslator(), accessibleItems);
 		listenTo(naviCtr);
-		if (displayConfig.isShowDateNavigation()){
+		if (displayConfig.isShowDateNavigation()) {
 			vcRightColumn.put("navi", naviCtr.getInitialComponent());
 		}
 
@@ -203,18 +207,19 @@ public class ItemsController extends BasicController implements Activateable2 {
 		mainPanel.setContent(vcItems);
 		this.putInitialPanel(mainPanel);
 	}
-	
+
 	private void setAllItemIds(List<Item> items) {
 		allItemIds = new ArrayList<ItemId>();
-		for(Item item:items) {
+		for (Item item : items) {
 			allItemIds.add(new ItemId(item));
 		}
 	}
-	
+
 	private boolean isSameAllItems(List<Item> items) {
-		if(allItemIds == null) return false;
+		if (allItemIds == null)
+			return false;
 		List<ItemId> itemIds = new ArrayList<ItemId>();
-		for(Item item:items) {
+		for (Item item : items) {
 			itemIds.add(new ItemId(item));
 		}
 		return allItemIds.containsAll(itemIds) && itemIds.containsAll(allItemIds);
@@ -222,19 +227,19 @@ public class ItemsController extends BasicController implements Activateable2 {
 
 	/**
 	 * Creates all necessary buttons for editing the feed's items
-	 * @param feed the current feed object
+	 * 
+	 * @param feed
+	 *            the current feed object
 	 */
 	private void createEditButtons(UserRequest ureq, Feed feed) {
-		List<Item> items = feed.getCopiedListOfItems();
-
 		editButtons = new ArrayList<>();
 		deleteButtons = new ArrayList<>();
 		artefactLinks = new HashMap<>();
 		if (feed.isInternal()) {
 			addItemButton = LinkFactory.createButtonSmall("feed.add.item", vcItems, this);
 			addItemButton.setElementCssClass("o_sel_feed_item_new");
-			if (items != null) {
-				for (Item item : items) {
+			if (accessibleItems != null) {
+				for (Item item : accessibleItems) {
 					createButtonsForItem(ureq, feed, item);
 				}
 			}
@@ -244,7 +249,8 @@ public class ItemsController extends BasicController implements Activateable2 {
 			// - it has just been created,
 			// - all items have been removed or
 			// - the feed url of an external feed has been set empty.
-			// In such a case, the user can decide whether to make it internal or
+			// In such a case, the user can decide whether to make it internal
+			// or
 			// external.
 			makeInternalAndExternalButtons();
 		}
@@ -257,32 +263,36 @@ public class ItemsController extends BasicController implements Activateable2 {
 	 * @param feed
 	 */
 	private void createCommentsAndRatingsLinks(UserRequest ureq, Feed feed) {
-		List<Item> items = feed.getCopiedListOfItems();
-		if (items != null) {
-			for (Item item : items) {
+		if (accessibleItems != null) {
+			for (Item item : accessibleItems) {
 				// Add rating and commenting controller
 				createCommentsAndRatingsLink(ureq, feed, item);
-			}			
-		}		
+			}
+		}
 	}
+
 	/**
 	 * Create comments and rating component link for given feed item
+	 * 
 	 * @param ureq
 	 * @param feed
 	 * @param item
 	 */
 	private void createCommentsAndRatingsLink(UserRequest ureq, Feed feed, Item item) {
-		if(feed == null || item == null) return;//check against concurrent changes
+		if (feed == null || item == null)
+			return;// check against concurrent changes
 		if (CoreSpringFactory.containsBean(CommentAndRatingService.class)) {
-			if(commentsLinks == null) {
-				commentsLinks = new HashMap<Item,Controller>();
-			} else if(commentsLinks.containsKey(item)) {
+			if (commentsLinks == null) {
+				commentsLinks = new HashMap<Item, Controller>();
+			} else if (commentsLinks.containsKey(item)) {
 				removeAsListenerAndDispose(commentsLinks.get(item));
 			}
 
 			boolean anonym = ureq.getUserSession().getRoles().isGuestOnly();
-			CommentAndRatingSecurityCallback secCallback = new CommentAndRatingDefaultSecurityCallback(getIdentity(), callback.mayEditMetadata(), anonym);
-			UserCommentsAndRatingsController commentsAndRatingCtr = new UserCommentsAndRatingsController(ureq, getWindowControl(), feed, item.getGuid(), secCallback, true, true, false);
+			CommentAndRatingSecurityCallback secCallback = new CommentAndRatingDefaultSecurityCallback(getIdentity(),
+					callback.mayEditMetadata(), anonym);
+			UserCommentsAndRatingsController commentsAndRatingCtr = new UserCommentsAndRatingsController(ureq,
+					getWindowControl(), feed, item.getGuid(), secCallback, true, true, false);
 			commentsAndRatingCtr.setUserObject(item);
 			listenTo(commentsAndRatingCtr);
 			commentsLinks.put(item, commentsAndRatingCtr);
@@ -298,22 +308,20 @@ public class ItemsController extends BasicController implements Activateable2 {
 	 * @param feed
 	 */
 	private void createDateComponents(Feed feed) {
-		List<Item> items = feed.getCopiedListOfItems();
-		if (items != null) {
-			for (Item item : items) {
+		if (accessibleItems != null) {
+			for (Item item : accessibleItems) {
 				String guid = item.getGuid();
-				if(item.getDate() != null) {
+				if (item.getDate() != null) {
 					DateComponentFactory.createDateComponentWithYear("date." + guid, item.getDate(), vcItems);
 				}
-			}			
-		}				
+			}
+		}
 	}
-	
+
 	private void createItemLinks(Feed feed) {
-		List<Item> items = feed.getCopiedListOfItems();
-		itemLinks = new ArrayList<Link>();
-		if (items != null) {
-			for (Item item : items) {
+		itemLinks = new ArrayList<>();
+		if (accessibleItems != null) {
+			for (Item item : accessibleItems) {
 				createItemLink(item);
 			}
 		}
@@ -324,14 +332,16 @@ public class ItemsController extends BasicController implements Activateable2 {
 	 */
 	private void createItemLink(Item item) {
 		String guid = item.getGuid();
-		Link itemLink_more = LinkFactory.createCustomLink("link.to." + guid, "link.to." + guid, "feed.link.more", Link.LINK, vcItems, this);
+		Link itemLink_more = LinkFactory.createCustomLink("link.to." + guid, "link.to." + guid, "feed.link.more",
+				Link.LINK, vcItems, this);
 		itemLink_more.setIconRightCSS("o_icon o_icon_start");
 		itemLink_more.setCustomEnabledLinkCSS("o_link_forward");
 		itemLink_more.setUserObject(item);
-		
-		Link itemLink_title = LinkFactory.createCustomLink("titlelink.to." + guid, "titlelink.to." + guid, StringEscapeUtils.escapeHtml(item.getTitle()), Link.NONTRANSLATED, vcItems, this);
+
+		Link itemLink_title = LinkFactory.createCustomLink("titlelink.to." + guid, "titlelink.to." + guid,
+				StringEscapeUtils.escapeHtml(item.getTitle()), Link.NONTRANSLATED, vcItems, this);
 		itemLink_title.setUserObject(item);
-		
+
 		itemLinks.add(itemLink_title);
 		itemLinks.add(itemLink_more);
 	}
@@ -357,35 +367,41 @@ public class ItemsController extends BasicController implements Activateable2 {
 
 		String guid = item.getGuid();
 		String editId = "feed.edit.item.".concat(guid);
-		Link editButton = LinkFactory.createCustomLink(editId, editId, "feed.edit.item", Link.BUTTON_SMALL, vcItems, this);
+		Link editButton = LinkFactory.createCustomLink(editId, editId, "feed.edit.item", Link.BUTTON_SMALL, vcItems,
+				this);
 		editButton.setElementCssClass("o_sel_feed_item_edit");
 		editButton.setEnabled(edit);
 		editButton.setVisible(edit);
-		
+
 		String deleteId = "delete.".concat(guid);
-		Link deleteButton = LinkFactory.createCustomLink(deleteId, deleteId, "delete", Link.BUTTON_SMALL, vcItems, this);
+		Link deleteButton = LinkFactory.createCustomLink(deleteId, deleteId, "delete", Link.BUTTON_SMALL, vcItems,
+				this);
 		deleteButton.setElementCssClass("o_sel_feed_item_delete");
 		deleteButton.setEnabled(delete);
 		deleteButton.setVisible(delete);
 
-		if(feedResource.isInternal() && getIdentity().getKey() != null && getIdentity().getKey().equals(item.getAuthorKey())) {
-			String businessPath = BusinessControlFactory.getInstance().getAsString(getWindowControl().getBusinessControl());
+		if (feedResource.isInternal() && getIdentity().getKey() != null
+				&& getIdentity().getKey().equals(item.getAuthorKey())) {
+			String businessPath = BusinessControlFactory.getInstance()
+					.getAsString(getWindowControl().getBusinessControl());
 			businessPath += "[item=" + item.getGuid() + ":0]";
-			
-			if(portfolioModule.isEnabled()) {
+
+			if (portfolioModule.isEnabled()) {
 				String name = "feed.artefact.item.".concat(guid);
 				BlogEntryMedia media = new BlogEntryMedia(feed, item);
-				MediaCollectorComponent collectorCmp = new MediaCollectorComponent(name, getWindowControl(), media, blogMediaHandler, businessPath);
+				MediaCollectorComponent collectorCmp = new MediaCollectorComponent(name, getWindowControl(), media,
+						blogMediaHandler, businessPath);
 				vcItems.put(name, collectorCmp);
 			} else {
-				Controller artefactCtrl = EPUIFactory.createArtefactCollectWizzardController(ureq, getWindowControl(), feedResource, businessPath);
-				if(artefactCtrl != null) {
+				Controller artefactCtrl = EPUIFactory.createArtefactCollectWizzardController(ureq, getWindowControl(),
+						feedResource, businessPath);
+				if (artefactCtrl != null) {
 					artefactLinks.put(item, artefactCtrl);
 					vcItems.put("feed.artefact.item.".concat(guid), artefactCtrl.getInitialComponent());
 				}
 			}
 		}
-		
+
 		editButton.setUserObject(item);
 		deleteButton.setUserObject(item);
 		editButtons.add(editButton);
@@ -398,14 +414,14 @@ public class ItemsController extends BasicController implements Activateable2 {
 	@Override
 	protected void doDispose() {
 		// make sure the lock is released
-		FeedManager.getInstance().releaseLock(lock);
+		feedManager.releaseLock(lock);
 		// Dispose confirm deletion dialog controller since it isn't listend to.
 		if (confirmDialogCtr != null) {
 			removeAsListenerAndDispose(confirmDialogCtr);
 		}
-		
-		if(artefactLinks != null) {
-			for(Controller ctrl:artefactLinks.values()) {
+
+		if (artefactLinks != null) {
+			for (Controller ctrl : artefactLinks.values()) {
 				ctrl.dispose();
 			}
 			artefactLinks.clear();
@@ -420,40 +436,41 @@ public class ItemsController extends BasicController implements Activateable2 {
 	 */
 	@Override
 	protected void event(UserRequest ureq, Component source, Event event) {
-		FeedManager feedManager = FeedManager.getInstance();
-		// feed for this event and make sure the updated feed object is in the view
-		Feed feed = feedManager.getFeed(feedResource);
-		vcItems.contextPut("feed", feed);
-		
+		// feed for this event and make sure the updated feed object is in the
+		// view
+		feedResource = feedManager.loadFeed(feedResource);
+		accessibleItems = feedManager.loadFilteredAndSortedItems(feedResource, callback, ureq.getIdentity());
+
 		if (source == addItemButton) {
-			currentItem = new Item();
+			currentItem = new ItemImpl(feedResource);
 			currentItem.setDraft(true);
 			currentItem.setAuthorKey(ureq.getIdentity().getKey());
-			// Generate new GUID for item, needed for media files that are stored relative to the GUID
-			currentItem.setGuid(CodeHelper.getGlobalForeverUniqueID()); 
-			// Create item and media containers 
-			feedManager.createItemContainer(feed, currentItem);
-			itemFormCtr = uiFactory.createItemFormController(ureq, getWindowControl(), currentItem, feed);
+			// Generate new GUID for item, needed for media files that are
+			// stored relative to the GUID
+			currentItem.setGuid(CodeHelper.getGlobalForeverUniqueID());
+			itemFormCtr = uiFactory.createItemFormController(ureq, getWindowControl(), currentItem, feedResource);
 			activateModalDialog(itemFormCtr, uiFactory.getTranslator().translate("feed.edit.item"));
 
 		} else if (editButtons != null && editButtons.contains(source)) {
 			currentItem = (Item) ((Link) source).getUserObject();
-			// check if still available, maybe deleted by other user in the meantime
-			if (feed.getItems().contains(currentItem)) {
-				lock = feedManager.acquireLock(feed, currentItem, getIdentity());
+			// check if still available, maybe deleted by other user in the
+			// meantime
+			if (accessibleItems.contains(currentItem)) {
+				lock = feedManager.acquireLock(feedResource, currentItem, getIdentity());
 				if (lock.isSuccess()) {
 					// reload to prevent stale object, then launch editor
-					currentItem = feedManager.getItem(feed, currentItem.getGuid());					
-					itemFormCtr = uiFactory.createItemFormController(ureq, getWindowControl(), currentItem, feed);
+					currentItem = feedManager.loadItem(currentItem.getKey());
+					itemFormCtr = uiFactory.createItemFormController(ureq, getWindowControl(), currentItem,
+							feedResource);
 					activateModalDialog(itemFormCtr, uiFactory.getTranslator().translate("feed.edit.item"));
 				} else {
 					String fullName = userManager.getUserDisplayName(lock.getOwner());
 					showInfo("feed.item.is.being.edited.by", fullName);
-				}				
+				}
 			} else {
 				showInfo("feed.item.is.being.edited.by", "unknown");
 			}
-			
+
 		} else if (deleteButtons != null && deleteButtons.contains(source)) {
 			Item item = (Item) ((Link) source).getUserObject();
 			confirmDialogCtr = activateYesNoDialog(ureq, null, translate("feed.item.confirm.delete"), confirmDialogCtr);
@@ -461,69 +478,70 @@ public class ItemsController extends BasicController implements Activateable2 {
 
 		} else if (itemLinks != null && itemLinks.contains(source)) {
 			Item item = (Item) ((Link) source).getUserObject();
-			// Reload first, could be stale
-			item = feedManager.getItem(feed, item.getGuid());					
-			if(item != null) {
+			if (item != null) {
 				displayItemController(ureq, item);
 			}
 		} else if (source == makeInternalButton) {
-			if (feed.isUndefined()) {
-				feedManager.updateFeedMode(Boolean.FALSE, feed);				
-			} else if (feed.isExternal()) {
-				// Very special case: another user concurrently changed feed to external. Do nothing
+			if (feedResource.isUndefined()) {
+				feedResource = feedManager.updateFeedMode(Boolean.FALSE, feedResource);
+			} else if (feedResource.isExternal()) {
+				// Very special case: another user concurrently changed feed to
+				// external. Do nothing
 				vcItems.setDirty(true);
 				return;
 			}
 			// else nothing to do, already set to internal by a concurrent user
-			
+
 			// Add temporary item and open edit dialog
 			addItemButton = LinkFactory.createButton("feed.add.item", vcItems, this);
 			addItemButton.setElementCssClass("o_sel_feed_item_new");
-			currentItem = new Item();
+			currentItem = new ItemImpl(feedResource);
 			currentItem.setDraft(true);
 			currentItem.setAuthorKey(ureq.getIdentity().getKey());
-			// Generate new GUID for item, needed for media files that are stored relative to the GUID
-			currentItem.setGuid(CodeHelper.getGlobalForeverUniqueID()); 
-			// Create item and media containers 
-			feedManager.createItemContainer(feed, currentItem);
-			itemFormCtr = uiFactory.createItemFormController(ureq, getWindowControl(), currentItem, feed);
+			// Generate new GUID for item, needed for media files that are
+			// stored relative to the GUID
+			currentItem.setGuid(CodeHelper.getGlobalForeverUniqueID());
+			itemFormCtr = uiFactory.createItemFormController(ureq, getWindowControl(), currentItem, feedResource);
 			activateModalDialog(itemFormCtr, uiFactory.getTranslator().translate("feed.edit.item"));
 			// do logging
-			ThreadLocalUserActivityLogger.log(FeedLoggingAction.FEED_EDIT, getClass(), LoggingResourceable.wrap(feed));
-			
+			ThreadLocalUserActivityLogger.log(FeedLoggingAction.FEED_EDIT, getClass(),
+					LoggingResourceable.wrap(feedResource));
 
 		} else if (source == makeExternalButton) {
-			if (feed.isUndefined()) {
-				feedManager.updateFeedMode(Boolean.TRUE, feed);
+			if (feedResource.isUndefined()) {
+				feedResource = feedManager.updateFeedMode(Boolean.TRUE, feedResource);
 				vcItems.setDirty(true);
-				// Ask listening FeedMainController to open and handle a new external
+				// Ask listening FeedMainController to open and handle a new
+				// external
 				// feed dialog.
 				fireEvent(ureq, HANDLE_NEW_EXTERNAL_FEED_DIALOG_EVENT);
 				// do logging
-				ThreadLocalUserActivityLogger.log(FeedLoggingAction.FEED_EDIT, getClass(), LoggingResourceable.wrap(feed));
-			} 
+				ThreadLocalUserActivityLogger.log(FeedLoggingAction.FEED_EDIT, getClass(),
+						LoggingResourceable.wrap(feedResource));
+			}
 			// else nothing to do, already set to external by a concurrent user
 
 		} else if (source == olderItemsLink) {
 			helper.olderItems();
-			createEditButtons(ureq, feed);
-			createCommentsAndRatingsLinks(ureq, feed);
+			createEditButtons(ureq, feedResource);
+			createCommentsAndRatingsLinks(ureq, feedResource);
 			vcItems.setDirty(true);
 
 		} else if (source == newerItemsLink) {
 			helper.newerItems();
-			createEditButtons(ureq, feed);
-			createCommentsAndRatingsLinks(ureq, feed);
+			createEditButtons(ureq, feedResource);
+			createCommentsAndRatingsLinks(ureq, feedResource);
 			vcItems.setDirty(true);
 
 		} else if (source == startpageLink) {
 			helper.startpage();
-			createEditButtons(ureq, feed);
-			createCommentsAndRatingsLinks(ureq, feed);
+			createEditButtons(ureq, feedResource);
+			createCommentsAndRatingsLinks(ureq, feedResource);
 			vcItems.setDirty(true);
 
 		} else if (source instanceof Link) {
-			// if it's a link try to get attached identity and assume that user wants
+			// if it's a link try to get attached identity and assume that user
+			// wants
 			// to see the users home page
 			Link userLink = (Link) source;
 			Object userObject = userLink.getUserObject();
@@ -533,22 +551,24 @@ public class ItemsController extends BasicController implements Activateable2 {
 				NewControllerFactory.getInstance().launch(bPath, ureq, getWindowControl());
 			}
 		}
-		
+
 		// Check if someone else added an item, reload everything
-		if (!isSameAllItems(feed.getFilteredItems(callback, ureq.getIdentity()))) {
-			resetItems(ureq, feed);
+		List<Item> items = feedManager.loadFilteredAndSortedItems(feedResource, callback, ureq.getIdentity());
+		if (!isSameAllItems(items)) {
+			resetItems(ureq, feedResource);
 		}
 	}
 
 	/**
 	 * @see org.olat.core.gui.control.DefaultController#event(org.olat.core.gui.UserRequest,
-	 *      org.olat.core.gui.control.Controller, org.olat.core.gui.control.Event)
+	 *      org.olat.core.gui.control.Controller,
+	 *      org.olat.core.gui.control.Event)
 	 */
 	protected void event(UserRequest ureq, Controller source, Event event) {
-		FeedManager feedManager = FeedManager.getInstance();
-		// reload feed for this event and make sure the updated feed object is in the view
-		Feed feed = feedManager.getFeed(feedResource);
-		vcItems.contextPut("feed", feed);
+		// reload feed for this event and make sure the updated feed object is
+		// in the view
+		feedResource = feedManager.loadFeed(feedResource);
+		accessibleItems = feedManager.loadFilteredAndSortedItems(feedResource, callback, ureq.getIdentity());
 
 		if (source == cmc) {
 			if (event.equals(CloseableModalController.CLOSE_MODAL_EVENT)) {
@@ -556,29 +576,28 @@ public class ItemsController extends BasicController implements Activateable2 {
 				cmc = null;
 				removeAsListenerAndDispose(itemFormCtr);
 				itemFormCtr = null;
-				// Check if this item has ever been added to the feed. If not, remove the temp dir
-				cleanupTmpItemMediaDir(currentItem, feed, feedManager);
+				// Check if this item has ever been added to the feed. If not,
+				// remove the temp dir
+				cleanupTmpItemMediaDir(currentItem);
 				// If there were no items and the user doesn't want to save the
 				// first item, go back to the decision whether to make the feed
 				// internally or subscribe to an external feed.
-				if (!feed.hasItems()) {
-					feedManager.updateFeedMode(null, feed);
+				if (!feedManager.hasItems(feedResource)) {
+					feedResource = feedManager.updateFeedMode(null, feedResource);
 					makeInternalAndExternalButtons();
 				}
-				//release lock
+				// release lock
 				feedManager.releaseLock(lock);
 			}
 		} else if (source == confirmDialogCtr && DialogBoxUIFactory.isYesEvent(event)) {
 			// The user confirmed that the item shall be deleted
 			Item item = (Item) ((DialogBoxController) source).getUserObject();
-			lock = feedManager.acquireLock(feed, item, getIdentity());
+			lock = feedManager.acquireLock(feedResource, item, getIdentity());
 			if (lock.isSuccess()) {
 				// remove the item from the naviCtr
 				naviCtr.remove(item);
-				// remove the item also from the helper (cached selection)
-				helper.removeItem(item);
 				// permanently remove item
-				feed = feedManager.remove(item, feed);
+				feedResource = feedManager.deleteItem(item);
 				// remove delete and edit buttons of this item
 				deleteButtons.remove(source);
 				for (Link editButton : editButtons) {
@@ -589,23 +608,24 @@ public class ItemsController extends BasicController implements Activateable2 {
 				}
 				// If the last item has been deleted, provide buttons for adding
 				// items manually or from an external source/feed.
-				if (!feed.hasItems()) {
+				if (!feedManager.hasItems(feedResource)) {
 					makeInternalAndExternalButtons();
 					// The subscription/feed url from the feed info is obsolete
 					fireEvent(ureq, ItemsController.FEED_INFO_IS_DIRTY_EVENT);
 				} else {
 					if (callback.mayEditItems() || callback.mayCreateItems()) {
-						createEditButtons(ureq, feed);
+						createEditButtons(ureq, feedResource);
 					}
-					createCommentsAndRatingsLinks(ureq, feed);
+					createCommentsAndRatingsLinks(ureq, feedResource);
 				}
 				vcItems.setDirty(true);
 				// in case we were in single item view, show all items
 				mainPanel.setContent(vcItems);
 				feedManager.releaseLock(lock);
-				lock = null;				
+				lock = null;
 				// do logging
-				ThreadLocalUserActivityLogger.log(FeedLoggingAction.FEED_ITEM_DELETE, getClass(), LoggingResourceable.wrap(item));
+				ThreadLocalUserActivityLogger.log(FeedLoggingAction.FEED_ITEM_DELETE, getClass(),
+						LoggingResourceable.wrap(item));
 
 			} else {
 				String fullName = userManager.getUserDisplayName(lock.getOwner());
@@ -616,58 +636,57 @@ public class ItemsController extends BasicController implements Activateable2 {
 			if (event.equals(Event.CHANGED_EVENT) || event.equals(Event.CANCELLED_EVENT)) {
 				if (event.equals(Event.CHANGED_EVENT)) {
 					FileElement mediaFile = currentItem.getMediaFile();
-					if (feedManager.getItemContainer(currentItem, feed) == null) {
+					if (feedManager.getItemContainer(currentItem) == null) {
 						// Ups, deleted in the meantime by someone else
 						// remove the item from the naviCtr
 						naviCtr.remove(currentItem);
-						// remove the item also from the helper (cached selection)
-						helper.removeItem(currentItem);
 					} else {
-						if (!feed.getItems().contains(currentItem)) {
-							// Add the modified item if it is not part of the feed
-							feed = feedManager.addItem(currentItem, mediaFile, feed);
-							if(feed == null) {
-								//the item could not be added, is not internal
-								feed = feedManager.getFeed(feedResource);
-								if(!feed.isInternal() && !feed.isExternal() && !feed.hasItems()) {
-									feed = feedManager.updateFeedMode(Boolean.FALSE, feed);
-									feed = feedManager.addItem(currentItem, mediaFile, feed);
+						if (!accessibleItems.contains(currentItem)) {
+							// Add the modified item if it is not part of the
+							// feed
+							feedResource = feedManager.createItem(feedResource, currentItem, mediaFile);
+							if (feedResource == null) {
+								// the item could not be added, is not internal
+								feedResource = feedManager.loadFeed(feedResource);
+								if (!feedResource.isInternal() && !feedResource.isExternal()
+										&& !feedManager.hasItems(feedResource)) {
+									feedResource = feedManager.updateFeedMode(Boolean.FALSE, feedResource);
+									feedResource = feedManager.createItem(feedResource, currentItem, mediaFile);
 								}
 							}
-							
-							if(feed != null) {
-								createButtonsForItem(ureq, feed, currentItem);
+
+							if (feedResource != null) {
+								createButtonsForItem(ureq, feedResource, currentItem);
 								createItemLink(currentItem);
 								// Add date component
 								String guid = currentItem.getGuid();
-								if(currentItem.getDate() != null) {
-									DateComponentFactory.createDateComponentWithYear("date." + guid, currentItem.getDate(), vcItems);
+								if (currentItem.getDate() != null) {
+									DateComponentFactory.createDateComponentWithYear("date." + guid,
+											currentItem.getDate(), vcItems);
 								}
 								// Add comments and rating
-								createCommentsAndRatingsLink(ureq, feed, currentItem);
+								createCommentsAndRatingsLink(ureq, feedResource, currentItem);
 								// add it to the navigation controller
 								naviCtr.add(currentItem);
-								// ... and also to the helper
-								helper.addItem(currentItem);
-								if (feed.getItems() != null && feed.getItems().size() == 1) {
-									// First item added, show feed url (for subscription)
+								accessibleItems = feedManager.loadFilteredAndSortedItems(feedResource, callback,
+										ureq.getIdentity());
+								if (accessibleItems != null && accessibleItems.size() == 1) {
+									// First item added, show feed url (for
+									// subscription)
 									fireEvent(ureq, ItemsController.FEED_INFO_IS_DIRTY_EVENT);
-									// Set the base URI of the feed for the current user. All users
+									// Set the base URI of the feed for the
+									// current user. All users
 									// have unique URIs.
 									helper.setURIs();
 								}
-								// do logging
-								ThreadLocalUserActivityLogger.log(FeedLoggingAction.FEED_ITEM_CREATE, getClass(), LoggingResourceable.wrap(currentItem));
 							}
 						} else {
 							// Write item file
-							feed = feedManager.updateItem(currentItem, mediaFile, feed);
-							// Update current item in the users view, replace in helper cache of
-							// current selected items.
-							helper.updateItem(currentItem); 
+							currentItem = feedManager.updateItem(currentItem, mediaFile);
 							// Do logging
-							ThreadLocalUserActivityLogger.log(FeedLoggingAction.FEED_ITEM_EDIT, getClass(), LoggingResourceable.wrap(currentItem));
-						}						
+							ThreadLocalUserActivityLogger.log(FeedLoggingAction.FEED_ITEM_EDIT, getClass(),
+									LoggingResourceable.wrap(currentItem));
+						}
 					}
 					vcItems.setDirty(true);
 					// if the current item is displayed, update the view
@@ -676,13 +695,16 @@ public class ItemsController extends BasicController implements Activateable2 {
 					}
 
 				} else if (event.equals(Event.CANCELLED_EVENT)) {
-					// Check if this item has ever been added to the feed. If not, remove the temp dir
-					cleanupTmpItemMediaDir(currentItem, feed, feedManager);
-					// If there were no items and the user doesn't want to save the
-					// first item, go back to the decision whether to make the feed
+					// Check if this item has ever been added to the feed. If
+					// not, remove the temp dir
+					cleanupTmpItemMediaDir(currentItem);
+					// If there were no items and the user doesn't want to save
+					// the
+					// first item, go back to the decision whether to make the
+					// feed
 					// internally or subscribe to an external feed.
-					if (!feed.hasItems()) {
-						feedManager.updateFeedMode(null, feed);
+					if (!feedManager.hasItems(feedResource)) {
+						feedResource = feedManager.updateFeedMode(null, feedResource);
 						makeInternalAndExternalButtons();
 					}
 				}
@@ -698,19 +720,16 @@ public class ItemsController extends BasicController implements Activateable2 {
 			}
 		} else if (source == naviCtr && event instanceof NavigationEvent) {
 			List<? extends Dated> selItems = ((NavigationEvent) event).getSelectedItems();
-			List<Item> items = new ArrayList<Item>();
+			List<Item> items = new ArrayList<>();
 			for (Dated item : selItems) {
 				if (item instanceof Item) {
 					items.add((Item) item);
 				}
 			}
-			// make sure items are sorted properly
-			Collections.sort(items, new ItemPublishDateComparator());
-			helper.setSelectedItems(items);
 			if (callback.mayEditItems() || callback.mayCreateItems()) {
-				createEditButtons(ureq, feed);
+				createEditButtons(ureq, feedResource);
 			}
-			createCommentsAndRatingsLinks(ureq, feed);
+			createCommentsAndRatingsLinks(ureq, feedResource);
 			vcItems.setDirty(true);
 			mainPanel.setContent(vcItems);
 
@@ -718,76 +737,80 @@ public class ItemsController extends BasicController implements Activateable2 {
 			if (event == Event.BACK_EVENT) {
 				mainPanel.setContent(vcItems);
 			}
-			
+
 		} else if (source instanceof UserCommentsAndRatingsController) {
 			UserCommentsAndRatingsController commentsRatingsCtr = (UserCommentsAndRatingsController) source;
 			if (event == UserCommentsAndRatingsController.EVENT_COMMENT_LINK_CLICKED) {
 				// go to details page
 				Item item = (Item) commentsRatingsCtr.getUserObject();
-				item = feedManager.getItem(feed, item.getGuid());	
-				if(item != null) {
+				if (item != null) {
 					ItemController myItemCtr = displayItemController(ureq, item);
-					List<ContextEntry> entries = BusinessControlFactory.getInstance().createCEListFromResourceType(ItemController.ACTIVATION_KEY_COMMENTS);
+					List<ContextEntry> entries = BusinessControlFactory.getInstance()
+							.createCEListFromResourceType(ItemController.ACTIVATION_KEY_COMMENTS);
 					myItemCtr.activate(ureq, entries, null);
 				}
 			}
 		}
-		
-		// Check if someone else added an item, reload everything
-		if (feed == null) {
-			//do something
-		} else if(!isSameAllItems(feed.getFilteredItems(callback, getIdentity()))) {
-			resetItems(ureq, feed);
+
+		// reload everything
+		if (feedResource != null) {
+			resetItems(ureq, feedResource);
 		}
 	}
 
 	/**
 	 * Private helper to remove any temp media files created for this feed
+	 * 
 	 * @param tmpItem
-	 * @param feed
-	 * @param feedManager
 	 */
-	private void cleanupTmpItemMediaDir(Item tmpItem, Feed feed, FeedManager feedManager) {
-		// Add GUID null check to not accidentally delete the entire feed directory
-		// in case there is somewhere a programming error
-		if (!feed.getItems().contains(tmpItem) && tmpItem.getGuid() != null) {
-			VFSContainer itemContainer = feedManager.getItemContainer(tmpItem, feed);
-			if (itemContainer != null) {
-				itemContainer.delete();
-			}
-		}					
+	private void cleanupTmpItemMediaDir(Item tmpItem) {
+		String guid = tmpItem.getGuid();
+		if (guid == null) return;
+		
+		// delete the dir only if the item is not saved.
+		Long key = tmpItem.getKey();
+		if (key == null) {
+			feedManager.deleteItem(tmpItem);
+		}
 	}
 
 	/**
-	 * @param controller The <code>FormBasicController</code> to be displayed in
-	 *          the modal dialog.
+	 * @param controller
+	 *            The <code>FormBasicController</code> to be displayed in the
+	 *            modal dialog.
 	 */
 	private void activateModalDialog(FormBasicController controller, String title) {
 		listenTo(controller);
-		cmc = new CloseableModalController(getWindowControl(), translate("close"), controller.getInitialComponent(), true, title);
+		cmc = new CloseableModalController(getWindowControl(), translate("close"), controller.getInitialComponent(),
+				true, title);
 		listenTo(cmc);
 		cmc.activate();
 	}
 
 	/**
 	 * Sets the items view dirty.
+	 * 
 	 * @param ureq
-	 * @param feed the current feed
+	 * @param feed
+	 *            the current feed
 	 */
 	public void resetItems(UserRequest ureq, Feed feed) {
-		FeedManager.getInstance().loadItems(feed);
-		List<Item> items = feed.getFilteredItems(callback, ureq.getIdentity());
-		helper.setSelectedItems(items);
-		naviCtr.setDatedObjects(items);
-		setAllItemIds(items);
+		feedResource = feedManager.loadFeed(feedResource);
+		accessibleItems = feedManager.loadFilteredAndSortedItems(feed, callback, ureq.getIdentity());
+		vcItems.contextPut("feed", feedResource);
+		vcItems.contextPut("items", accessibleItems);
+
+		naviCtr.setDatedObjects(accessibleItems);
+		setAllItemIds(accessibleItems);
 		// Add item details page link
-		createItemLinks(feed);
+		createItemLinks(feedResource);
 		// Add item user comments link and rating
 		if (displayConfig.isShowCRInMinimized()) {
-			createCommentsAndRatingsLinks(ureq, feed);
+			createCommentsAndRatingsLinks(ureq, feedResource);
 		}
 		// Add date components
-		createDateComponents(feed);
+		createDateComponents(feedResource);
+
 		vcItems.setDirty(true);
 	}
 
@@ -801,26 +824,31 @@ public class ItemsController extends BasicController implements Activateable2 {
 		removeAsListenerAndDispose(itemCtr);
 		Link editButton = getButtonByUserObject(item, editButtons);
 		Link deleteButton = getButtonByUserObject(item, deleteButtons);
-		Controller artefactLink =  getArtefactLinkByUserObject(item);
-		FeedManager feedManager = FeedManager.getInstance();
-		Feed feed = feedManager.getFeed(feedResource);
-		itemCtr = new ItemController(ureq, getWindowControl(), item, feed, helper, uiFactory, callback, editButton, deleteButton, artefactLink, displayConfig);
-		listenTo(itemCtr);
-		mainPanel.setContent(itemCtr.getInitialComponent());
+		Controller artefactLink = getArtefactLinkByUserObject(item);
+		Feed feed = feedManager.loadFeed(feedResource);
+		item = feedManager.loadItem(item.getKey());
+		if (item != null) {
+			itemCtr = new ItemController(ureq, getWindowControl(), item, feed, helper, uiFactory, callback, editButton,
+					deleteButton, artefactLink, displayConfig);
+			listenTo(itemCtr);
+			mainPanel.setContent(itemCtr.getInitialComponent());
+		}
 		return itemCtr;
 	}
 
 	@Override
 	public void activate(UserRequest ureq, List<ContextEntry> entries, StateEntry state) {
-		if(entries == null || entries.isEmpty() || feedResource == null) return;
-		
+		if (entries == null || entries.isEmpty() || feedResource == null)
+			return;
+
+		Item item = null;
 		String itemId = entries.get(0).getOLATResourceable().getResourceableTypeName();
 		if(itemId != null && itemId.startsWith("item=")) {
 			itemId = itemId.substring(5, itemId.length());
+			Long itemKey = Long.parseLong(itemId);
+			item = FeedManager.getInstance().loadItem(itemKey);
 		}
-		int index = feedResource.getItemIds().indexOf(itemId);
-		if (index >= 0) {
-			Item item = feedResource.getItems().get(index);
+		if (item != null) {
 			activate(ureq, item);
 		}
 	}
@@ -850,7 +878,7 @@ public class ItemsController extends BasicController implements Activateable2 {
 		}
 		return result;
 	}
-	
+
 	private Controller getArtefactLinkByUserObject(Item item) {
 		Controller result = null;
 		if (artefactLinks != null && artefactLinks.containsKey(item)) {
@@ -858,16 +886,16 @@ public class ItemsController extends BasicController implements Activateable2 {
 		}
 		return result;
 	}
-	
+
 	private class ItemId {
 		private final String guid;
 		private final Date lastModification;
-		
+
 		public ItemId(Item item) {
 			guid = item.getGuid();
 			lastModification = item.getLastModified();
 		}
-		
+
 		@Override
 		public int hashCode() {
 			return guid.hashCode() + (lastModification == null ? -483 : lastModification.hashCode());
@@ -875,15 +903,15 @@ public class ItemsController extends BasicController implements Activateable2 {
 
 		@Override
 		public boolean equals(Object obj) {
-			if(this == obj) {
+			if (this == obj) {
 				return true;
 			}
-			if(obj instanceof ItemId) {
-				ItemId id = (ItemId)obj;
-				return guid.equals(id.guid) && ((lastModification == null && id.lastModification == null) ||
-						(lastModification != null && lastModification.equals(id.lastModification)));
+			if (obj instanceof ItemId) {
+				ItemId id = (ItemId) obj;
+				return guid.equals(id.guid) && ((lastModification == null && id.lastModification == null)
+						|| (lastModification != null && lastModification.equals(id.lastModification)));
 			}
-			
+
 			return false;
 		}
 	}
diff --git a/src/main/java/org/olat/modules/webFeed/ui/blog/BlogPostFormController.java b/src/main/java/org/olat/modules/webFeed/ui/blog/BlogPostFormController.java
index 0d16873253f..4a1e27364aa 100644
--- a/src/main/java/org/olat/modules/webFeed/ui/blog/BlogPostFormController.java
+++ b/src/main/java/org/olat/modules/webFeed/ui/blog/BlogPostFormController.java
@@ -41,9 +41,9 @@ import org.olat.core.gui.translator.Translator;
 import org.olat.core.util.vfs.Quota;
 import org.olat.core.util.vfs.VFSContainer;
 import org.olat.core.util.vfs.callbacks.FullAccessWithQuotaCallback;
-import org.olat.modules.webFeed.managers.FeedManager;
-import org.olat.modules.webFeed.models.Feed;
-import org.olat.modules.webFeed.models.Item;
+import org.olat.modules.webFeed.Feed;
+import org.olat.modules.webFeed.Item;
+import org.olat.modules.webFeed.manager.FeedManager;
 
 /**
  * Form controller for blog posts.
@@ -69,13 +69,13 @@ public class BlogPostFormController extends FormBasicController {
 	 * @param ureq
 	 * @param control
 	 */
-	public BlogPostFormController(UserRequest ureq, WindowControl control, Item post, Feed blog, Translator translator) {
+	public BlogPostFormController(UserRequest ureq, WindowControl control, Item item, Feed feed, Translator translator) {
 		super(ureq, control);
-		this.post = post;
-		this.currentlyDraft = post.isDraft();
-		this.baseDir = FeedManager.getInstance().getItemContainer(post, blog);
+		this.post = item;
+		this.currentlyDraft = item.isDraft();
+		this.baseDir = FeedManager.getInstance().getItemContainer(item);
 		if(baseDir.getLocalSecurityCallback() == null) {
-			Quota quota = FeedManager.getInstance().getQuota(blog.getResource());
+			Quota quota = FeedManager.getInstance().getQuota(feed);
 			baseDir.setLocalSecurityCallback(new FullAccessWithQuotaCallback(quota));
 		}
 		setTranslator(translator);
@@ -97,7 +97,8 @@ public class BlogPostFormController extends FormBasicController {
 	protected void formOK(UserRequest ureq) {
 		// Update post. It is saved by the manager.
 		setValues();
-		if(!currentlyDraft || post.getModifierKey() > 0) {
+		Long modifierKey = post.getModifierKey();
+		if(!currentlyDraft || modifierKey != null) {
 			post.setModifierKey(ureq.getIdentity().getKey());
 		//fxdiff BAKS-18
 		} else if(currentlyDraft && !ureq.getIdentity().getKey().equals(post.getAuthorKey())) {
@@ -134,7 +135,8 @@ public class BlogPostFormController extends FormBasicController {
 	protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
 		if (source == draftLink) {
 			setValues();
-			if(!currentlyDraft || post.getModifierKey() > 0) {
+			Long modifierKey = post.getModifierKey();
+			if(!currentlyDraft ||  modifierKey != null) {
 				post.setModifierKey(ureq.getIdentity().getKey());
 			//fxdiff BAKS-18
 			} else if(currentlyDraft && !ureq.getIdentity().getKey().equals(post.getAuthorKey())) {
diff --git a/src/main/java/org/olat/modules/webFeed/ui/blog/BlogUIFactory.java b/src/main/java/org/olat/modules/webFeed/ui/blog/BlogUIFactory.java
index ea471ed486e..8a6fe108016 100644
--- a/src/main/java/org/olat/modules/webFeed/ui/blog/BlogUIFactory.java
+++ b/src/main/java/org/olat/modules/webFeed/ui/blog/BlogUIFactory.java
@@ -33,8 +33,8 @@ import org.olat.course.ICourse;
 import org.olat.course.nodes.AbstractFeedCourseNode;
 import org.olat.course.nodes.feed.blog.BlogNodeEditController;
 import org.olat.course.run.userview.UserCourseEnvironment;
-import org.olat.modules.webFeed.models.Feed;
-import org.olat.modules.webFeed.models.Item;
+import org.olat.modules.webFeed.Feed;
+import org.olat.modules.webFeed.Item;
 import org.olat.modules.webFeed.ui.FeedMainController;
 import org.olat.modules.webFeed.ui.FeedUIFactory;
 
diff --git a/src/main/java/org/olat/modules/webFeed/ui/blog/_content/info.html b/src/main/java/org/olat/modules/webFeed/ui/blog/_content/info.html
index c38ba3ded1d..4d79c1f80f1 100644
--- a/src/main/java/org/olat/modules/webFeed/ui/blog/_content/info.html
+++ b/src/main/java/org/olat/modules/webFeed/ui/blog/_content/info.html
@@ -6,19 +6,19 @@
 		<i class="o_icon o_icon-fw o_icon o_FileResource-BLOG_icon"></i> 
 		$r.escapeHtml($!feed.getTitle())
 	</h2>
-	#if ($helper.getImageUrl() && $helper.getImageUrl() != "")
-		<img class="o_media" src="$helper.getImageUrl()?thumbnail=180x121" alt="Blog Image" />
+	#if ($helper.getImageUrl($feed) && $helper.getImageUrl($feed) != "")
+		<img class="o_media" src="$helper.getImageUrl($feed)?thumbnail=180x121" alt="Blog Image" />
 	#end
 	<div class="o_block">
 		#if ($!feed.getAuthor())
-			<div class="o_author">$r.translate("feed.author"): $r.escapeHtml($helper.getFeedAuthor())</div>
+			<div class="o_author">$r.translate("feed.author"): $r.escapeHtml($feed.getAuthor())</div>
 		#end
-		<div class="o_date">$r.translate("feed.last.change") $!helper.getLastModified()	</div>
-		#if ($helper.getFeedDescriptionForBrowser() != "")
-		<div class="o_desc o_info">$!helper.getFeedDescriptionForBrowser()</div>
+		<div class="o_date">$r.translate("feed.last.change") $!helper.getLastModified($feed)</div>
+		#if ($feed.getDescription() != "")
+		<div class="o_desc o_info">$!helper.getFeedDescriptionForBrowser($feed)</div>
 		#end
 	
-		#if ( !$feed.isUndefined() )
+		#if (!$feed.isUndefined())
 			<div class="o_subscription">
 				<a href="$!helper.getFeedUrl()" class="o_nowrap" target="_blank">
 					<i class="o_icon o_icon-fw o_icon_rss"></i> 
diff --git a/src/main/java/org/olat/modules/webFeed/ui/blog/_content/posts.html b/src/main/java/org/olat/modules/webFeed/ui/blog/_content/posts.html
index 09ee86afc71..96c902e8f26 100644
--- a/src/main/java/org/olat/modules/webFeed/ui/blog/_content/posts.html
+++ b/src/main/java/org/olat/modules/webFeed/ui/blog/_content/posts.html
@@ -4,10 +4,9 @@
 		$r.render("feed.add.item")
 	</div>
 #end
-##<h4 class="o_title">$r.translate("blog.posts") ($helper.itemsCount($callback))</h4>
 <div class="clearfix"></div>
 
-#if (!$feed.hasItems())
+#if ($items.size() == 0)
 	<div class="o_blog_no_posts o_important">
 		<p>
 			<i class="o_icon o_icon_warn"></i>
@@ -23,8 +22,7 @@
 		#end
 	</div>
 #else
-	<!-- loop over episodes -->
-	#foreach( $post in $helper.getItems($callback) ) 	
+	#foreach( $post in $helper.getItemsOnPage($items) ) 	
 		<div class="o_post o_block_with_datecomp clearfix $!post.extraCSSClass()">
 			<div class="o_head">
 				#if (( $helper.isAuthor($post) && !$post.isDraft()) && $feed.isInternal() && $r.available("feed.artefact.item.$post.getGuid()"))
@@ -93,7 +91,7 @@
 	#end
 	
 	<ul class="pagination">
-		#if ( $helper.hasOlderItems() )
+		#if ( $helper.hasOlderItems($items) )
 		<li>
 			$r.render("feed.older.items")
 		</li>
diff --git a/src/main/java/org/olat/modules/webFeed/ui/blog/_i18n/LocalStrings_ar.properties b/src/main/java/org/olat/modules/webFeed/ui/blog/_i18n/LocalStrings_ar.properties
index 8b79c23e5d4..0587a1a86b4 100644
--- a/src/main/java/org/olat/modules/webFeed/ui/blog/_i18n/LocalStrings_ar.properties
+++ b/src/main/java/org/olat/modules/webFeed/ui/blog/_i18n/LocalStrings_ar.properties
@@ -4,7 +4,6 @@ blog.form.content=\u0627\u0644\u0645\u062D\u062A\u0648\u0649
 blog.has.no.episodes=\u0644\u0627 \u062A\u0648\u062C\u062F \u0645\u062F\u062E\u0644\u0627\u062A \u0641\u0649 \u0647\u0630\u0647 \u0627\u0644\u0645\u062F\u0648\u0646\u0629.
 blog.internal.or.external=\u064A\u0645\u0643\u0646\u0643 \u0625\u0645\u0627 \u0625\u0646\u0634\u0627\u0621 \u0645\u062F\u062E\u0644\u0627\u062A\u0643 \u0627\u0644\u062E\u0627\u0635\u0629 \u0623\u0648 \u062A\u0636\u0645\u064A\u0646 \u0645\u062F\u0648\u0646\u0629 \u062E\u0627\u0631\u062C\u064A\u0629\u060C \u0648\u0639\u0646\u062F \u062A\u0636\u0645\u064A\u0646 \u0645\u062F\u0648\u0646\u0629 \u062E\u0627\u0631\u062C\u064A\u0629 \u0641\u0625\u0646 \u0643\u0644 \u0627\u0644\u0645\u062F\u062E\u0644\u0627\u062A \u0633\u064A\u062A\u0645 \u0639\u0631\u0636\u0647\u0627.
 blog.is.being.edited.by=.{0} \u0647\u0630\u0647 \u0627\u0644\u0645\u062F\u0648\u0646\u0629 \u064A\u062A\u0645 \u062A\u0639\u062F\u064A\u0644\u0647\u0627 \u0628\u0648\u0627\u0633\u0637\u0629
-blog.posts=\u0627\u0644\u0645\u062F\u062E\u0644\u0627\u062A
 blog.subscribe.to.this.feed=\u0627\u0634\u062A\u0631\u0643 \u0641\u0649 \u0647\u0630\u0647 \u0627\u0644\u0645\u062F\u0648\u0646\u0629
 
 
diff --git a/src/main/java/org/olat/modules/webFeed/ui/blog/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/modules/webFeed/ui/blog/_i18n/LocalStrings_de.properties
index 78b9916d543..a6fe2e763f6 100644
--- a/src/main/java/org/olat/modules/webFeed/ui/blog/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/modules/webFeed/ui/blog/_i18n/LocalStrings_de.properties
@@ -3,7 +3,6 @@ blog.has.no.episodes = F\u00FCr diesen Blog sind keine Eintr\u00E4ge vorhanden.
 blog.is.being.edited.by = Der Blog wird bereits von {0} bearbeitet.
 blog.episode.is.being.edited.by = Der Eintrag wird bereits von {0} bearbeitet.
 blog.subscribe.to.this.feed = Diesen Blog abonnieren
-blog.posts = Eintr\u00E4ge
 blog.form.content = Inhalt
 
 feed.author = Von
diff --git a/src/main/java/org/olat/modules/webFeed/ui/blog/_i18n/LocalStrings_el.properties b/src/main/java/org/olat/modules/webFeed/ui/blog/_i18n/LocalStrings_el.properties
index 665e07946aa..80c9e9d9e91 100644
--- a/src/main/java/org/olat/modules/webFeed/ui/blog/_i18n/LocalStrings_el.properties
+++ b/src/main/java/org/olat/modules/webFeed/ui/blog/_i18n/LocalStrings_el.properties
@@ -4,7 +4,6 @@ blog.form.content=\u03A0\u03B5\u03C1\u03B9\u03B5\u03C7\u03CC\u03BC\u03B5\u03BD\u
 blog.has.no.episodes=\u0394\u03B5\u03BD \u03C5\u03C0\u03AC\u03C1\u03C7\u03BF\u03C5\u03BD \u03B5\u03B9\u03C3\u03B1\u03B3\u03C9\u03B3\u03AD\u03C2 \u03B3\u03B9\u03B1 \u03C4\u03BF blog \u03B1\u03C5\u03C4\u03CC.
 blog.internal.or.external=\u0388\u03C7\u03B5\u03C4\u03B5 \u03C4\u03B7 \u03B4\u03C5\u03BD\u03B1\u03C4\u03CC\u03C4\u03B7\u03C4\u03B1 \u03BD\u03B1 \u03B4\u03B7\u03BC\u03B9\u03BF\u03C5\u03C1\u03B3\u03AE\u03C3\u03B5\u03C4\u03B5 \u03C4\u03B9\u03C2 \u03C0\u03C1\u03BF\u03C3\u03C9\u03C0\u03B9\u03BA\u03AD\u03C2 \u03C3\u03B1\u03C2 \u03B5\u03B9\u03C3\u03B1\u03B3\u03C9\u03B3\u03AD\u03C2 \u03B5\u03AF\u03C4\u03B5 \u03BD\u03B1 \u03C3\u03C5\u03BC\u03C0\u03B5\u03C1\u03B9\u03BB\u03AC\u03B2\u03B5\u03C4\u03B5 \u03BA\u03AC\u03C0\u03BF\u03B9\u03BF \u03B5\u03BE\u03C9\u03C4\u03B5\u03C1\u03B9\u03BA\u03CC blog. \u038C\u03C4\u03B1\u03BD \u03C3\u03C5\u03BC\u03C0\u03B5\u03C1\u03B9\u03BB\u03B1\u03BC\u03B2\u03AC\u03BD\u03B5\u03C4\u03B5 \u03B5\u03BE\u03C9\u03C4\u03B5\u03C1\u03B9\u03BA\u03CC blog, \u03B5\u03BC\u03C6\u03B1\u03BD\u03AF\u03B6\u03BF\u03BD\u03C4\u03B1\u03B9 \u03BF\u03B9 \u03B5\u03B9\u03C3\u03B1\u03B3\u03C9\u03B3\u03AD\u03C2 \u03C4\u03BF\u03C5.
 blog.is.being.edited.by=\u03A4\u03BF blog \u03B1\u03C5\u03C4\u03CC \u03C4\u03C1\u03BF\u03C0\u03BF\u03C0\u03BF\u03B9\u03B5\u03AF\u03C4\u03B1\u03B9 \u03B1\u03C0\u03BF \u03C4\u03BF\u03BD/\u03C4\u03B7\u03BD {0}.
-blog.posts=\u0395\u03B9\u03C3\u03B1\u03B3\u03C9\u03B3\u03AD\u03C2
 blog.subscribe.to.this.feed=\u0395\u03B3\u03B3\u03C1\u03B1\u03C6\u03B5\u03AF\u03C4\u03B5 \u03C3\u03B5 \u03B1\u03C5\u03C4\u03CC \u03C4\u03BF blog
 
 
diff --git a/src/main/java/org/olat/modules/webFeed/ui/blog/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/modules/webFeed/ui/blog/_i18n/LocalStrings_en.properties
index 1c95ae12751..6fa75e138cb 100644
--- a/src/main/java/org/olat/modules/webFeed/ui/blog/_i18n/LocalStrings_en.properties
+++ b/src/main/java/org/olat/modules/webFeed/ui/blog/_i18n/LocalStrings_en.properties
@@ -4,7 +4,6 @@ blog.form.content=Content
 blog.has.no.episodes=There are no entries for this blog.
 blog.internal.or.external=You can either create your own entries or include an external blog. When including an external blog its entries will be displayed.
 blog.is.being.edited.by=This blog is being edited by {0}.
-blog.posts=Entries
 blog.subscribe.to.this.feed=Subscribe to this blog
 
 
diff --git a/src/main/java/org/olat/modules/webFeed/ui/blog/_i18n/LocalStrings_fr.properties b/src/main/java/org/olat/modules/webFeed/ui/blog/_i18n/LocalStrings_fr.properties
index 452ba959fe3..7af050cc49b 100644
--- a/src/main/java/org/olat/modules/webFeed/ui/blog/_i18n/LocalStrings_fr.properties
+++ b/src/main/java/org/olat/modules/webFeed/ui/blog/_i18n/LocalStrings_fr.properties
@@ -4,7 +4,6 @@ blog.form.content=Contenu
 blog.has.no.episodes=Il n'y a pas d'entr\u00E9es dans ce blogue.
 blog.internal.or.external=Vous pouvez ou bien cr\u00E9er vous-m\u00EAmes de nouvelles entr\u00E9es ou int\u00E9grer un blogue externe. Si vous int\u00E9grez un blogue, les billets de ce blogue seront affich\u00E9es.
 blog.is.being.edited.by=Ce blogue est en train d'\u00EAtre \u00E9dit\u00E9 par {0}.
-blog.posts=Entr\u00E9es
 blog.subscribe.to.this.feed=S'abonner \u00E0 ce blogue
 
 
diff --git a/src/main/java/org/olat/modules/webFeed/ui/blog/_i18n/LocalStrings_it.properties b/src/main/java/org/olat/modules/webFeed/ui/blog/_i18n/LocalStrings_it.properties
index 72e4efc6c68..c291ce7602e 100644
--- a/src/main/java/org/olat/modules/webFeed/ui/blog/_i18n/LocalStrings_it.properties
+++ b/src/main/java/org/olat/modules/webFeed/ui/blog/_i18n/LocalStrings_it.properties
@@ -4,7 +4,6 @@ blog.form.content=Contenuto
 blog.has.no.episodes=Non ci sono entrate per questo blog.
 blog.internal.or.external=Pu\u00F2 creare delle nuove entrate oppure integrare un blog esterno. Integrando un blog esterno saranno visibili le entrate corrispondenti.
 blog.is.being.edited.by=Questo blog viene attualmente editato da {0}.
-blog.posts=Entrate
 blog.subscribe.to.this.feed=Abbonare questo blog
 
 
diff --git a/src/main/java/org/olat/modules/webFeed/ui/blog/_i18n/LocalStrings_jp.properties b/src/main/java/org/olat/modules/webFeed/ui/blog/_i18n/LocalStrings_jp.properties
index b76cbc494bd..f0aee072c83 100644
--- a/src/main/java/org/olat/modules/webFeed/ui/blog/_i18n/LocalStrings_jp.properties
+++ b/src/main/java/org/olat/modules/webFeed/ui/blog/_i18n/LocalStrings_jp.properties
@@ -4,7 +4,6 @@ blog.form.content=\u30B3\u30F3\u30C6\u30F3\u30C4
 blog.has.no.episodes=\u3053\u306E\u30D6\u30ED\u30B0\u306B\u306F\u3001\u30A8\u30F3\u30C8\u30EA\u304C\u3042\u308A\u307E\u305B\u3093\u3002
 blog.internal.or.external=\u3042\u306A\u305F\u306F\u3001\u81EA\u5206\u306E\u30A8\u30F3\u30C8\u30EA\u3092\u4F5C\u6210\u3059\u308B\u3001\u307E\u305F\u306F\u5916\u90E8\u30D6\u30ED\u30B0\u3092\u542B\u3081\u308B\u3053\u3068\u304C\u3067\u304D\u307E\u3059\u3002\u5916\u90E8\u30D6\u30ED\u30B0\u3092\u542B\u3081\u308B\u5834\u5408\u3001\u305D\u306E\u30A8\u30F3\u30C8\u30EA\u304C\u8868\u793A\u3055\u308C\u307E\u3059\u3002
 blog.is.being.edited.by=\u3053\u306E\u30D6\u30ED\u30B0\u306F\u3001{0} \u306B\u3088\u308A\u7DE8\u96C6\u3055\u308C\u3066\u3044\u307E\u3059\u3002
-blog.posts=\u30A8\u30F3\u30C8\u30EA
 blog.subscribe.to.this.feed=\u3053\u306E\u30D6\u30ED\u30B0\u3092\u8CFC\u8AAD\u3059\u308B
 
 
diff --git a/src/main/java/org/olat/modules/webFeed/ui/blog/_i18n/LocalStrings_nl_NL.properties b/src/main/java/org/olat/modules/webFeed/ui/blog/_i18n/LocalStrings_nl_NL.properties
index a467ee7c5aa..d67fa153416 100644
--- a/src/main/java/org/olat/modules/webFeed/ui/blog/_i18n/LocalStrings_nl_NL.properties
+++ b/src/main/java/org/olat/modules/webFeed/ui/blog/_i18n/LocalStrings_nl_NL.properties
@@ -4,7 +4,6 @@ blog.form.content=Inhoud
 blog.has.no.episodes=Er zijn geen bijdragen voor dit blog.
 blog.internal.or.external=U kunt uw eigen bijdragen maken of een naar een extern blog verwijzen. Wanneer u naar een extern blog verwijst worden de bijdragen daarvan weergegeven.
 blog.is.being.edited.by=Dit blog wordt bewerkt door {0}.
-blog.posts=Bijdragen
 blog.subscribe.to.this.feed=Op deze blog abboneren
 
 
diff --git a/src/main/java/org/olat/modules/webFeed/ui/blog/_i18n/LocalStrings_pl.properties b/src/main/java/org/olat/modules/webFeed/ui/blog/_i18n/LocalStrings_pl.properties
index e7355545092..2d53fe9e1ac 100644
--- a/src/main/java/org/olat/modules/webFeed/ui/blog/_i18n/LocalStrings_pl.properties
+++ b/src/main/java/org/olat/modules/webFeed/ui/blog/_i18n/LocalStrings_pl.properties
@@ -4,7 +4,6 @@ blog.form.content=Zawarto\u015B\u0107
 blog.has.no.episodes=W tym blogu nie ma jeszcze \u017Cadnych wpis\u00F3w.
 blog.internal.or.external=Mo\u017Cesz umieszcza\u0107 w\u0142asne wpisy lub do\u0142\u0105czy\u0107 zewn\u0119trzny blog i wy\u015Bwietla\u0107 wpisy w nim zgromadzone.
 blog.is.being.edited.by=Ten blog jest edytowany przez {0}.
-blog.posts=Wpisy
 blog.subscribe.to.this.feed=Subskrybuj ten blog
 
 
diff --git a/src/main/java/org/olat/modules/webFeed/ui/blog/_i18n/LocalStrings_pt_BR.properties b/src/main/java/org/olat/modules/webFeed/ui/blog/_i18n/LocalStrings_pt_BR.properties
index e2653c7831c..ab53a42c7db 100644
--- a/src/main/java/org/olat/modules/webFeed/ui/blog/_i18n/LocalStrings_pt_BR.properties
+++ b/src/main/java/org/olat/modules/webFeed/ui/blog/_i18n/LocalStrings_pt_BR.properties
@@ -4,7 +4,6 @@ blog.form.content=Conte\u00FAdo
 blog.has.no.episodes=N\u00E3o existem entradas para este blog.
 blog.internal.or.external=Voc\u00EA pode criar suas pr\u00F3prias entradas ou incluir um blog externo. Ao incluir um blog externo suas entradas ser\u00E3o exibidas.
 blog.is.being.edited.by=Este blog est\u00E1 sendo editado por {0}.
-blog.posts=Entradas
 blog.subscribe.to.this.feed=Inscrever-se neste blog
 
 
diff --git a/src/main/java/org/olat/modules/webFeed/ui/blog/_i18n/LocalStrings_ru.properties b/src/main/java/org/olat/modules/webFeed/ui/blog/_i18n/LocalStrings_ru.properties
index a787c2c2d74..f54286fecb9 100644
--- a/src/main/java/org/olat/modules/webFeed/ui/blog/_i18n/LocalStrings_ru.properties
+++ b/src/main/java/org/olat/modules/webFeed/ui/blog/_i18n/LocalStrings_ru.properties
@@ -4,7 +4,6 @@ blog.form.content=\u0421\u043E\u0434\u0435\u0440\u0436\u0438\u043C\u043E\u0435
 blog.has.no.episodes=\u041D\u0435\u0442 \u0437\u0430\u043F\u0438\u0441\u0435\u0439 \u0434\u043B\u044F \u044D\u0442\u043E\u0433\u043E \u0431\u043B\u043E\u0433\u0430.
 blog.internal.or.external=\u0412\u044B \u043C\u043E\u0436\u0435\u0442\u0435 \u0441\u043E\u0437\u0434\u0430\u0432\u0430\u0442\u044C \u0441\u0432\u043E\u0438 \u0441\u043E\u0431\u0441\u0442\u0432\u0435\u043D\u043D\u044B\u0435 \u0437\u0430\u043F\u0438\u0441\u0438 \u0438\u043B\u0438 \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0438\u0442\u044C \u0432\u043D\u0435\u0448\u043D\u0438\u0439 \u0431\u043B\u043E\u0433. \u041F\u0440\u0438 \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u0438 \u0432\u043D\u0435\u0448\u043D\u0435\u0433\u043E \u0431\u043B\u043E\u0433 \u0435\u0433\u043E \u0437\u0430\u043F\u0438\u0441\u0438 \u0431\u0443\u0434\u0443\u0442 \u043F\u043E\u043A\u0430\u0437\u0430\u043D\u044B. \u0427\u0442\u043E \u0431\u044B \u0432\u044B \u0445\u043E\u0442\u0435\u043B\u0438 \u0441\u0434\u0435\u043B\u0430\u0442\u044C?
 blog.is.being.edited.by=\u042D\u0442\u043E\u0442 \u0431\u043B\u043E\u0433 \u0441\u0435\u0439\u0447\u0430\u0441 \u0440\u0435\u0434\u0430\u043A\u0442\u0438\u0440\u0443\u0435\u0442\u0441\u044F \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u0435\u043C {0}.
-blog.posts=\u0417\u0430\u043F\u0438\u0441\u0438
 blog.subscribe.to.this.feed=\u041F\u043E\u0434\u043F\u0438\u0441\u0430\u0442\u044C\u0441\u044F \u043D\u0430 \u044D\u0442\u043E\u0442 \u0431\u043B\u043E\u0433
 
 
diff --git a/src/main/java/org/olat/modules/webFeed/ui/blog/_i18n/LocalStrings_zh_CN.properties b/src/main/java/org/olat/modules/webFeed/ui/blog/_i18n/LocalStrings_zh_CN.properties
index 337368da662..afb354a1425 100644
--- a/src/main/java/org/olat/modules/webFeed/ui/blog/_i18n/LocalStrings_zh_CN.properties
+++ b/src/main/java/org/olat/modules/webFeed/ui/blog/_i18n/LocalStrings_zh_CN.properties
@@ -4,7 +4,6 @@ blog.form.content=\u5185\u5BB9
 blog.has.no.episodes=\u6B64\u535A\u5BA2\u4E2D\u65E0\u9879\u76EE\u3002
 blog.internal.or.external=\u60A8\u65E2\u53EF\u4EE5\u521B\u5EFA\u60A8\u81EA\u5DF1\u7684\u535A\u5BA2\u6216\u8005\u5BFC\u5165\u5916\u90E8\u7684\u535A\u5BA2\uFF0C\u5F53\u5BFC\u5165\u5916\u90E8\u535A\u5BA2\u65F6\uFF0C\u5B83\u7684\u6807\u9898\u4F1A\u663E\u793A\u51FA\u6765\u3002
 blog.is.being.edited.by={0}\u6B63\u5728\u7F16\u8F91\u6B64\u535A\u5BA2\u3002
-blog.posts=\u535A\u6587
 blog.subscribe.to.this.feed=\u8BA2\u9605\u6B64\u535A\u5BA2
 
 
diff --git a/src/main/java/org/olat/modules/webFeed/ui/blog/_i18n/LocalStrings_zh_TW.properties b/src/main/java/org/olat/modules/webFeed/ui/blog/_i18n/LocalStrings_zh_TW.properties
index 7115aa4dbdc..e45e6caad1d 100644
--- a/src/main/java/org/olat/modules/webFeed/ui/blog/_i18n/LocalStrings_zh_TW.properties
+++ b/src/main/java/org/olat/modules/webFeed/ui/blog/_i18n/LocalStrings_zh_TW.properties
@@ -4,7 +4,6 @@ blog.form.content=\u5167\u5BB9
 blog.has.no.episodes=\u9019\u500B\u90E8\u843D\u683C\u6C92\u6709\u9805\u76EE\u3002
 blog.internal.or.external=\u60A8\u53EF\u4EE5\u5EFA\u7ACB\u60A8\u81EA\u5DF1\u7684\u9805\u76EE\u6216\u5305\u542B\u4E00\u500B\u5916\u90E8\u7684\u90E8\u843D\u683C\u3002\u7576\u5305\u542B\u4E00\u500B\u5916\u90E8\u7684\u90E8\u843D\u683C\u6642\uFF0C\u5B83\u7684\u9805\u76EE\u5C07\u88AB\u986F\u793A\u3002\u60A8\u60F3\u8981\u505A\u4EC0\u9EBC\uFF1F
 blog.is.being.edited.by=\u9019\u500B\u90E8\u843D\u683C\u6B63\u88AB {0} \u7DE8\u8F2F\u3002
-blog.posts=\u9805\u76EE
 blog.subscribe.to.this.feed=\u8A02\u95B1\u9019\u500B\u90E8\u843D\u683C
 
 
diff --git a/src/main/java/org/olat/modules/webFeed/ui/podcast/EpisodeFormController.java b/src/main/java/org/olat/modules/webFeed/ui/podcast/EpisodeFormController.java
index ccb04522550..e74bd811272 100644
--- a/src/main/java/org/olat/modules/webFeed/ui/podcast/EpisodeFormController.java
+++ b/src/main/java/org/olat/modules/webFeed/ui/podcast/EpisodeFormController.java
@@ -49,9 +49,9 @@ import org.olat.core.util.vfs.Quota;
 import org.olat.core.util.vfs.VFSContainer;
 import org.olat.core.util.vfs.VFSLeaf;
 import org.olat.core.util.vfs.callbacks.FullAccessWithQuotaCallback;
-import org.olat.modules.webFeed.managers.FeedManager;
-import org.olat.modules.webFeed.models.Feed;
-import org.olat.modules.webFeed.models.Item;
+import org.olat.modules.webFeed.Feed;
+import org.olat.modules.webFeed.Item;
+import org.olat.modules.webFeed.manager.FeedManager;
 import org.springframework.beans.factory.annotation.Autowired;
 
 /**
@@ -87,13 +87,13 @@ public class EpisodeFormController extends FormBasicController {
 	 * @param ureq
 	 * @param control
 	 */
-	public EpisodeFormController(UserRequest ureq, WindowControl control, Item episode, Feed podcast, Translator translator) {
+	public EpisodeFormController(UserRequest ureq, WindowControl control, Item item, Feed feed, Translator translator) {
 		super(ureq, control);
-		this.episode = episode;
-		this.podcast = podcast;
-		this.baseDir = FeedManager.getInstance().getItemContainer(episode, podcast);
+		this.episode = item;
+		this.podcast = feed;
+		this.baseDir = FeedManager.getInstance().getItemContainer(item);
 		if(baseDir.getLocalSecurityCallback() == null) {
-			Quota quota = FeedManager.getInstance().getQuota(podcast.getResource());
+			Quota quota = FeedManager.getInstance().getQuota(feed);
 			baseDir.setLocalSecurityCallback(new FullAccessWithQuotaCallback(quota));
 		}
 		
@@ -294,7 +294,7 @@ public class EpisodeFormController extends FormBasicController {
 		file = uifactory.addFileElement(getWindowControl(), "file", flc);
 		file.setLabel("podcast.episode.file.label", null);
 		file.setMandatory(true, "podcast.episode.mandatory");
-		File mediaFile = FeedManager.getInstance().getItemEnclosureFile(episode, podcast);
+		File mediaFile = FeedManager.getInstance().loadItemEnclosureFile(episode);
 		file.setInitialFile(mediaFile);
 		file.addActionListener(FormEvent.ONCHANGE);
 		if(baseDir.getLocalSecurityCallback() != null && baseDir.getLocalSecurityCallback().getQuota() != null) {
@@ -303,9 +303,9 @@ public class EpisodeFormController extends FormBasicController {
 			file.setMaxUploadSizeKB(uploadLimitKB.intValue(), "ULLimitExceeded", new String[] { Formatter.roundToString((uploadLimitKB.floatValue()) / 1024f, 1), supportAddr });
 		}
 		
-		String width = episode.getWidth() > 0 ? Integer.toString(episode.getWidth()) : "";
+		String width = episode.getWidth() != null && episode.getWidth() > 0 ? Integer.toString(episode.getWidth()) : "";
 		widthEl = uifactory.addTextElement("video-width", "podcast.episode.file.width", 12, width, flc);
-		String height = episode.getHeight() > 0 ? Integer.toString(episode.getHeight()) : "";
+		String height = episode.getHeight() != null && episode.getHeight() > 0 ? Integer.toString(episode.getHeight()) : "";
 		heightEl = uifactory.addTextElement("video-height", "podcast.episode.file.height", 12, height, flc);
 
 		// Submit and cancel buttons
diff --git a/src/main/java/org/olat/modules/webFeed/ui/podcast/PodcastUIFactory.java b/src/main/java/org/olat/modules/webFeed/ui/podcast/PodcastUIFactory.java
index 12187a5605c..e9f4ecaf056 100644
--- a/src/main/java/org/olat/modules/webFeed/ui/podcast/PodcastUIFactory.java
+++ b/src/main/java/org/olat/modules/webFeed/ui/podcast/PodcastUIFactory.java
@@ -33,8 +33,8 @@ import org.olat.course.ICourse;
 import org.olat.course.nodes.AbstractFeedCourseNode;
 import org.olat.course.nodes.feed.podcast.PodcastNodeEditController;
 import org.olat.course.run.userview.UserCourseEnvironment;
-import org.olat.modules.webFeed.models.Feed;
-import org.olat.modules.webFeed.models.Item;
+import org.olat.modules.webFeed.Feed;
+import org.olat.modules.webFeed.Item;
 import org.olat.modules.webFeed.ui.FeedMainController;
 import org.olat.modules.webFeed.ui.FeedUIFactory;
 
diff --git a/src/main/java/org/olat/modules/webFeed/ui/podcast/_content/episodes.html b/src/main/java/org/olat/modules/webFeed/ui/podcast/_content/episodes.html
index 952023100a0..46b7b1e799f 100644
--- a/src/main/java/org/olat/modules/webFeed/ui/podcast/_content/episodes.html
+++ b/src/main/java/org/olat/modules/webFeed/ui/podcast/_content/episodes.html
@@ -7,7 +7,7 @@
 <h4 class="o_title">$r.translate("podcast.episodes")</h4>
 <div class="clearfix"></div>
 
-#if (!$feed.hasItems() )
+#if ($items.size() == 0)
 	<div class="o_podcast_no_episodes o_important">
 		<p>
 			<i class="o_icon o_icon_warn"></i>
@@ -24,15 +24,15 @@
 	</div>
 #else
 	<!-- loop over episodes -->
-	#foreach( $episode in $helper.getItems($callback) ) 	
-		<div class="o_episode o_block_with_datecomp clearfix $!post.extraCSSClass()">
+	#foreach( $episode in $helper.getItemsOnPage($items) )
+		<div class="o_episode o_block_with_datecomp clearfix $!episode.extraCSSClass()">
 			<div class="o_head">
 				#if (( $helper.isAuthor($episode) && !$episode.isDraft()) && $feed.isInternal() && $r.available("feed.artefact.item.$episode.getGuid()"))
 					<div class="pull-right">
 						$r.render("feed.artefact.item.$episode.getGuid()")
 					</div>
 				#end
-				<h3 class="o_title">
+
 				#if ( $episode.getContent() && $episode.getContent() != "" )
 					$r.render("titlelink.to.$episode.getGuid()")
 				#else
@@ -122,7 +122,7 @@
 	#end
 	
 	<ul class="pagination">
-		#if ( $helper.hasOlderItems() )
+		#if ( $helper.hasOlderItems($items) )
 		<li>
 			$r.render("feed.older.items")
 		</li>
diff --git a/src/main/java/org/olat/modules/webFeed/ui/podcast/_content/info.html b/src/main/java/org/olat/modules/webFeed/ui/podcast/_content/info.html
index cdf8cc78999..4d088d216c6 100644
--- a/src/main/java/org/olat/modules/webFeed/ui/podcast/_content/info.html
+++ b/src/main/java/org/olat/modules/webFeed/ui/podcast/_content/info.html
@@ -1,21 +1,21 @@
 <div class="o_podcast_info">
 	#if ($callback.mayEditMetadata())
-		<div class="o_feed_edit">$r.render("feed.edit")</div>
+		<div class="o_feed_edit pull-right">$r.render("feed.edit")</div>
 	#end
 	<h2>	
 		<i class="o_icon o_icon-fw o_icon o_FileResource-PODCAST_icon"></i> 
 		$r.escapeHtml($!feed.getTitle())
 	</h2>
-	#if ($helper.getImageUrl() && $helper.getImageUrl() != "")
-		<img class="o_media" src="$helper.getImageUrl()?thumbnail=180x121" alt="Podcast Icon" />
+	#if ($helper.getImageUrl($feed) && $helper.getImageUrl($feed) != "")
+		<img class="o_media" src="$helper.getImageUrl($feed)?thumbnail=180x121" alt="Podcast Icon" />
 	#end
 	<div class="o_block">
 		#if ($!feed.getAuthor())
-			<div class="o_author">$r.translate("feed.author"): $r.escapeHtml($helper.getFeedAuthor())</div>
+			<div class="o_author">$r.translate("feed.author"): $r.escapeHtml($feed.getAuthor())</div>
 		#end
-		<div class="o_date">$r.translateWithPackage("org.olat.modules.webFeed.ui.blog","feed.last.change") $!helper.getLastModified()	</div>
-		#if ($helper.getFeedDescriptionForBrowser() != "")
-		<div class="o_desc o_info">$!helper.getFeedDescriptionForBrowser()</div>
+		<div class="o_date">$r.translateWithPackage("org.olat.modules.webFeed.ui.blog","feed.last.change") $!helper.getLastModified($feed)	</div>
+		#if ($feed.getDescription() != "")
+		<div class="o_desc o_info">$!helper.getFeedDescriptionForBrowser($feed)</div>
 		#end
 		
 		#if ( !$feed.isUndefined() && $helper.getFeedUrl() )			
diff --git a/src/main/java/org/olat/repository/handlers/BlogHandler.java b/src/main/java/org/olat/repository/handlers/BlogHandler.java
index 0d58899d9e6..13ef8c6934d 100644
--- a/src/main/java/org/olat/repository/handlers/BlogHandler.java
+++ b/src/main/java/org/olat/repository/handlers/BlogHandler.java
@@ -50,7 +50,7 @@ import org.olat.fileresource.types.FileResource;
 import org.olat.fileresource.types.ResourceEvaluation;
 import org.olat.modules.webFeed.FeedResourceSecurityCallback;
 import org.olat.modules.webFeed.FeedSecurityCallback;
-import org.olat.modules.webFeed.managers.FeedManager;
+import org.olat.modules.webFeed.manager.FeedManager;
 import org.olat.modules.webFeed.ui.FeedMainController;
 import org.olat.modules.webFeed.ui.FeedRuntimeController;
 import org.olat.modules.webFeed.ui.blog.BlogUIFactory;
@@ -113,6 +113,7 @@ public class BlogHandler implements RepositoryHandler {
 		File fResourceFileroot = FileResourceManager.getInstance().getFileResourceRootImpl(resource).getBasefile();
 		File blogRoot = new File(fResourceFileroot, FeedManager.getInstance().getFeedKind(resource));
 		FileResource.copyResource(file, filename, blogRoot);
+		FeedManager.getInstance().importFeedFromXML(resource);
 		RepositoryEntry re = CoreSpringFactory.getImpl(RepositoryService.class)
 				.create(initialAuthor, null, "", displayname, description, resource, RepositoryEntry.ACC_OWNERS);
 		DBFactory.getInstance().commit();
@@ -138,7 +139,7 @@ public class BlogHandler implements RepositoryHandler {
 		// For now, notifications are not implemented since a blog feed is meant
 		// to be subscriped to anyway.
 		// NotificationsManager.getInstance().deletePublishersOf(res);
-		FeedManager.getInstance().delete(res);
+		FeedManager.getInstance().deleteFeed(res);
 		return true;
 	}
 
diff --git a/src/main/java/org/olat/repository/handlers/PodcastHandler.java b/src/main/java/org/olat/repository/handlers/PodcastHandler.java
index f78a84a3114..f457debcf5e 100644
--- a/src/main/java/org/olat/repository/handlers/PodcastHandler.java
+++ b/src/main/java/org/olat/repository/handlers/PodcastHandler.java
@@ -50,7 +50,7 @@ import org.olat.fileresource.types.PodcastFileResource;
 import org.olat.fileresource.types.ResourceEvaluation;
 import org.olat.modules.webFeed.FeedResourceSecurityCallback;
 import org.olat.modules.webFeed.FeedSecurityCallback;
-import org.olat.modules.webFeed.managers.FeedManager;
+import org.olat.modules.webFeed.manager.FeedManager;
 import org.olat.modules.webFeed.ui.FeedMainController;
 import org.olat.modules.webFeed.ui.FeedRuntimeController;
 import org.olat.modules.webFeed.ui.podcast.PodcastUIFactory;
@@ -113,6 +113,7 @@ public class PodcastHandler implements RepositoryHandler {
 		File fResourceFileroot = FileResourceManager.getInstance().getFileResourceRootImpl(resource).getBasefile();
 		File blogRoot = new File(fResourceFileroot, FeedManager.getInstance().getFeedKind(resource));
 		FileResource.copyResource(file, filename, blogRoot);
+		FeedManager.getInstance().importFeedFromXML(resource);
 		RepositoryEntry re = CoreSpringFactory.getImpl(RepositoryService.class)
 				.create(initialAuthor, null, "", displayname, description, resource, RepositoryEntry.ACC_OWNERS);
 		DBFactory.getInstance().commit();
@@ -138,7 +139,7 @@ public class PodcastHandler implements RepositoryHandler {
 		// For now, notifications are not implemented since a podcast feed is meant
 		// to be subscriped to anyway.
 		// NotificationsManager.getInstance().deletePublishersOf(res);
-		FeedManager.getInstance().delete(res);
+		FeedManager.getInstance().deleteFeed(res);
 		return true;
 	}
 	
diff --git a/src/main/java/org/olat/util/logging/activity/LoggingResourceable.java b/src/main/java/org/olat/util/logging/activity/LoggingResourceable.java
index 3970419996e..d5a09acd556 100644
--- a/src/main/java/org/olat/util/logging/activity/LoggingResourceable.java
+++ b/src/main/java/org/olat/util/logging/activity/LoggingResourceable.java
@@ -51,8 +51,8 @@ import org.olat.modules.portfolio.Assignment;
 import org.olat.modules.portfolio.Binder;
 import org.olat.modules.portfolio.Media;
 import org.olat.modules.portfolio.Section;
-import org.olat.modules.webFeed.models.Feed;
-import org.olat.modules.webFeed.models.Item;
+import org.olat.modules.webFeed.Feed;
+import org.olat.modules.webFeed.Item;
 import org.olat.repository.RepositoryEntry;
 import org.olat.repository.RepositoryManager;
 import org.olat.resource.OLATResource;
diff --git a/src/main/resources/META-INF/persistence.xml b/src/main/resources/META-INF/persistence.xml
index 5afa943412c..c4fccb8b2ed 100644
--- a/src/main/resources/META-INF/persistence.xml
+++ b/src/main/resources/META-INF/persistence.xml
@@ -190,6 +190,8 @@
 		<class>org.olat.modules.reminder.model.SentReminderImpl</class>
 		<class>org.olat.modules.video.model.VideoTranscodingImpl</class>
 		<class>org.olat.modules.video.model.VideoMetaImpl</class>
+		<class>org.olat.modules.webFeed.model.FeedImpl</class>
+		<class>org.olat.modules.webFeed.model.ItemImpl</class>
 		<class>org.olat.ims.lti.model.LTIOutcomeImpl</class>
 		<class>org.olat.portfolio.model.InvitationImpl</class>
 		<class>org.olat.portfolio.model.structel.EPStructureElementToGroupRelation</class>
diff --git a/src/main/resources/database/mysql/setupDatabase.sql b/src/main/resources/database/mysql/setupDatabase.sql
index 2191658e6e8..29f91becabe 100644
--- a/src/main/resources/database/mysql/setupDatabase.sql
+++ b/src/main/resources/database/mysql/setupDatabase.sql
@@ -1921,6 +1921,48 @@ create table o_sms_message_log (
    primary key (id)
 );
 
+-- webfeed
+create table o_feed (
+   id bigint not null auto_increment,
+   creationdate datetime not null,
+   lastmodified datetime not null,
+   f_resourceable_id bigint,
+   f_resourceable_type varchar(64),
+   f_type varchar(20),
+   f_title varchar(1024),
+   f_description varchar(1024),
+   f_author varchar(255),
+   f_image_name varchar(255),
+   f_external boolean,
+   f_external_feed_url varchar(1024),
+   f_external_image_url varchar(1024),
+   primary key (id)
+);
+
+create table o_feed_item (
+   id bigint not null auto_increment,
+   creationdate datetime not null,
+   lastmodified datetime not null,
+   f_title varchar(1024),
+   f_description mediumtext,
+   f_content mediumtext,
+   f_author varchar(255),
+   f_guid varchar(255),
+   f_external_link varchar(1024),
+   f_draft boolean,
+   f_publish_date datetime,
+   f_width bigint,
+   f_height bigint,
+   f_filename varchar(1024),
+   f_type varchar(255),
+   f_length bigint,
+   f_external_url varchar(1024),
+   fk_feed_id bigint not null,
+   fk_identity_author_id bigint,
+   fk_identity_modified_id bigint,
+   primary key (id)
+);
+
 -- user view
 create view o_bs_identity_short_v as (
    select
@@ -2252,6 +2294,8 @@ alter table o_pf_binder_user_infos ENGINE = InnoDB;
 alter table o_eva_form_session ENGINE = InnoDB;
 alter table o_eva_form_response ENGINE = InnoDB;
 alter table o_sms_message_log ENGINE = InnoDB;
+alter table o_feed ENGINE = InnoDB;
+alter table o_feed_item ENGINE = InnoDB;
 
 -- rating
 alter table o_userrating add constraint FKF26C8375236F20X foreign key (creator_id) references o_bs_identity (id);
@@ -2711,6 +2755,16 @@ create index cer_uuid_idx on o_cer_certificate (c_uuid);
 -- sms
 alter table o_sms_message_log add constraint sms_log_to_identity_idx foreign key (fk_identity) references o_bs_identity (id);
 
+-- webfeed
+create index idx_feed_resourceable_idx on o_feed (f_resourceable_id, f_resourceable_type);
+alter table o_feed_item add constraint item_to_feed_fk foreign key(fk_feed_id) references o_feed(id);
+create index idx_item_feed_idx on o_feed_item(fk_feed_id);
+alter table o_feed_item add constraint feed_item_to_ident_author_fk foreign key (fk_identity_author_id) references o_bs_identity (id);
+create index idx_item_ident_author_idx on o_feed_item(fk_identity_author_id);
+alter table o_feed_item add constraint feed_item_to_ident_modified_fk foreign key (fk_identity_modified_id) references o_bs_identity (id);
+create index idx_item_ident_modified_idx on o_feed_item(fk_identity_modified_id);
+create index idx_item_guid_idx on o_feed_item (f_guid);
+
 -- o_logging_table
 create index log_target_resid_idx on o_loggingtable(targetresid);
 create index log_ptarget_resid_idx on o_loggingtable(parentresid);
diff --git a/src/main/resources/database/oracle/setupDatabase.sql b/src/main/resources/database/oracle/setupDatabase.sql
index d758091c722..d7d05acaa6c 100644
--- a/src/main/resources/database/oracle/setupDatabase.sql
+++ b/src/main/resources/database/oracle/setupDatabase.sql
@@ -1965,6 +1965,47 @@ create table o_sms_message_log (
    primary key (id)
 );
 
+-- webfeed
+create table o_feed ( 
+   id number(20) generated always as identity,
+   creationdate date not null,
+   lastmodified date not null,
+   f_resourceable_id number(20),
+   f_resourceable_type varchar(64),
+   f_title varchar(1024), 
+   f_description varchar(1024),
+   f_author varchar(255),
+   f_image_name varchar(255),
+   f_external number(2) default 0,
+   f_external_feed_url varchar(1024),
+   f_external_image_url varchar(1024),
+   primary key (id)
+);
+
+create table o_feed_item (
+   id number(20) generated always as identity,
+   creationdate date not null,
+   lastmodified date not null,
+   f_title varchar(1024),
+   f_description clob,
+   f_content clob,
+   f_author varchar(255),
+   f_guid varchar(255),
+   f_external_link varchar(1024),
+   f_draft number(2) default 0,
+   f_publish_date date,
+   f_width number(20),
+   f_height number(20),
+   f_filename varchar(1024),
+   f_type varchar(255),
+   f_length number(20),
+   f_external_url varchar(1024),
+   fk_feed_id number(20),
+   fk_identity_author_id number(20),
+   fk_identity_modified_id number(20),
+   primary key (id)
+);
+
 -- user view
 create view o_bs_identity_short_v as (
    select
@@ -2905,6 +2946,16 @@ create index cer_uuid_idx on o_cer_certificate (c_uuid);
 alter table o_sms_message_log add constraint sms_log_to_identity_idx foreign key (fk_identity) references o_bs_identity (id);
 create index idx_sms_log_to_identity_idx on o_sms_message_log(fk_identity);
 
+-- webfeed
+create index idx_feed_resourceable_idx on o_feed (f_resourceable_id, f_resourceable_type);
+alter table o_feed_item add constraint item_to_feed_fk foreign key(fk_feed_id) references o_feed(id);
+create index idx_item_feed_idx on o_feed_item(fk_feed_id);
+alter table o_feed_item add constraint feed_item_to_ident_author_fk foreign key (fk_identity_author_id) references o_bs_identity (id);
+create index idx_item_ident_author_idx on o_feed_item (fk_identity_author_id);
+alter table o_feed_item add constraint feed_item_to_ident_modified_fk foreign key (fk_identity_modified_id) references o_bs_identity (id);
+create index idx_item_ident_modified_idx on o_feed_item (fk_identity_modified_id);
+create index idx_item_guid_idx on o_feed_item (f_guid);
+
 -- o_logging_table
 create index log_target_resid_idx on o_loggingtable(targetresid);
 create index log_ptarget_resid_idx on o_loggingtable(parentresid);
diff --git a/src/main/resources/database/postgresql/setupDatabase.sql b/src/main/resources/database/postgresql/setupDatabase.sql
index cb98cf9c9e7..84a9cb94f38 100644
--- a/src/main/resources/database/postgresql/setupDatabase.sql
+++ b/src/main/resources/database/postgresql/setupDatabase.sql
@@ -1912,12 +1912,54 @@ create table o_sms_message_log (
    id bigserial not null,
    creationdate timestamp not null,
    lastmodified timestamp not null,
-   s_message_uuid varchar(256) not null,   s_server_response varchar(256),
+   s_message_uuid varchar(256) not null,
+   s_server_response varchar(256),
    s_service_id varchar(32) not null,
    fk_identity int8 not null,
    primary key (id)
 );
 
+-- webfeed
+create table o_feed (
+   id bigserial not null,
+   creationdate timestamp not null,
+   lastmodified timestamp not null,
+   f_resourceable_id bigint,
+   f_resourceable_type varchar(64),
+   f_title varchar(1024),
+   f_description varchar(1024),
+   f_author varchar(255),
+   f_image_name varchar(255),
+   f_external boolean,
+   f_external_feed_url varchar(1024),
+   f_external_image_url varchar(1024),
+   primary key (id)
+);
+
+create table o_feed_item (
+   id bigserial not null,
+   creationdate timestamp not null,
+   lastmodified timestamp not null,
+   f_title varchar(1024),
+   f_description text,
+   f_content text,
+   f_author varchar(255),
+   f_guid varchar(255),
+   f_external_link varchar(1024),
+   f_draft boolean,
+   f_publish_date timestamp,
+   f_width int8,
+   f_height int8,
+   f_filename varchar(1024),
+   f_type varchar(255),
+   f_length bigint,
+   f_external_url varchar(1024),
+   fk_feed_id bigint,
+   fk_identity_author_id int8,
+   fk_identity_modified_id int8,
+   primary key (id)
+);
+
 -- user view
 create view o_bs_identity_short_v as (
    select
@@ -2755,6 +2797,16 @@ create index cer_uuid_idx on o_cer_certificate (c_uuid);
 alter table o_sms_message_log add constraint sms_log_to_identity_idx foreign key (fk_identity) references o_bs_identity (id);
 create index idx_sms_log_to_identity_idx on o_sms_message_log(fk_identity);
 
+-- webfeed
+create index idx_feed_resourceable_idx on o_feed (f_resourceable_id, f_resourceable_type);
+alter table o_feed_item add constraint item_to_feed_fk foreign key(fk_feed_id) references o_feed(id);
+create index idx_item_feed_idx on o_feed_item(fk_feed_id);
+alter table o_feed_item add constraint feed_item_to_ident_author_fk foreign key (fk_identity_author_id) references o_bs_identity (id);
+create index idx_item_ident_author_idx on o_feed_item (fk_identity_author_id);
+alter table o_feed_item add constraint feed_item_to_ident_modified_fk foreign key (fk_identity_modified_id) references o_bs_identity (id);
+create index idx_item_ident_modified_idx on o_feed_item (fk_identity_modified_id);
+create index idx_item_guid_idx on o_feed_item (f_guid);
+
 -- o_logging_table
 create index log_target_resid_idx on o_loggingtable(targetresid);
 create index log_ptarget_resid_idx on o_loggingtable(parentresid);
diff --git a/src/test/java/org/olat/modules/webFeed/FeedManagerImplTest.java b/src/test/java/org/olat/modules/webFeed/FeedManagerImplTest.java
deleted file mode 100644
index 9f8e1472853..00000000000
--- a/src/test/java/org/olat/modules/webFeed/FeedManagerImplTest.java
+++ /dev/null
@@ -1,174 +0,0 @@
-/**
-* 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) since 2004 at Multimedia- & E-Learning Services (MELS),<br>
-* University of Zurich, Switzerland.
-* <hr>
-* <a href="http://www.openolat.org">
-* OpenOLAT - Online Learning and Training</a><br>
-* This file has been modified by the OpenOLAT community. Changes are licensed
-* under the Apache 2.0 license as the original file.  
-* <p>
-*/
-package org.olat.modules.webFeed;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-import org.olat.core.id.OLATResourceable;
-import org.olat.core.util.CodeHelper;
-import org.olat.core.util.vfs.VFSContainer;
-import org.olat.modules.webFeed.managers.FeedManager;
-import org.olat.modules.webFeed.models.Feed;
-import org.olat.modules.webFeed.models.Item;
-import org.olat.test.OlatTestCase;
-import org.springframework.beans.factory.annotation.Autowired;
-
-
-/**
- * 
- * JUnit tests for <code>FeedManager</code> methods.
- * 
- * <P>
- * Initial Date: Feb 16, 2009 <br>
- * 
- * @author Gregor Wassmann
- */
-public class FeedManagerImplTest extends OlatTestCase {
-	@Autowired
-	private FeedManager feedManager;
-	private Feed feed;
-	private static final String PODCAST_TITLE = "My Test Feed";
-
-	
-	/**
-	 * @see junit.framework.TestCase#setUp()
-	 */
-	@Before
-	public void setup() {
-		// Create a feed that can be read, updated or deleted.
-		OLATResourceable podcastResource = feedManager.createPodcastResource();
-		feed = feedManager.getFeed(podcastResource);
-		feed.setTitle(PODCAST_TITLE);
-		feedManager.updateFeedMetadata(feed);
-
-		// Add an episode
-		// A feed can only be edited when it is an internal feed (meaning that
-		// it is made within OLAT). Obviously, external feeds cannot be changed.
-		Item item = new Item();
-		item.setTitle("My Test Item");
-		feed = feedManager.updateFeedMode(Boolean.FALSE, feed);
-		feed = feedManager.addItem(item, null, feed);
-	}
-
-	/**
-	 * @see junit.framework.TestCase#tearDown()
-	 */
-	@After
-	public void tearDown() {
-		 if (feed != null) feedManager.delete(feed);
-	}
-	
-	@Test
-	public void should_service_present() {
-		Assert.assertNotNull(feedManager);
-		Assert.assertNotNull(feed);
-	}
-
-	/**
-	 * Test method create
-	 */
-	@Test
-	public void testCreatePodcast() {
-		OLATResourceable podcastResource = feedManager.createPodcastResource();
-		Feed newPodcast = feedManager.getFeed(podcastResource);
-		assertNotNull(newPodcast);
-		assertNotNull(newPodcast.getId());
-
-		// Has a feed folder been created?
-		VFSContainer podcastContainer = feedManager.getFeedContainer(newPodcast);
-		assertNotNull(podcastContainer);
-		feedManager.delete(newPodcast);
-	}
-
-	/**
-	 * Test method read
-	 */
-	@Test public void testReadPodcast() {
-		Feed readPodcast = feedManager.getFeed(feed);
-		assertNotNull(readPodcast);
-		assertNotNull(readPodcast.getId());
-		assertEquals(PODCAST_TITLE, readPodcast.getTitle());
-	}
-
-	/**
-	 * Test method update
-	 */
-	@Test public void testUpdate() {
-		feed.setTitle("The title changed");
-		feedManager.updateFeedMetadata(feed);
-		// re-read for assertion
-		Feed readPodcast = feedManager.getFeed(feed);
-		assertEquals(feed.getTitle(), readPodcast.getTitle());
-	}
-
-	/**
-	 * Test method delete
-	 */
-	@Test public void testDelete() {
-		// int initialCount = feedManager.podcastCount();
-		feedManager.delete(feed);
-		// int newCount = feedManager.podcastCount();
-		// assertEquals(initialCount - 1, newCount);
-		// assertNull(feed);
-	}
-
-	/**
-	 * Test method add
-	 */
-	@Test public void testAdd() {
-		Item newEpisode = new Item();
-		newEpisode.setGuid(CodeHelper.getGlobalForeverUniqueID()); 
-		newEpisode.setTitle("This is my new Item");
-		// Count episodes before
-		int initialCount = feed.getItems().size();
-		feedManager.addItem(newEpisode, null, feed);
-		// re-read feed and count episodes
-		feed = feedManager.getFeed(feed);
-		int newCount = feed.getItems().size();
-		// Compare
-		assertEquals(initialCount + 1, newCount);
-	}
-
-	/**
-	 * Test method remove
-	 */
-	@Test public void testRemove() {
-		Item item = feed.getItems().get(0);
-		// Count episodes before
-		int initialCount = feed.getItems().size();
-		feedManager.remove(item, feed);
-		// re-read feed and count episodes after adding a new one
-		feed = feedManager.getFeed(feed);
-		int newCount = feed.getItems().size();
-		// Compare
-		assertEquals(initialCount - 1, newCount);
-	}
-}
diff --git a/src/test/java/org/olat/modules/webFeed/manager/FeedDAOTest.java b/src/test/java/org/olat/modules/webFeed/manager/FeedDAOTest.java
new file mode 100644
index 00000000000..9a1a80cc430
--- /dev/null
+++ b/src/test/java/org/olat/modules/webFeed/manager/FeedDAOTest.java
@@ -0,0 +1,283 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <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 the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <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>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.webFeed.manager;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.Date;
+import java.util.GregorianCalendar;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.olat.core.commons.persistence.DB;
+import org.olat.modules.webFeed.Feed;
+import org.olat.modules.webFeed.model.FeedImpl;
+import org.olat.resource.OLATResource;
+import org.olat.test.JunitTestHelper;
+import org.olat.test.OlatTestCase;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 02.05.2017<br>
+ * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
+ *
+ */
+public class FeedDAOTest extends OlatTestCase {
+	
+	@Autowired
+	private DB dbInstance;
+	@Autowired
+	private FeedDAO feedDao;
+	
+	@Test
+	public void createFeed_ores() {
+		OLATResource resource = JunitTestHelper.createRandomResource();
+		
+		Feed feed = feedDao.createFeedForResourcable(resource);
+		Assert.assertNotNull(feed);
+		dbInstance.commitAndCloseSession();
+
+		//check values
+		Assert.assertNotNull(feed.getKey());
+		Assert.assertNotNull(feed.getCreationDate());
+		Assert.assertNotNull(feed.getLastModified());
+		Assert.assertEquals(resource.getResourceableId(), feed.getResourceableId());
+		Assert.assertEquals(resource.getResourceableTypeName(), feed.getResourceableTypeName());
+	}
+	
+	@Test
+	public void createFeed_feed() {
+		OLATResource resource = JunitTestHelper.createRandomResource();
+		Feed tempFeed = new FeedImpl(resource);
+		
+		Feed feed = feedDao.createFeed(tempFeed);
+		Assert.assertNotNull(feed);
+		dbInstance.commitAndCloseSession();
+
+		//check values
+		Assert.assertNotNull(feed.getKey());
+		Assert.assertNotNull(feed.getCreationDate());
+		Assert.assertNotNull(feed.getLastModified());
+		Assert.assertEquals(resource.getResourceableId(), feed.getResourceableId());
+		Assert.assertEquals(resource.getResourceableTypeName(), feed.getResourceableTypeName());
+	}
+	
+	@Test
+	public void createFeed_feed_keepDates() {
+		OLATResource resource = JunitTestHelper.createRandomResource();
+		Feed tempFeed = new FeedImpl(resource);
+		Date created = new GregorianCalendar(2000, 1, 1).getTime();
+		tempFeed.setCreationDate(created);
+		Date modified = new GregorianCalendar(2000, 2, 2).getTime();
+		tempFeed.setLastModified(modified);
+		
+		Feed feed = feedDao.createFeed(tempFeed);
+		dbInstance.commitAndCloseSession();
+
+		//check values
+		assertThat(feed.getCreationDate()).isCloseTo(created, 1000);
+		assertThat(feed.getLastModified()).isCloseTo(modified, 1000);
+	}
+	
+	@Test
+	public void createFeed_null() {
+		Feed feed = feedDao.createFeed(null);
+		dbInstance.commitAndCloseSession();
+
+		Assert.assertNull(feed);
+	}
+	
+	@Test
+	public void copyFeed() {
+		OLATResource source = JunitTestHelper.createRandomResource();
+		OLATResource target = JunitTestHelper.createRandomResource();
+		Feed feed = feedDao.createFeedForResourcable(source);
+		dbInstance.commitAndCloseSession();
+		
+		Feed copy = feedDao.copyFeed(feed, target);
+		dbInstance.commitAndCloseSession();
+
+		assertThat(copy.getKey()).isNotEqualTo(feed.getKey());
+		assertThat(copy.getResourceableId()).isNotEqualTo(feed.getResourceableId());
+		assertThat(copy.getCreationDate()).isCloseTo(feed.getCreationDate(), 1000);
+		assertThat(copy.getLastModified()).isCloseTo(feed.getLastModified(), 1000);
+	}
+	
+	@Test
+	public void copyFeed_Source_null() {
+		OLATResource target = JunitTestHelper.createRandomResource();
+		
+		Feed copy = feedDao.copyFeed(null, target);
+		dbInstance.commitAndCloseSession();
+
+		assertThat(copy).isNull();
+	}
+	
+	@Test
+	public void copyFeed_Target_null() {
+		OLATResource source = JunitTestHelper.createRandomResource();
+		
+		Feed copy = feedDao.copyFeed(source, null);
+		dbInstance.commitAndCloseSession();
+
+		assertThat(copy).isNull();
+	}
+	
+	@Test
+	public void loadFeed_Long() {
+		OLATResource resource = JunitTestHelper.createRandomResource();
+
+		Feed feed = feedDao.createFeedForResourcable(resource);
+		dbInstance.commitAndCloseSession();
+		
+		Feed reloaded = feedDao.loadFeed(feed.getKey());
+
+		//check values
+		Assert.assertEquals(feed.getKey(), reloaded.getKey());
+		Assert.assertEquals(resource.getResourceableId(), reloaded.getResourceableId());
+		Assert.assertEquals(resource.getResourceableTypeName(), reloaded.getResourceableTypeName());
+		// It's ok when the date is about the same in the database.
+		Assert.assertTrue("Dates aren't close enough to each other!",
+				(feed.getCreationDate().getTime() - reloaded.getCreationDate().getTime()) < 1000);
+	}
+	
+	@Test
+	public void loadFeed_notExisting() {	
+		// load feed for a non existing key
+		Feed feed = feedDao.loadFeed(-1L);
+
+		Assert.assertNull(feed);
+	}
+	
+	@Test
+	public void loadFeed_Resourceable() {
+		OLATResource resource = JunitTestHelper.createRandomResource();
+
+		Feed feed = feedDao.createFeedForResourcable(resource);
+		dbInstance.commitAndCloseSession();
+		
+		Feed reloaded = feedDao.loadFeed(resource);
+
+		//check values
+		Assert.assertEquals(feed.getKey(), reloaded.getKey());
+		Assert.assertEquals(resource.getResourceableId(), reloaded.getResourceableId());
+		Assert.assertEquals(resource.getResourceableTypeName(), reloaded.getResourceableTypeName());
+	}
+	
+	@Test
+	public void updateFeed() {
+		OLATResource resource = JunitTestHelper.createRandomResource();
+
+		// create and save a feed
+		Feed feed = feedDao.createFeedForResourcable(resource);
+		dbInstance.commitAndCloseSession();
+		
+		// change the values of the feed
+		String initialAuthor = "Author";
+		feed.setAuthor(initialAuthor);
+		String description = "My Feed";
+		feed.setDescription(description);
+		Boolean external = true;
+		feed.setExternal(external);
+		String externalFeedUrl = "http://feed.xml";
+		feed.setExternalFeedUrl(externalFeedUrl);
+		String externalImageURL = "https://www.example.com/image.png";
+		feed.setExternalImageURL(externalImageURL);
+		String name = "My Image";
+		feed.setImageName(name);
+		String title = "Display";
+		feed.setTitle(title);
+		
+		// update the feed in the database and reload it
+		feedDao.updateFeed(feed);
+		dbInstance.commitAndCloseSession();
+		Feed reloaded = feedDao.loadFeed(feed.getKey());
+
+		//check values
+		Assert.assertEquals(feed.getKey(), reloaded.getKey());
+		Assert.assertEquals(feed.getAuthor(), reloaded.getAuthor());
+		Assert.assertEquals(feed.getDescription(), reloaded.getDescription());
+		Assert.assertEquals(feed.isExternal(), reloaded.isExternal());
+		Assert.assertEquals(feed.isInternal(), reloaded.isInternal());
+		Assert.assertEquals(feed.isUndefined(), reloaded.isUndefined());
+		Assert.assertEquals(feed.getExternalFeedUrl(), reloaded.getExternalFeedUrl());
+		Assert.assertEquals(feed.getExternalImageURL(), reloaded.getExternalImageURL());
+		Assert.assertEquals(feed.getAuthor(), reloaded.getAuthor());
+		Assert.assertEquals(feed.getImageName(), reloaded.getImageName());
+		Assert.assertEquals(feed.getTitle(), reloaded.getTitle());
+		Assert.assertEquals(resource.getResourceableId(), reloaded.getResourceableId());
+		Assert.assertEquals(resource.getResourceableTypeName(), reloaded.getResourceableTypeName());
+	}
+	
+	@Test
+	public void updateFeed_null() {
+		OLATResource resource = JunitTestHelper.createRandomResource();
+
+		// create and save a feed
+		feedDao.createFeedForResourcable(resource);
+		dbInstance.commitAndCloseSession();
+		
+		// update null
+		Feed updated = feedDao.updateFeed(null);
+
+		//check values
+		Assert.assertNull(updated);
+	}
+	
+	@Test
+	public void removeFeed() {
+		// store 3 feeds
+		OLATResource resource1 = JunitTestHelper.createRandomResource();
+		OLATResource resource2 = JunitTestHelper.createRandomResource();
+		OLATResource resource3 = JunitTestHelper.createRandomResource();
+		
+		Feed feed1 = feedDao.createFeedForResourcable(resource1);
+		Feed feed2 = feedDao.createFeedForResourcable(resource2);
+		Feed feed3 = feedDao.createFeedForResourcable(resource3);
+		dbInstance.commitAndCloseSession();
+		
+		// delete 1 feed
+		feedDao.removeFeedForResourceable(feed2);
+		dbInstance.commitAndCloseSession();
+		
+		// check if one feed is deleted and two feeds are still in the database
+		Feed reloaded1 = feedDao.loadFeed(feed1.getKey());
+		Assert.assertNotNull(reloaded1);
+		Feed reloaded2 = feedDao.loadFeed(feed2.getKey());
+		Assert.assertNull(reloaded2);
+		Feed reloaded3 = feedDao.loadFeed(feed3.getKey());
+		Assert.assertNotNull(reloaded3);
+	}
+	
+	@Test
+	public void removeFeed_null() {
+		OLATResource resource = JunitTestHelper.createRandomResource();
+		Feed feed = feedDao.createFeedForResourcable(resource);
+		dbInstance.commitAndCloseSession();
+		
+		feedDao.removeFeedForResourceable(null);
+		dbInstance.commitAndCloseSession();
+		
+		Feed reloaded1 = feedDao.loadFeed(feed.getKey());
+		Assert.assertNotNull(reloaded1);
+	}
+
+}
diff --git a/src/test/java/org/olat/modules/webFeed/manager/FeedFileStorgeTest.java b/src/test/java/org/olat/modules/webFeed/manager/FeedFileStorgeTest.java
new file mode 100644
index 00000000000..8cc7e6b177e
--- /dev/null
+++ b/src/test/java/org/olat/modules/webFeed/manager/FeedFileStorgeTest.java
@@ -0,0 +1,668 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <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 the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <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>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.webFeed.manager;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.olat.core.util.vfs.VFSContainer;
+import org.olat.fileresource.FileResourceManager;
+import org.olat.fileresource.types.BlogFileResource;
+import org.olat.fileresource.types.FeedFileResource;
+import org.olat.modules.webFeed.Feed;
+import org.olat.modules.webFeed.Item;
+import org.olat.modules.webFeed.model.FeedImpl;
+import org.olat.modules.webFeed.model.ItemImpl;
+import org.olat.test.OlatTestCase;
+
+/**
+ * Test the FeedFileStorage.
+ * 
+ * saveItemMedia
+ * 
+ * 
+ * Initial date: 22.05.2017<br>
+ * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
+ *
+ */
+public class FeedFileStorgeTest extends OlatTestCase {
+	
+	FeedFileStorge sut;
+	
+	private FileResourceManager fileResourceManager;
+	
+	@Before
+	public void setup() {
+		fileResourceManager = FileResourceManager.getInstance();
+		sut = new FeedFileStorge();
+	}
+	
+	@Test
+	public void getOrCreateFeedContainer_create() {
+		FeedFileResource ores = new BlogFileResource();
+		
+		VFSContainer feedContainer = sut.getOrCreateFeedContainer(ores);
+		
+		assertThat(feedContainer).isNotNull();
+		
+		fileResourceManager.deleteFileResource(ores);
+	}
+	
+	@Test
+	public void getOrCreateFeedContainer_get() {
+		FeedFileResource ores = new BlogFileResource();
+		
+		sut.getOrCreateFeedContainer(ores);
+		VFSContainer feedContainer = sut.getOrCreateFeedContainer(ores);
+		
+		assertThat(feedContainer).isNotNull();
+		
+		fileResourceManager.deleteFileResource(ores);
+	}
+	
+	@Test
+	public void getOrCreateFeedContainer_null() {
+		VFSContainer feedContainer = sut.getOrCreateFeedContainer(null);
+		
+		assertThat(feedContainer).isNull();
+	}
+	
+	@Test
+	public void getOrCreateFeedMediaContainer_create() {
+		FeedFileResource ores = new BlogFileResource();
+		
+		VFSContainer feedMediaContainer = sut.getOrCreateFeedMediaContainer(ores);
+		
+		assertThat(feedMediaContainer).isNotNull();
+		
+		fileResourceManager.deleteFileResource(ores);
+	}
+	
+	@Test
+	public void getOrCreateFeedMediaContainer_get() {
+		FeedFileResource ores = new BlogFileResource();
+		
+		sut.getOrCreateFeedMediaContainer(ores);
+		VFSContainer feedMediaContainer = sut.getOrCreateFeedMediaContainer(ores);
+		
+		assertThat(feedMediaContainer).isNotNull();
+		
+		fileResourceManager.deleteFileResource(ores);
+	}
+	
+	@Test
+	public void getOrCreateFeedMediaContainer_null() {
+		VFSContainer feedMediaContainer = sut.getOrCreateFeedMediaContainer(null);
+		
+		assertThat(feedMediaContainer).isNull();
+	}
+	
+	@Test
+	public void getOrCreateFeedItemsContainer_create() {
+		FeedFileResource ores = new BlogFileResource();
+		
+		VFSContainer feedItemsContainer = sut.getOrCreateFeedItemsContainer(ores);
+		
+		assertThat(feedItemsContainer).isNotNull();
+		
+		fileResourceManager.deleteFileResource(ores);
+	}
+	
+	@Test
+	public void getOrCreateFeedItemsContainer_get() {
+		FeedFileResource ores = new BlogFileResource();
+		
+		sut.getOrCreateFeedItemsContainer(ores);
+		VFSContainer feedItemsContainer = sut.getOrCreateFeedItemsContainer(ores);
+		
+		assertThat(feedItemsContainer).isNotNull();
+		
+		fileResourceManager.deleteFileResource(ores);
+	}
+	
+	@Test
+	public void getOrCreateFeedItemsContainer_null() {
+		VFSContainer feedItemsContainer = sut.getOrCreateFeedItemsContainer(null);
+		
+		assertThat(feedItemsContainer).isNull();
+	}
+	
+	@Test
+	public void getOrCreateItemContainer_create() {
+		BlogFileResource resource = new BlogFileResource();
+		Feed feed = new FeedImpl(resource);
+		Item item = new ItemImpl(feed);
+		item.setGuid("guid");
+		
+		VFSContainer itemContainer = sut.getOrCreateItemContainer(item);
+		
+		assertThat(itemContainer).isNotNull();
+		
+		fileResourceManager.deleteFileResource(resource);
+	}
+	
+	@Test
+	public void getOrCreateItemContainer_get() {
+		BlogFileResource resource = new BlogFileResource();
+		Feed feed = new FeedImpl(resource);
+		Item item = new ItemImpl(feed);
+		item.setGuid("guid");
+		
+		sut.getOrCreateItemContainer(item);
+		VFSContainer itemContainer = sut.getOrCreateItemContainer(item);
+		
+		assertThat(itemContainer).isNotNull();
+		
+		fileResourceManager.deleteFileResource(resource);
+	}
+	
+	@Test
+	public void getOrCreateItemContainer_Item_null() {
+		VFSContainer itemContainer = sut.getOrCreateItemContainer(null);
+		
+		assertThat(itemContainer).isNull();
+	}
+	
+	@Test
+	public void getOrCreateItemContainer_Feed_null() {
+		Item item = new ItemImpl(null);
+		item.setGuid("guid");
+		
+		VFSContainer itemContainer = sut.getOrCreateItemContainer(item);
+		
+		assertThat(itemContainer).isNull();
+	}
+	
+	@Test
+	public void getOrCreateItemContainer_Guid_empty() {
+		BlogFileResource resource = new BlogFileResource();
+		Feed feed = new FeedImpl(resource);
+		Item item = new ItemImpl(feed);
+		item.setGuid("");
+		
+		VFSContainer itemContainer = sut.getOrCreateItemContainer(item);
+		
+		assertThat(itemContainer).isNull();
+		
+		fileResourceManager.deleteFileResource(resource);
+	}
+	
+	@Test
+	public void getOrCreateItemContainer_guid_create() {
+		BlogFileResource resource = new BlogFileResource();
+		Feed feed = new FeedImpl(resource);
+		String guid = "guid 123";
+		
+		VFSContainer itemContainer = sut.getOrCreateItemContainer(feed, guid);
+		
+		assertThat(itemContainer).isNotNull();
+		
+		fileResourceManager.deleteFileResource(resource);
+	}
+	
+	@Test
+	public void getOrCreateItemContainer_guid_get() {
+		BlogFileResource resource = new BlogFileResource();
+		Feed feed = new FeedImpl(resource);
+		String guid = "guid 123";
+		
+		sut.getOrCreateItemContainer(feed, guid);
+		VFSContainer itemContainer = sut.getOrCreateItemContainer(feed, guid);
+		
+		assertThat(itemContainer).isNotNull();
+		
+		fileResourceManager.deleteFileResource(resource);
+	}
+	
+	@Test
+	public void getOrCreateItemContainer_guid_Feed_null() {
+		String guid = "guid 123";
+		
+		VFSContainer itemContainer = sut.getOrCreateItemContainer(null, guid);
+		
+		assertThat(itemContainer).isNull();
+	}
+	
+	@Test
+	public void getOrCreateItemContainer_guid_Guid_empty() {
+		BlogFileResource resource = new BlogFileResource();
+		Feed feed = new FeedImpl(resource);
+		
+		VFSContainer itemContainer = sut.getOrCreateItemContainer(feed, null);
+		
+		assertThat(itemContainer).isNull();
+		
+		fileResourceManager.deleteFileResource(resource);
+	}
+
+	@Test
+	public void getOrCreateItemMediaContainer_create() {
+		BlogFileResource resource = new BlogFileResource();
+		Feed feed = new FeedImpl(resource);
+		Item item = new ItemImpl(feed);
+		item.setGuid("guid");
+		
+		VFSContainer itemMediaContainer = sut.getOrCreateItemMediaContainer(item);
+		
+		assertThat(itemMediaContainer).isNotNull();
+		
+		fileResourceManager.deleteFileResource(resource);
+	}
+	
+	@Test
+	public void getOrCreateItemMediaContainer_get() {
+		BlogFileResource resource = new BlogFileResource();
+		Feed feed = new FeedImpl(resource);
+		Item item = new ItemImpl(feed);
+		item.setGuid("guid");
+		
+		sut.getOrCreateItemMediaContainer(item);
+		VFSContainer itemMediaContainer = sut.getOrCreateItemMediaContainer(item);
+		
+		assertThat(itemMediaContainer).isNotNull();
+		
+		fileResourceManager.deleteFileResource(resource);
+	}
+	
+	@Test
+	public void getOrCreateItemMediaContainer_Item_null() {
+		VFSContainer itemMediaContainer = sut.getOrCreateItemMediaContainer(null);
+		
+		assertThat(itemMediaContainer).isNull();
+	}
+	
+	@Test
+	public void getOrCreateItemMediaContainer_Feed_null() {
+		Item item = new ItemImpl(null);
+		item.setGuid("guid");
+		
+		VFSContainer itemMediaContainer = sut.getOrCreateItemMediaContainer(item);
+		
+		assertThat(itemMediaContainer).isNull();
+	}
+	
+	@Test
+	public void getOrCreateItemMediaContainer_Guid_empty() {
+		BlogFileResource resource = new BlogFileResource();
+		Feed feed = new FeedImpl(resource);
+		Item item = new ItemImpl(feed);
+		item.setGuid("");
+		
+		VFSContainer itemMediaContainer = sut.getOrCreateItemMediaContainer(item);
+		
+		assertThat(itemMediaContainer).isNull();
+		
+		fileResourceManager.deleteFileResource(resource);
+	}
+	
+	@Test
+	public void deleteItemContainer() {
+		BlogFileResource resource = new BlogFileResource();
+		Feed feed = new FeedImpl(resource);
+		Item item1 = new ItemImpl(feed);
+		item1.setGuid("guid 1");
+		sut.getOrCreateItemContainer(item1);
+		Item item2= new ItemImpl(feed);
+		item2.setGuid("guid 2");
+		sut.getOrCreateItemContainer(item2);
+		
+		sut.deleteItemContainer(item1);
+		
+		// check if there is only one item container left
+		assertThat(sut.getOrCreateFeedItemsContainer(feed).getItems().size()).isEqualTo(1);
+		
+		fileResourceManager.deleteFileResource(resource);
+	}
+	
+	@Test
+	public void deleteItemContainer_not_existing() {
+		BlogFileResource resource = new BlogFileResource();
+		Feed feed = new FeedImpl(resource);
+		Item item1 = new ItemImpl(feed);
+		item1.setGuid("guid 1");
+		Item item2= new ItemImpl(feed);
+		item2.setGuid("guid 2");
+		sut.getOrCreateItemContainer(item2);
+		
+		sut.deleteItemContainer(item1);
+		
+		// check if there is only one item container left
+		assertThat(sut.getOrCreateFeedItemsContainer(feed).getItems().size()).isEqualTo(1);
+		
+		fileResourceManager.deleteFileResource(resource);
+	}
+	
+	@Test
+	public void deleteItemContainer_null() {
+		BlogFileResource resource = new BlogFileResource();
+		Feed feed = new FeedImpl(resource);
+		Item item1 = new ItemImpl(feed);
+		item1.setGuid("guid 1");
+		sut.getOrCreateItemContainer(item1);
+		
+		sut.deleteItemContainer(null);
+		
+		// check if there is one item container left
+		assertThat(sut.getOrCreateFeedItemsContainer(feed).getItems().size()).isEqualTo(1);
+		
+		fileResourceManager.deleteFileResource(resource);
+	}
+	
+	@Test
+	public void saveFeedAsXML_new() {
+		BlogFileResource resource = new BlogFileResource();
+		Feed feed = new FeedImpl(resource);
+		feed.setAuthor("initialAuthor");
+		
+		sut.saveFeedAsXML(feed);
+		
+		// check if there is one file in the feed container
+		assertThat(sut.getOrCreateFeedContainer(feed).getItems().size()).isEqualTo(1);
+		
+		fileResourceManager.deleteFileResource(resource);
+	}
+	
+	@Test
+	public void saveFeedAsXML_overwrite() {
+		BlogFileResource resource = new BlogFileResource();
+		Feed feed = new FeedImpl(resource);
+		feed.setAuthor("initialAuthor");
+		sut.saveFeedAsXML(feed);
+		
+		feed.setAuthor("secondAuthor");
+		sut.saveFeedAsXML(feed);
+		
+		// check if there is one file in the feed container
+		assertThat(sut.getOrCreateFeedContainer(feed).getItems().size()).isEqualTo(1);
+		
+		fileResourceManager.deleteFileResource(resource);
+	}
+
+	@Test
+	public void saveFeedAsXML_null() {
+		sut.saveFeedAsXML(null);
+		
+		// no exception
+	}
+	
+	@Test
+	public void loadFeedFromXML_Ores() {
+		BlogFileResource resource = new BlogFileResource();
+		Feed feed = new FeedImpl(resource);
+		String autor = "autor";
+		feed.setAuthor(autor);
+		sut.saveFeedAsXML(feed);
+		
+		Feed reloaded = sut.loadFeedFromXML(resource);
+		
+		assertThat(reloaded).isNotNull();
+		assertThat(reloaded.getAuthor()).isEqualTo(autor);
+		
+		fileResourceManager.deleteFileResource(resource);
+	}
+	
+	@Test
+	public void loadFeedFromXML_Ores_not_existing() {
+		BlogFileResource resource = new BlogFileResource();
+		
+		Feed reloaded = sut.loadFeedFromXML(resource);
+		
+		assertThat(reloaded).isNull();
+		
+		fileResourceManager.deleteFileResource(resource);
+	}
+	
+	@Test
+	public void loadFeedFromXML_Path() {
+		BlogFileResource resource = new BlogFileResource();
+		Feed feed = new FeedImpl(resource);
+		String autor = "autor";
+		feed.setAuthor(autor);
+		sut.saveFeedAsXML(feed);
+		Path feedDir = fileResourceManager.getFileResource(resource).toPath();
+		
+		Feed reloaded = FeedManager.getInstance().loadFeedFromXML(feedDir);
+		
+		assertThat(reloaded).isNotNull();
+		assertThat(reloaded.getAuthor()).isEqualTo(autor);
+		
+		fileResourceManager.deleteFileResource(resource);
+	}
+	
+	@Test
+	public void loadFeedFromXML_Path_not_existing() {
+		Path feedDir = Paths.get("abc");
+		
+		Feed reloaded = FeedManager.getInstance().loadFeedFromXML(feedDir);
+		
+		assertThat(reloaded).isNull();
+	}
+	
+	@Test
+	public void deleteFeedXML() {
+		BlogFileResource resource = new BlogFileResource();
+		Feed feed = new FeedImpl(resource);
+		feed.setAuthor("initialAuthor");
+		sut.saveFeedAsXML(feed);
+		
+		sut.deleteFeedXML(feed);
+		
+		// check if there is no file in the feed container
+		assertThat(sut.getOrCreateFeedContainer(feed).getItems().size()).isEqualTo(0);
+		
+		fileResourceManager.deleteFileResource(resource);
+	}
+	
+	@Test
+	public void deleteFeedXML_not_existing() {
+		BlogFileResource resource = new BlogFileResource();
+		Feed feed = new FeedImpl(resource);
+		feed.setAuthor("initialAuthor");
+		
+		sut.deleteFeedXML(feed);
+		
+		// check if there is no file in the feed container
+		assertThat(sut.getOrCreateFeedContainer(feed).getItems().size()).isEqualTo(0);
+		
+		fileResourceManager.deleteFileResource(resource);
+	}
+	
+	@Test
+	public void deleteFeedXMLnull() {
+		sut.deleteFeedXML(null);
+		
+		// no exception
+	}
+	
+	@Test
+	public void saveItemAsXML_new() {
+		BlogFileResource resource = new BlogFileResource();
+		Feed feed = new FeedImpl(resource);
+		Item item = new ItemImpl(feed);
+		item.setGuid("123");
+		item.setAuthor("autor");
+		
+		sut.saveItemAsXML(item);
+		
+		// check if there is one file in the item container
+		assertThat(sut.getOrCreateItemContainer(item).getItems().size()).isEqualTo(1);
+		
+		fileResourceManager.deleteFileResource(resource);
+	}
+	
+	@Test
+	public void saveItemAsXML_overwrite() {
+		BlogFileResource resource = new BlogFileResource();
+		Feed feed = new FeedImpl(resource);
+		Item item = new ItemImpl(feed);
+		item.setGuid("123");
+		item.setAuthor("autor");
+		sut.saveItemAsXML(item);
+		
+		item.setAuthor("secondAuthor");
+		sut.saveItemAsXML(item);
+		
+		// check if there is one file in the item container
+		assertThat(sut.getOrCreateItemContainer(item).getItems().size()).isEqualTo(1);
+		
+		fileResourceManager.deleteFileResource(resource);
+	}
+
+	@Test
+	public void saveItemAsXML_null() {
+		sut.saveItemAsXML(null);
+		
+		// no exception
+	}
+	
+	@Test
+	public void loadItemFromXML() {
+		BlogFileResource resource = new BlogFileResource();
+		Feed feed = new FeedImpl(resource);
+		Item item = new ItemImpl(feed);
+		String guid = "guid öüä";
+		item.setGuid(guid);
+		String autor = "autor";
+		item.setAuthor(autor);
+		sut.saveItemAsXML(item);
+		VFSContainer itemContainer = sut.getOrCreateItemContainer(item); 
+		
+		Item reloaded = sut.loadItemFromXML(itemContainer);
+		
+		assertThat(reloaded).isNotNull();
+		assertThat(reloaded.getAuthor()).isEqualTo(autor);
+		
+		fileResourceManager.deleteFileResource(resource);
+	}
+	
+	@Test
+	public void loadItemFromXML_not_existing() {
+		BlogFileResource resource = new BlogFileResource();
+		Feed feed = new FeedImpl(resource);
+		Item item = new ItemImpl(feed);
+		String guid = "guid öüä";
+		item.setGuid(guid);
+		String autor = "autor";
+		item.setAuthor(autor);
+		VFSContainer itemContainer = sut.getOrCreateItemContainer(item); 
+		
+		Item reloaded = sut.loadItemFromXML(itemContainer);
+		
+		assertThat(reloaded).isNull();
+		
+		fileResourceManager.deleteFileResource(resource);
+	}
+	
+	@Test
+	public void loadItemFromXML_null() {
+		Item reloaded = sut.loadItemFromXML(null);
+		
+		assertThat(reloaded).isNull();
+	}
+	
+	@Test
+	public void loadItemsFromXML() {
+		BlogFileResource resource = new BlogFileResource();
+		Feed feed = new FeedImpl(resource);
+		Item item1 = new ItemImpl(feed);
+		String guid1 = "guid 1";
+		item1.setGuid(guid1);
+		sut.saveItemAsXML(item1);
+		Item item2 = new ItemImpl(feed);
+		String guid2 = "guid 2";
+		item2.setGuid(guid2);
+		sut.saveItemAsXML(item2);
+		Item item3 = new ItemImpl(feed);
+		String guid3 = "guid 3";
+		item3.setGuid(guid3);
+		sut.saveItemAsXML(item3);
+		
+		List<Item> items = sut.loadItemsFromXML(feed);
+		
+		assertThat(items.size()).isEqualTo(3);
+		
+		fileResourceManager.deleteFileResource(resource);
+	}
+	
+	@Test
+	public void loadItemsFromXML_empty() {
+		BlogFileResource resource = new BlogFileResource();
+		Feed feed = new FeedImpl(resource);
+		
+		List<Item> items = sut.loadItemsFromXML(feed);
+		
+		assertThat(items.size()).isEqualTo(0);
+		
+		fileResourceManager.deleteFileResource(resource);
+	}
+	
+	@Test
+	public void loadItemsFromXML_null() {
+		List<Item> items = sut.loadItemsFromXML(null);
+		
+		assertThat(items.size()).isEqualTo(0);
+	}
+	
+	@Test
+	public void deleteItemXML() {
+		BlogFileResource resource = new BlogFileResource();
+		Feed feed = new FeedImpl(resource);
+		Item item = new ItemImpl(feed);
+		item.setGuid("123");
+		item.setAuthor("autor");
+		sut.saveItemAsXML(item);
+		
+		sut.deleteItemXML(item);
+		
+		// check if there is no file in the item container
+		assertThat(sut.getOrCreateItemContainer(item).getItems().size()).isEqualTo(0);
+		
+		fileResourceManager.deleteFileResource(resource);
+	}
+	
+	@Test
+	public void deleteItemXML_not_existing() {
+		BlogFileResource resource = new BlogFileResource();
+		Feed feed = new FeedImpl(resource);
+		Item item = new ItemImpl(feed);
+		item.setGuid("123");
+		item.setAuthor("autor");
+		
+		sut.deleteItemXML(item);
+		
+		// check if there is no file in the item container
+		assertThat(sut.getOrCreateItemContainer(item).getItems().size()).isEqualTo(0);
+		
+		fileResourceManager.deleteFileResource(resource);
+	}
+	
+	@Test
+	public void deleteItemXMLnull() {
+		sut.deleteItemXML(null);
+		
+		// no exception
+	}
+
+}
diff --git a/src/test/java/org/olat/modules/webFeed/manager/FeedManagerImplTest.java b/src/test/java/org/olat/modules/webFeed/manager/FeedManagerImplTest.java
new file mode 100644
index 00000000000..abef98be3f3
--- /dev/null
+++ b/src/test/java/org/olat/modules/webFeed/manager/FeedManagerImplTest.java
@@ -0,0 +1,236 @@
+/**
+* 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) since 2004 at Multimedia- & E-Learning Services (MELS),<br>
+* University of Zurich, Switzerland.
+* <hr>
+* <a href="http://www.openolat.org">
+* OpenOLAT - Online Learning and Training</a><br>
+* This file has been modified by the OpenOLAT community. Changes are licensed
+* under the Apache 2.0 license as the original file.  
+* <p>
+*/
+package org.olat.modules.webFeed.manager;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.olat.core.util.coordinate.Coordinator;
+import org.olat.core.util.coordinate.CoordinatorManager;
+import org.olat.core.util.coordinate.Syncer;
+import org.olat.fileresource.FileResourceManager;
+import org.olat.modules.webFeed.Feed;
+import org.olat.modules.webFeed.Item;
+import org.olat.resource.OLATResource;
+import org.olat.resource.OLATResourceManager;
+import org.springframework.test.util.ReflectionTestUtils;
+
+/**
+ * 
+ * Initial date: 12.05.2017<br>
+ * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
+ *
+ */
+public class FeedManagerImplTest {
+	
+	@Mock
+	private FeedDAO feedDAOMock;
+	@Mock
+	private ItemDAO itemDAOMock;
+	@Mock
+	private FeedFileStorge feedFileStorageMock;
+	@Mock
+	private OLATResourceManager resourceManagerMock;
+	@Mock
+	private FileResourceManager fileResourceManagerMock;
+	@Mock
+	private CoordinatorManager coordinaterManagerMock;
+	@Mock
+	private Coordinator coordinaterMock;
+	@Mock
+	OLATResource resourceDummy;
+	@Mock
+	Feed feedDummy;
+	@Mock
+	Item itemDummy;
+	@Mock
+	Syncer syncerDummy;
+	
+	private FeedManagerImpl sut;
+	
+	@Before
+	public void injectNonSpringManagers() {
+		MockitoAnnotations.initMocks(this);
+		when(coordinaterManagerMock.getCoordinator()).thenReturn(coordinaterMock);
+		when(coordinaterMock.getSyncer()).thenReturn(syncerDummy);
+		sut = new FeedManagerImpl(resourceManagerMock, fileResourceManagerMock, coordinaterManagerMock);
+		feedDAOMock = mock(FeedDAO.class);
+		ReflectionTestUtils.setField(sut, "feedDAO", feedDAOMock);
+		ReflectionTestUtils.setField(sut, "itemDAO", itemDAOMock);
+		ReflectionTestUtils.setField(sut, "feedFileStorage", feedFileStorageMock);
+		
+	}
+	
+	@Test
+	public void importShouldNothingDoIfNoXmlFileIsPresent() {
+		when(feedFileStorageMock.loadFeedFromXML(any(OLATResource.class))).thenReturn(null);
+		
+		sut.importFeedFromXML(any(OLATResource.class));
+		
+		verifyZeroInteractions(feedDAOMock);
+		verifyZeroInteractions(itemDAOMock);
+	}
+	
+	@Test
+	public void importShouldSaveFeedToDatabase() {
+		when(feedFileStorageMock.loadFeedFromXML(any(OLATResource.class))).thenReturn(feedDummy);
+		
+		sut.importFeedFromXML(resourceDummy);
+		
+		verify(feedDAOMock).createFeed(any(Feed.class));
+	}
+	
+	@Test
+	public void importShouldSaveItemsToDatabase() {
+		List<Item> items = java.util.Arrays.asList(itemDummy, itemDummy, itemDummy);
+
+		when(feedDAOMock.loadFeed(any(OLATResource.class))).thenReturn(feedDummy);		
+		when(feedFileStorageMock.loadFeedFromXML(any(OLATResource.class))).thenReturn(feedDummy);
+		when(feedFileStorageMock.loadItemsFromXML(resourceDummy)).thenReturn(items);
+		
+		sut.importFeedFromXML(resourceDummy);
+		
+		verify(itemDAOMock, times(3)).createItem(any(Feed.class), any(Item.class));
+	}
+	
+	@Test
+	public void importShouldNotCreateItemsIfNoXmlFilesArePresent() {
+		List<Item> emtpyList = new ArrayList<>();
+
+		when(feedDAOMock.loadFeed(any(OLATResource.class))).thenReturn(feedDummy);		
+		when(feedFileStorageMock.loadFeedFromXML(any(OLATResource.class))).thenReturn(feedDummy);
+		when(feedFileStorageMock.loadItemsFromXML(resourceDummy)).thenReturn(emtpyList);
+		
+		sut.importFeedFromXML(resourceDummy);
+
+		verifyZeroInteractions(itemDAOMock);
+	}
+	
+	@Test
+	public void importShoulDeleteAuthorKey() {
+		List<Item> items = java.util.Arrays.asList(itemDummy);
+
+		when(feedDAOMock.loadFeed(any(OLATResource.class))).thenReturn(feedDummy);		
+		when(feedFileStorageMock.loadFeedFromXML(any(OLATResource.class))).thenReturn(feedDummy);
+		when(feedFileStorageMock.loadItemsFromXML(resourceDummy)).thenReturn(items);
+		when(itemDAOMock.loadItemByGuid(any(Long.class), any(String.class))).thenReturn(itemDummy);
+		
+		sut.importFeedFromXML(resourceDummy);
+		
+		verify(itemDummy).setAuthorKey(null);
+	}
+	
+	@Test
+	public void importShoulDeleteModifierKey() {
+		List<Item> items = java.util.Arrays.asList(itemDummy);
+
+		when(feedDAOMock.loadFeed(any(OLATResource.class))).thenReturn(feedDummy);		
+		when(feedFileStorageMock.loadFeedFromXML(any(OLATResource.class))).thenReturn(feedDummy);
+		when(feedFileStorageMock.loadItemsFromXML(resourceDummy)).thenReturn(items);
+		when(itemDAOMock.loadItemByGuid(any(Long.class), any(String.class))).thenReturn(itemDummy);
+		
+		sut.importFeedFromXML(resourceDummy);
+		
+		verify(itemDummy).setModifierKey(null);
+	}
+	
+	@Test
+	public void importShoulDeleteFeedXmlFile() {
+		when(feedDAOMock.loadFeed(any(OLATResource.class))).thenReturn(feedDummy);		
+		when(feedFileStorageMock.loadFeedFromXML(any(OLATResource.class))).thenReturn(feedDummy);
+
+		sut.importFeedFromXML(resourceDummy);
+		
+		verify(feedFileStorageMock).deleteFeedXML(feedDummy);
+	}
+	
+	@Test
+	public void importShoulDeleteItemsXmlFiles() {
+		List<Item> items = java.util.Arrays.asList(itemDummy, itemDummy, itemDummy);
+
+		when(feedDAOMock.loadFeed(any(OLATResource.class))).thenReturn(feedDummy);		
+		when(feedFileStorageMock.loadFeedFromXML(any(OLATResource.class))).thenReturn(feedDummy);
+		when(feedFileStorageMock.loadItemsFromXML(resourceDummy)).thenReturn(items);
+		
+		sut.importFeedFromXML(resourceDummy);
+
+		verify(feedFileStorageMock, times(3)).deleteItemXML(itemDummy);
+	}
+	
+	@Test
+	public void exportShouldWriteFeedToXmlFile() {
+		when(feedDAOMock.loadFeed(any(OLATResource.class))).thenReturn(feedDummy);	
+
+		sut.getFeedArchive(resourceDummy);
+		
+		verify(feedFileStorageMock).saveFeedAsXML(feedDummy);
+	}
+	
+	@Test
+	public void exportShouldWriteItemsToXmlFiles() {
+		List<Item> threeItems = java.util.Arrays.asList(itemDummy, itemDummy, itemDummy);
+		
+		when(itemDAOMock.loadItems(any(Feed.class))).thenReturn(threeItems);
+		when(feedDAOMock.loadFeed(any(OLATResource.class))).thenReturn(feedDummy);
+
+		sut.getFeedArchive(resourceDummy);
+		
+		verify(feedFileStorageMock, times(3)).saveItemAsXML(itemDummy);
+	}
+	
+	@Test
+	public void exportShoulDeleteFeedXmlFile() {
+		when(feedDAOMock.loadFeed(any(OLATResource.class))).thenReturn(feedDummy);	
+
+		sut.getFeedArchive(resourceDummy);
+		
+		verify(feedFileStorageMock).deleteFeedXML(feedDummy);
+	}
+	
+	@Test
+	public void exportShoulDeleteItemsXmlFiles() {
+		List<Item> threeItems = java.util.Arrays.asList(itemDummy, itemDummy, itemDummy);
+
+		when(itemDAOMock.loadItems(any(Feed.class))).thenReturn(threeItems);
+		when(feedDAOMock.loadFeed(any(OLATResource.class))).thenReturn(feedDummy);
+		
+		sut.getFeedArchive(resourceDummy);
+
+		verify(feedFileStorageMock, times(3)).deleteItemXML(itemDummy);
+	}
+	
+}
diff --git a/src/test/java/org/olat/modules/webFeed/manager/ItemDAOTest.java b/src/test/java/org/olat/modules/webFeed/manager/ItemDAOTest.java
new file mode 100644
index 00000000000..5bda874655d
--- /dev/null
+++ b/src/test/java/org/olat/modules/webFeed/manager/ItemDAOTest.java
@@ -0,0 +1,570 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <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 the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <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>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.webFeed.manager;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.List;
+
+import org.junit.Test;
+import org.olat.core.commons.persistence.DB;
+import org.olat.modules.webFeed.Enclosure;
+import org.olat.modules.webFeed.Feed;
+import org.olat.modules.webFeed.Item;
+import org.olat.modules.webFeed.model.EnclosureImpl;
+import org.olat.modules.webFeed.model.ItemImpl;
+import org.olat.resource.OLATResource;
+import org.olat.test.JunitTestHelper;
+import org.olat.test.OlatTestCase;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 05.05.2017<br>
+ * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
+ *
+ */
+public class ItemDAOTest extends OlatTestCase {
+
+	@Autowired
+	private DB dbInstance;
+	@Autowired
+	private FeedDAO feedDao;
+	@Autowired
+	private ItemDAO itemDao;
+	
+	@Test
+	public void createItem() {
+		OLATResource resource = JunitTestHelper.createRandomResource();
+		Feed feed = feedDao.createFeedForResourcable(resource);
+		dbInstance.commitAndCloseSession();
+		
+		Item item = itemDao.createItem(feed);
+		dbInstance.commitAndCloseSession();
+
+		//check values
+		assertThat(item.getKey()).isNotNull();
+		assertThat(item.getCreationDate()).isNotNull();
+		assertThat(item.getLastModified()).isNotNull();
+		assertThat(item.getFeed()).isEqualTo(feed);
+	}
+	
+	@Test
+	public void createItem_Null() {
+		Item item = itemDao.createItem(null);
+		dbInstance.commitAndCloseSession();
+
+		//check values
+		assertThat(item).isNull();
+	}
+	
+	@Test
+	public void createItem_Feed_Item() {
+		OLATResource resource = JunitTestHelper.createRandomResource();
+		Feed feed = feedDao.createFeedForResourcable(resource);
+		dbInstance.commitAndCloseSession();
+		
+		Item tempItem = new ItemImpl(feed);
+		
+		Item item = itemDao.createItem(feed, tempItem);
+		dbInstance.commitAndCloseSession();
+
+		//check values
+		assertThat(item.getKey()).isNotNull();
+		assertThat(item.getCreationDate()).isNotNull();
+		assertThat(item.getLastModified()).isNotNull();
+		assertThat(item.getFeed()).isEqualTo(feed);
+	}
+	
+	@Test
+	public void createItem_Feed_Item_keepDates() {
+		OLATResource resource = JunitTestHelper.createRandomResource();
+		Feed feed = feedDao.createFeedForResourcable(resource);
+		dbInstance.commitAndCloseSession();
+		
+		Item tempItem = new ItemImpl(feed);
+		Date created = new GregorianCalendar(2000, 1, 1).getTime();
+		tempItem.setCreationDate(created);
+		Date modified = new GregorianCalendar(2000, 2, 2).getTime();
+		tempItem.setLastModified(modified);
+		
+		Item item = itemDao.createItem(feed, tempItem);
+		dbInstance.commitAndCloseSession();
+
+		//check values
+		assertThat(item.getCreationDate()).isCloseTo(created, 1000);
+		assertThat(item.getLastModified()).isCloseTo(modified, 1000);
+	}
+	
+	@Test
+	public void createItem_Feed_Null() {
+		Item tempItem = new ItemImpl(null);
+		Item item = itemDao.createItem(null, tempItem);
+		dbInstance.commitAndCloseSession();
+
+		//check values
+		assertThat(item).isNull();
+	}
+	
+	@Test
+	public void copyItem_Feed_Item() {
+		OLATResource resource = JunitTestHelper.createRandomResource();
+		Feed feed = feedDao.createFeedForResourcable(resource);
+		dbInstance.commitAndCloseSession();
+		Item item = itemDao.createItem(feed);
+		dbInstance.commitAndCloseSession();
+		
+		Item copy = itemDao.copyItem(feed, item);
+		dbInstance.commitAndCloseSession();
+
+		//check values
+		assertThat(copy.getKey()).isNotEqualTo(item.getKey());
+		assertThat(copy.getCreationDate()).isCloseTo(item.getCreationDate(), 1000);
+		assertThat(copy.getLastModified()).isCloseTo(item.getLastModified(), 1000);
+	}
+	
+	@Test
+	public void copyItem_Feed_null() {
+		OLATResource resource = JunitTestHelper.createRandomResource();
+		Feed feed = feedDao.createFeedForResourcable(resource);
+		dbInstance.commitAndCloseSession();
+		Item item = itemDao.createItem(feed);
+		dbInstance.commitAndCloseSession();
+		
+		Item copy = itemDao.copyItem(null, item);
+		dbInstance.commitAndCloseSession();
+
+		//check values
+		assertThat(copy).isNull();
+	}
+	
+	@Test
+	public void copyItem_Feed_Item_null() {
+		OLATResource resource = JunitTestHelper.createRandomResource();
+		Feed feed = feedDao.createFeedForResourcable(resource);
+		dbInstance.commitAndCloseSession();
+		
+		Item copy = itemDao.copyItem(feed, null);
+		dbInstance.commitAndCloseSession();
+
+		//check values
+		assertThat(copy).isNull();
+	}
+	
+	@Test
+	public void copyItem_Feed_Item_noCreationDate() {
+		OLATResource resource = JunitTestHelper.createRandomResource();
+		Feed feed = feedDao.createFeedForResourcable(resource);
+		dbInstance.commitAndCloseSession();
+		Item item = itemDao.createItem(feed);
+		dbInstance.commitAndCloseSession();
+		item.setCreationDate(null);
+		
+		Item copy = itemDao.copyItem(feed, item);
+		dbInstance.commitAndCloseSession();
+
+		//check values
+		assertThat(copy).isNull();
+	}
+	
+	@Test
+	public void copyItem_Feed_Item_noLastModified() {
+		OLATResource resource = JunitTestHelper.createRandomResource();
+		Feed feed = feedDao.createFeedForResourcable(resource);
+		dbInstance.commitAndCloseSession();
+		Item item = itemDao.createItem(feed);
+		dbInstance.commitAndCloseSession();
+		item.setLastModified(null);
+		
+		Item copy = itemDao.copyItem(feed, item);
+		dbInstance.commitAndCloseSession();
+
+		//check values
+		assertThat(copy).isNull();
+	}
+	
+	@Test
+	public void loadItem() {
+		OLATResource resource = JunitTestHelper.createRandomResource();
+		Feed feed = feedDao.createFeedForResourcable(resource);
+		dbInstance.commitAndCloseSession();
+
+		// create an item
+		Item item = itemDao.createItem(feed);
+		dbInstance.commitAndCloseSession();
+		
+		// reload the item from the database
+		Item reloaded = itemDao.loadItem(item.getKey());
+
+		//check values
+		assertThat(reloaded.getKey()).isEqualTo(item.getKey());
+		assertThat(reloaded.getFeed()).isEqualTo(feed);
+	}
+	
+	@Test
+	public void loadItem_notExisting() {	
+		// load item for a non existing key
+		Item item = itemDao.loadItem(-1L);
+
+		// the item should be null
+		assertThat(item).isNull();
+	}
+	
+	@Test
+	public void loadItemByGuid() {
+		OLATResource resource = JunitTestHelper.createRandomResource();
+		Feed feed = feedDao.createFeedForResourcable(resource);
+		dbInstance.commitAndCloseSession();
+
+		// create an item
+		String guid = "guid-123";
+		Item tempItem = new ItemImpl(feed);
+		tempItem.setGuid(guid);
+		itemDao.createItem(feed, tempItem);
+		dbInstance.commitAndCloseSession();
+		
+		// reload the item from the database
+		Item item = itemDao.loadItemByGuid(feed.getKey(), guid);
+
+		//check values
+		assertThat(item).isNotNull();
+	}
+	
+	@Test
+	public void loadItemByGuid_guidTwoTimes() {
+		OLATResource resource = JunitTestHelper.createRandomResource();
+		Feed feed = feedDao.createFeedForResourcable(resource);
+		OLATResource resource2 = JunitTestHelper.createRandomResource();
+		Feed feed2 = feedDao.createFeedForResourcable(resource2);
+		dbInstance.commitAndCloseSession();
+
+		// create an item
+		String guid = "guid-123";
+		Item tempItem = new ItemImpl(feed);
+		tempItem.setGuid(guid);
+		itemDao.createItem(feed, tempItem);
+		Item tempItem2 = new ItemImpl(feed2);
+		tempItem2.setGuid(guid);
+		itemDao.createItem(feed2, tempItem2);
+		dbInstance.commitAndCloseSession();
+		
+		// reload the item from the database
+		Item item = itemDao.loadItemByGuid(feed.getKey(), guid);
+
+		//check values
+		assertThat(item).isNotNull();
+	}
+	
+	@Test
+	public void loadItemByGuid_notExisting() {
+		OLATResource resource = JunitTestHelper.createRandomResource();
+		Feed feed = feedDao.createFeedForResourcable(resource);
+		dbInstance.commitAndCloseSession();
+		
+		// load item for a non existing guid
+		Item item = itemDao.loadItemByGuid(feed.getKey(), "guid-no");
+
+		// the item should be null
+		assertThat(item).isNull();
+	}
+
+	
+	@Test
+	public void loadItemByGuid_Feed_null() {	
+		OLATResource resource = JunitTestHelper.createRandomResource();
+		Feed feed = feedDao.createFeedForResourcable(resource);
+		dbInstance.commitAndCloseSession();
+
+		// create an item
+		String guid = "guid-123";
+		Item tempItem = new ItemImpl(feed);
+		tempItem.setGuid(guid);
+		itemDao.createItem(feed, tempItem);
+		dbInstance.commitAndCloseSession();
+		
+		// reload the item from the database
+		Item item = itemDao.loadItemByGuid(null, guid);
+
+		//check values
+		assertThat(item).isNull();
+	}
+	
+	@Test
+	public void loadItems_Feed() {
+		OLATResource resource = JunitTestHelper.createRandomResource();
+		Feed feed = feedDao.createFeedForResourcable(resource);
+		dbInstance.commitAndCloseSession();
+
+		// create three items
+		int numberOfItems = 3;
+		for (int i = 0; i < numberOfItems; i++) {
+			itemDao.createItem(feed);
+		}
+		dbInstance.commitAndCloseSession();
+		
+		List<Item> items = itemDao.loadItems(feed);
+
+		// check if all three items of the feed are loaded
+		assertThat(items.size()).isEqualTo(3);
+	}
+	
+	@Test
+	public void loadItemsGuid_Feed() {
+		OLATResource resource = JunitTestHelper.createRandomResource();
+		Feed feed = feedDao.createFeedForResourcable(resource);
+		dbInstance.commitAndCloseSession();
+
+		// create three items
+		int numberOfItems = 3;
+		for (int i = 0; i < numberOfItems; i++) {
+			itemDao.createItem(feed);
+		}
+		dbInstance.commitAndCloseSession();
+		
+		List<String> items = itemDao.loadItemsGuid(feed);
+
+		// check if all three guids of the feed are loaded
+		assertThat(items.size()).isEqualTo(3);
+	}
+	
+	@Test
+	public void loadItemsGuid_null() {
+		List<String> items = itemDao.loadItemsGuid(null);
+
+		assertThat(items).isNull();
+	}
+	
+	@Test
+	public void loadPublishedItems_Feed() {
+		OLATResource resource = JunitTestHelper.createRandomResource();
+		Feed feed = feedDao.createFeedForResourcable(resource);
+		dbInstance.commitAndCloseSession();
+
+		// create three items
+		int numberOfItems = 4;
+		for (int i = 0; i < numberOfItems; i++) {
+			Item item = new ItemImpl(feed);
+		
+			// Every 2nd Item has a publish date in the past
+			if (i%2 == 0) {
+				Date date = Date.from(LocalDate.of(2000, 1, 1).atStartOfDay(ZoneId.systemDefault()).toInstant());
+				item.setPublishDate(date);
+			}
+			itemDao.createItem(feed, item);
+		}
+		dbInstance.commitAndCloseSession();
+		
+		List<Item> items = itemDao.loadPublishedItems(feed);
+
+		// check if two items of the feed are loaded
+		assertThat(items.size()).isEqualTo(2);
+	}
+
+	
+	@Test
+	public void loadPublishedItems_null() {
+		List<Item> items = itemDao.loadPublishedItems(null);
+
+		assertThat(items).isNull();
+	}
+	
+	@Test
+	public void hasItems_Feed_true() {
+		OLATResource resource = JunitTestHelper.createRandomResource();
+		Feed feed = feedDao.createFeedForResourcable(resource);
+		dbInstance.commitAndCloseSession();
+
+		// create three items
+		int numberOfItems = 3;
+		for (int i = 0; i < numberOfItems; i++) {
+			itemDao.createItem(feed);
+		}
+		dbInstance.commitAndCloseSession();
+		
+		boolean hasItems = itemDao.hasItems(feed);
+
+		assertThat(hasItems).isTrue();
+	}
+	
+	@Test
+	public void hasItems_Feed_false() {
+		OLATResource resource = JunitTestHelper.createRandomResource();
+		Feed feed = feedDao.createFeedForResourcable(resource);
+		dbInstance.commitAndCloseSession();
+		
+		boolean hasItems = itemDao.hasItems(feed);
+
+		assertThat(hasItems).isFalse();
+	}
+	
+	@Test
+	public void hasItems_null() {
+		boolean hasItems = itemDao.hasItems(null);
+
+		assertThat(hasItems).isFalse();
+	}
+	
+	@Test
+	public void updateItem() {
+		OLATResource resource = JunitTestHelper.createRandomResource();
+		Feed feed = feedDao.createFeedForResourcable(resource);
+		dbInstance.commitAndCloseSession();
+		
+		Item item = itemDao.createItem(feed);
+		dbInstance.commitAndCloseSession();
+		
+		// update the item
+		item.setAuthor("author");
+		item.setAuthorKey(1L);
+		item.setContent("content");
+		item.setDescription("description");
+		item.setDraft(true);
+		item.setExternalLink("https://example.com/");
+		item.setGuid("guid-2");
+		item.setHeight(3);
+		item.setModifierKey(2L);
+		item.setPublishDate(new Date());
+		item.setTitle("tile");
+		item.setWidth(5);
+		Enclosure enclosure = new EnclosureImpl();
+		enclosure.setExternalUrl("http://exterla.url/abc.jpg");
+		enclosure.setFileName("Filename.jpg");
+		enclosure.setLength(9L);
+		enclosure.setType("type");
+		item.setEnclosure(enclosure);
+		Item updated = itemDao.updateItem(item);
+
+		//check values
+		assertThat(updated.getAuthor()).isEqualTo(item.getAuthor());
+		assertThat(updated.getAuthorKey()).isEqualTo(item.getAuthorKey());
+		assertThat(updated.getContent()).isEqualTo(item.getContent());
+		assertThat(updated.getDescription()).isEqualTo(item.getDescription());
+		assertThat(updated.isDraft()).isEqualTo(item.isDraft());
+		assertThat(updated.getExternalLink()).isEqualTo(item.getExternalLink());
+		assertThat(updated.getGuid()).isEqualTo(item.getGuid());
+		assertThat(updated.getHeight()).isEqualTo(item.getHeight());
+		assertThat(updated.getModifierKey()).isEqualTo(item.getModifierKey());
+		assertThat(updated.getPublishDate()).isEqualTo(item.getPublishDate());
+		assertThat(updated.getTitle()).isEqualTo(item.getTitle());
+		assertThat(updated.getWidth()).isEqualTo(item.getWidth());
+		assertThat(updated.getEnclosure().getExternalUrl()).isEqualTo(item.getEnclosure().getExternalUrl());
+		assertThat(updated.getEnclosure().getFileName()).isEqualTo(item.getEnclosure().getFileName());
+		assertThat(updated.getEnclosure().getLength()).isEqualTo(item.getEnclosure().getLength());
+		assertThat(updated.getEnclosure().getType()).isEqualTo(item.getEnclosure().getType());
+	}
+	
+	@Test
+	public void updateItem_null() {
+		OLATResource resource = JunitTestHelper.createRandomResource();
+		feedDao.createFeedForResourcable(resource);
+		dbInstance.commitAndCloseSession();
+		Item item = itemDao.createItem(null);
+		dbInstance.commitAndCloseSession();
+
+		Item updated = itemDao.updateItem(item);
+		
+		assertThat(updated).isNull();
+	}
+		
+	
+	@Test
+	public void removeItem() {
+		OLATResource resource = JunitTestHelper.createRandomResource();
+		Feed feed = feedDao.createFeedForResourcable(resource);
+		dbInstance.commitAndCloseSession();
+		
+		// create three items
+		Item item1 = itemDao.createItem(feed);
+		Item item2 = itemDao.createItem(feed);
+		Item item3 = itemDao.createItem(feed);
+		dbInstance.commitAndCloseSession();
+				
+		// delete one item
+		int numberOfDeletedItems = itemDao.removeItem(item2);
+		dbInstance.commitAndCloseSession();
+		
+		// one item should be deleted
+		assertThat(numberOfDeletedItems).isEqualTo(1);
+		
+		// check if one item is deleted and the two other items are still in
+		// the database
+		assertThat(itemDao.loadItem(item1.getKey())).isNotNull();
+		assertThat(itemDao.loadItem(item2.getKey())).isNull();
+		assertThat(itemDao.loadItem(item3.getKey())).isNotNull();
+	}
+	
+	@Test
+	public void removeItems_Feed() {
+		OLATResource resource = JunitTestHelper.createRandomResource();
+		Feed feed = feedDao.createFeedForResourcable(resource);
+		dbInstance.commitAndCloseSession();
+		
+		// create three items
+		Item item1 = itemDao.createItem(feed);
+		Item item2 = itemDao.createItem(feed);
+		Item item3 = itemDao.createItem(feed);
+		dbInstance.commitAndCloseSession();
+				
+		// delete all items of the feed
+		int numberOfDeletedItems = itemDao.removeItems(feed);
+		dbInstance.commitAndCloseSession();
+		
+		// three item should be deleted
+		assertThat(numberOfDeletedItems).isEqualTo(3);
+		
+		// check if items are deleted
+		assertThat(itemDao.loadItem(item1.getKey())).isNull();
+		assertThat(itemDao.loadItem(item2.getKey())).isNull();
+		assertThat(itemDao.loadItem(item3.getKey())).isNull();
+		
+		// the feed should still exist
+		assertThat(feedDao.loadFeed(resource)).isNotNull();
+	}
+	
+	@Test
+	public void removeItems_Feed_isNull() {
+		OLATResource resource = JunitTestHelper.createRandomResource();
+		Feed feed = feedDao.createFeedForResourcable(resource);
+		dbInstance.commitAndCloseSession();
+		
+		// create three items
+		Item item1 = itemDao.createItem(feed);
+		Item item2 = itemDao.createItem(feed);
+		Item item3 = itemDao.createItem(feed);
+		dbInstance.commitAndCloseSession();
+				
+		// remove should be handled properly
+		int numberOfDeletedItems = itemDao.removeItems(null);
+		dbInstance.commitAndCloseSession();
+		
+		// no item should be deleted
+		assertThat(numberOfDeletedItems).isEqualTo(0);
+		
+		// check if still all items are in the database
+		assertThat(itemDao.loadItem(item1.getKey())).isNotNull();
+		assertThat(itemDao.loadItem(item2.getKey())).isNotNull();
+		assertThat(itemDao.loadItem(item3.getKey())).isNotNull();
+	}
+	
+}
diff --git a/src/test/java/org/olat/modules/webFeed/manager/RomeFeedFetcherTest.java b/src/test/java/org/olat/modules/webFeed/manager/RomeFeedFetcherTest.java
new file mode 100644
index 00000000000..5027c787895
--- /dev/null
+++ b/src/test/java/org/olat/modules/webFeed/manager/RomeFeedFetcherTest.java
@@ -0,0 +1,282 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <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 the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <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>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.webFeed.manager;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.olat.core.id.OLATResourceable;
+import org.olat.modules.webFeed.Enclosure;
+import org.olat.modules.webFeed.Feed;
+import org.olat.modules.webFeed.Item;
+import org.olat.modules.webFeed.model.FeedImpl;
+
+import com.rometools.rome.feed.synd.SyndContent;
+import com.rometools.rome.feed.synd.SyndEnclosure;
+import com.rometools.rome.feed.synd.SyndEntry;
+import com.rometools.rome.feed.synd.SyndFeed;
+import com.rometools.rome.feed.synd.SyndImage;
+
+/**
+ * 
+ * Initial date: 12.05.2017<br>
+ * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
+ *
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class RomeFeedFetcherTest {
+
+	private RomeFeedFetcher sut = new RomeFeedFetcher();
+	
+	@Mock
+	Feed feedMock;
+	@Mock
+	OLATResourceable oresMock;
+	@Mock 
+	private SyndFeed syndFeedMock;
+	@Mock 
+	private SyndImage syndImageMock;
+	@Mock(answer = Answers.RETURNS_DEEP_STUBS)
+	private SyndEntry syndEntryMock;
+	@Mock(name="a")
+	private SyndEnclosure syndEnclosureMock;
+	@Mock
+	private SyndContent syndContentMock;
+	
+	@Test
+	public void justifiyFeed() {
+		String urlOld = "http://example.com/image1.jpg";
+		String urlNew = "http://example.com/image2.jpg";
+		Feed feed = new FeedImpl(oresMock);
+		feed.setExternalImageURL(urlOld);
+
+		when(syndImageMock.getUrl()).thenReturn(urlNew);
+		when(syndFeedMock.getImage()).thenReturn(syndImageMock);
+		
+		Feed justifiedFeed = sut.justifyFeed(feed, syndFeedMock);
+		
+		assertThat(justifiedFeed.getExternalImageURL()).isEqualTo(urlNew);
+	}
+	
+	@Test
+	public void justifiyFeed_externalImageIsNull() {
+		String urlOld = "http://example.com/image1.jpg";
+		Feed feed = new FeedImpl(oresMock);
+		feed.setExternalImageURL(urlOld);
+		
+		when(syndFeedMock.getImage()).thenReturn(null);
+		
+		Feed justifiedFeed = sut.justifyFeed(feed, syndFeedMock);
+		
+		assertThat(justifiedFeed.getExternalImageURL()).isNull();
+	}
+	
+	@Test
+	public void justifiyFeed_externalImageUrlIsNull() {
+		String urlOld = "http://example.com/image1.jpg";
+		String urlNew = null;
+		Feed feed = new FeedImpl(oresMock);
+		feed.setExternalImageURL(urlOld);
+		
+		when(syndFeedMock.getImage()).thenReturn(syndImageMock);
+		when(syndImageMock.getUrl()).thenReturn(urlNew);
+		
+		Feed justifiedFeed = sut.justifyFeed(feed, syndFeedMock);
+		
+		assertThat(justifiedFeed.getExternalImageURL()).isNull();
+	}
+	
+	@Test
+	public void justifiyFeed_Feed_null() {
+		String urlOld = "http://example.com/image1.jpg";
+		Feed feed = new FeedImpl(oresMock);
+		feed.setExternalImageURL(urlOld);
+		
+		Feed justifiedFeed = sut.justifyFeed(feed, null);
+		
+		assertThat(justifiedFeed.getExternalImageURL()).isEqualTo(urlOld);
+	}
+	
+	@Test
+	public void justifiyFeed_null_Feed() {
+		Feed justifiedFeed = sut.justifyFeed(null, syndFeedMock);
+		
+		assertThat(justifiedFeed).isNull();
+	}
+
+	@Test
+	public void convertEntry() {
+		// prepare the mock
+		String author = "author";
+		when(syndEntryMock.getAuthor()).thenReturn(author);
+		String description = "description";
+		when(syndEntryMock.getDescription().getValue()).thenReturn(description);
+		String link = "link";
+		when(syndEntryMock.getLink()).thenReturn(link);
+		String title = "title";
+		when(syndEntryMock.getTitle()).thenReturn(title);
+		String uri = "uri";
+		when(syndEntryMock.getUri()).thenReturn(uri);
+		
+		Date publishedDate = new Date();
+		when(syndEntryMock.getPublishedDate()).thenReturn(publishedDate);
+		Date updatedDate = new Date();
+		when(syndEntryMock.getUpdatedDate()).thenReturn(updatedDate);
+		
+		List<SyndContent> contents = Arrays.asList(syndContentMock);
+		when(syndEntryMock.getContents()).thenReturn(contents);
+		
+		when(syndEnclosureMock.getUrl()).thenReturn("URL");
+		List<SyndEnclosure> enclosures = Arrays.asList(syndEnclosureMock);
+		when(syndEntryMock.getEnclosures()).thenReturn(enclosures);
+		
+		// call the method
+		Item item = sut.convertEntry(feedMock, syndEntryMock);
+		
+		// test
+		assertThat(item.getFeed()).isEqualTo(feedMock);
+		assertThat(item.getAuthor()).isEqualTo(author);
+		assertThat(item.getContent()).isNotNull();
+		assertThat(item.getDescription()).isEqualTo(description);
+		assertThat(item.getEnclosure()).isNotNull();
+		assertThat(item.getExternalLink()).isEqualTo(link);
+		assertThat(item.getTitle()).isEqualTo(title);
+		assertThat(item.getGuid()).isEqualTo(uri);
+		assertThat(item.getLastModified()).isEqualTo(updatedDate);
+		assertThat(item.getPublishDate()).isEqualTo(publishedDate);
+	}
+	
+	@Test
+	public void convertEntry_Description_null() {
+		when(syndEntryMock.getDescription()).thenReturn(null);
+		when(syndEntryMock.getContents()).thenReturn(null);
+
+		Item item = sut.convertEntry(feedMock, syndEntryMock);
+
+		assertThat(item.getDescription()).isNull();
+	}
+	
+	@Test
+	public void convertEnclosures() {
+		Long length = 1l;
+		when(syndEnclosureMock.getLength()).thenReturn(length);
+		String type = "type";
+		when(syndEnclosureMock.getType()).thenReturn(type);
+		String url = "url";
+		when(syndEnclosureMock.getUrl()).thenReturn(url);
+		List<SyndEnclosure> enclosures = Arrays.asList(syndEnclosureMock);
+		
+		Enclosure enclosure = sut.convertEnclosures(enclosures);
+		
+		assertThat(enclosure.getExternalUrl()).isEqualTo(url);
+		assertThat(enclosure.getLength()).isEqualTo(length);
+		assertThat(enclosure.getType()).isEqualTo(type);
+	}
+	
+	@Test
+	public void convertEnclosures_null() {
+		Enclosure enclosure = sut.convertEnclosures(null);
+		
+		assertThat(enclosure).isNull();
+	}
+	
+	@Test
+	public void convertEnclosures_emptyList() {
+		List<SyndEnclosure> enclosures = new ArrayList<>();
+		
+		Enclosure enclosure = sut.convertEnclosures(enclosures);
+		
+		assertThat(enclosure).isNull();
+	}
+	
+	@Test
+	public void convertEnclosures_Url_null() {
+		when(syndEnclosureMock.getUrl()).thenReturn(null);
+		List<SyndEnclosure> enclosures = Arrays.asList(syndEnclosureMock);
+		
+		Enclosure enclosure = sut.convertEnclosures(enclosures);
+		
+		assertThat(enclosure).isNull();
+	}
+	
+	@Test
+	public void convertEnclosures_multipleEntries() {
+		String url = "url";
+		when(syndEnclosureMock.getUrl()).thenReturn(url);
+		List<SyndEnclosure> enclosures = 
+				Arrays.asList(syndEnclosureMock, syndEnclosureMock);
+		
+		sut.convertEnclosures(enclosures);
+		
+		// verify that getType is invoked only once / only from one list entry
+		verify(syndEnclosureMock).getType();
+	}
+	
+	@Test
+	public void joinContents() {
+		String value = "value";
+		when(syndContentMock.getValue()).thenReturn(value);
+		List<SyndContent> contents = Arrays.asList(syndContentMock);
+		
+		String content = sut.joinContents(contents);
+		
+		assertThat(content).isEqualTo(value);
+	}
+	
+	@Test
+	public void joinContents_null() {
+		String content = sut.joinContents(null);
+		
+		assertThat(content).isNull();
+	}
+	
+	@Test
+	public void joinContents_emptyList() {
+		List<SyndContent> contents = new ArrayList<>();
+		
+		String content = sut.joinContents(contents);
+		
+		assertThat(content).isNull();
+	}
+	
+	@Test
+	public void joinContents_multipleEntries() {
+		String value = "value";
+		when(syndContentMock.getValue()).thenReturn(value);
+		List<SyndContent> contents = 
+				Arrays.asList(syndContentMock, syndContentMock, syndContentMock);
+		
+		String content = sut.joinContents(contents);
+		
+		assertThat(content).isEqualTo("value<p />value<p />value");
+	}
+	
+}
diff --git a/src/test/java/org/olat/test/AllTestsJunit4.java b/src/test/java/org/olat/test/AllTestsJunit4.java
index a320bd3e96f..1693c395a3d 100644
--- a/src/test/java/org/olat/test/AllTestsJunit4.java
+++ b/src/test/java/org/olat/test/AllTestsJunit4.java
@@ -179,6 +179,10 @@ import org.junit.runners.Suite;
 	org.olat.modules.reminder.manager.ReminderRuleEngineTest.class,
 	org.olat.modules.video.manager.VideoTranscodingDAOTest.class,
 	org.olat.modules.video.manager.VideoMetadataDAOTest.class,
+	org.olat.modules.webFeed.manager.FeedDAOTest.class,
+	org.olat.modules.webFeed.manager.ItemDAOTest.class,
+	org.olat.modules.webFeed.manager.FeedFileStorgeTest.class,
+	org.olat.modules.webFeed.manager.FeedManagerImplTest.class,
 	org.olat.properties.PropertyTest.class,
 	org.olat.search.service.document.file.FileDocumentFactoryTest.class,
 	org.olat.search.service.indexer.repository.course.SPCourseNodeIndexerTest.class,
@@ -218,7 +222,7 @@ import org.junit.runners.Suite;
 	org.olat.ims.qti21.model.xml.OpenOLATAssessementItemsTest.class,
 	org.olat.ims.qti21.model.xml.QTI21ExplorerHandlerTest.class,
 	org.olat.ims.lti.LTIManagerTest.class,
-	org.olat.modules.webFeed.FeedManagerImplTest.class,
+	org.olat.modules.webFeed.manager.FeedManagerImplTest.class,
 	org.olat.modules.qpool.manager.MetadataConverterHelperTest.class,
 	org.olat.modules.qpool.manager.QuestionDAOTest.class,
 	org.olat.modules.qpool.manager.FileStorageTest.class,
@@ -308,6 +312,7 @@ import org.junit.runners.Suite;
 	 * Pure JUnit test without need of framework
 	 */
 	org.olat.modules.fo.WordCountTest.class,
+	org.olat.modules.webFeed.manager.RomeFeedFetcherTest.class,
 	/**
 	 * 
 	 * Place tests which load their own Spring context
-- 
GitLab