/**
* OLAT - Online Learning and Training<br>
* http://www.olat.org
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); <br>
* you may not use this file except in compliance with the License.<br>
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing,<br>
* software distributed under the License is distributed on an "AS IS" BASIS, <br>
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
* See the License for the specific language governing permissions and <br>
* limitations under the License.
* <p>
* Copyright (c) frentix GmbH<br>
* http://www.frentix.com<br>
* <p>
*/
package org.olat.modules.webFeed.dispatching;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang.RandomStringUtils;
import org.olat.basesecurity.Authentication;
import org.olat.basesecurity.Manager;
import org.olat.basesecurity.ManagerFactory;
import org.olat.core.helpers.Settings;
import org.olat.core.id.Identity;
import org.olat.core.id.OLATResourceable;
import org.olat.core.util.cache.n.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.repository.RepositoryEntry;
import org.olat.repository.RepositoryManager;
/**
* The Path class.
* <p>
* Collects all parameters provided in the URL.
* <p>
* Implements caching of validated paths.
* <p>
* For examples see constructor documentation.
* <P>
* Initial Date: Apr 9, 2009 <br>
*
* @author gwassmann
*/
public class Path {
// Not private for better performance (apperently)
protected static final CacheWrapper validatedUriCache = CoordinatorManager.getCoordinator().getCacher().getOrCreateCache(Path.class,
"feed");
// Instance variables
private int type;
private Long courseId, feedId, identityKey;
private String originalPath, itemId, nodeId, iconFileName, itemFileName, token;
// Path types
private static final int FEED = 1;
private static final int FEED_MEDIA = 2;
private static final int ITEM_MEDIA = 3;
private static final int AUTHENTICATED = 10;
private static final int AUTHENTICATED_FEED = FEED + AUTHENTICATED;
private static final int AUTHENTICATED_FEED_MEDIA = FEED_MEDIA + AUTHENTICATED;
private static final int AUTHENTICATED_ITEM_MEDIA = ITEM_MEDIA + AUTHENTICATED;
private static final int COURSE = 20;
private static final int COURSE_FEED = FEED + COURSE;
private static final int COURSE_FEED_MEDIA = FEED_MEDIA + COURSE;
private static final int COURSE_ITEM_MEDIA = ITEM_MEDIA + COURSE;
private static final int AUTHENTICATED_COURSE_FEED = FEED + COURSE + AUTHENTICATED;
private static final int AUTHENTICATED_COURSE_ICON = FEED_MEDIA + COURSE + AUTHENTICATED;
private static final int AUTHENTICATED_COURSE_ITEM = ITEM_MEDIA + COURSE + AUTHENTICATED;
// Class variables
private static final String TOKEN_PROVIDER = FeedMediaDispatcher.TOKEN_PROVIDER;
// Patterns
private static final String NUMBER = "(\\d+)";
private static final String WORD = "(\\w+)";
private static final String FILE_NAME = "([^/]+\\.\\w{3,4})";
private static final String SLASH = "/";
private static final String BASE_PATH_DELIMITER = "/_/";
private static final String COURSE_NODE_INDICATOR = "coursenode";
public static final String MEDIA_DIR = "media";
private static final String AUTHENTICATION = NUMBER + SLASH + WORD + SLASH;
private static final String COURSE_PREFIX = COURSE_NODE_INDICATOR + SLASH;
private static final String COURSE_NODE = NUMBER + SLASH + WORD + SLASH;
private static final String FEED_ID = NUMBER + BASE_PATH_DELIMITER;
private static final String FEED_PATH = FEED_ID + FeedManager.RSS_FEED_NAME;
private static final String FEED_MEDIA_PATH = FEED_ID + MEDIA_DIR + SLASH + FILE_NAME;
private static final String ITEM_MEDIA_PATH = FEED_ID + WORD + SLASH + MEDIA_DIR + SLASH + FILE_NAME;
private static final String COURSE_PATH = COURSE_PREFIX + COURSE_NODE;
private static final String AUTH_COURSE_PATH = COURSE_PREFIX + AUTHENTICATION + COURSE_NODE;
// The final path patterns
private static final Pattern feedPattern = Pattern.compile(FEED_PATH);
private static final Pattern feedMediaPattern = Pattern.compile(FEED_MEDIA_PATH);
private static final Pattern itemMediaPattern = Pattern.compile(ITEM_MEDIA_PATH);
private static final Pattern authFeedPattern = Pattern.compile(AUTHENTICATION + FEED_PATH);
private static final Pattern authFeedMediaPattern = Pattern.compile(AUTHENTICATION + FEED_MEDIA_PATH);
private static final Pattern authItemMediaPattern = Pattern.compile(AUTHENTICATION + ITEM_MEDIA_PATH);
private static final Pattern courseFeedPattern = Pattern.compile(COURSE_PATH + FEED_PATH);
private static final Pattern courseFeedMediaPattern = Pattern.compile(COURSE_PATH + FEED_MEDIA_PATH);
private static final Pattern courseItemMediaPattern = Pattern.compile(COURSE_PATH + ITEM_MEDIA_PATH);
private static final Pattern authCourseFeedPattern = Pattern.compile(AUTH_COURSE_PATH + FEED_PATH);
private static final Pattern authCourseFeedMediaPattern = Pattern.compile(AUTH_COURSE_PATH + FEED_MEDIA_PATH);
private static final Pattern authCourseItemMediaPattern = Pattern.compile(AUTH_COURSE_PATH + ITEM_MEDIA_PATH);
private static List<Pattern> patterns = new ArrayList<Pattern>();
static {
patterns.add(feedPattern);
patterns.add(feedMediaPattern);
patterns.add(itemMediaPattern);
patterns.add(authFeedPattern);
patterns.add(authFeedMediaPattern);
patterns.add(authItemMediaPattern);
patterns.add(courseFeedPattern);
patterns.add(courseFeedMediaPattern);
patterns.add(courseItemMediaPattern);
patterns.add(authCourseFeedPattern);
patterns.add(authCourseFeedMediaPattern);
patterns.add(authCourseItemMediaPattern);
}
/**
* Make a new path object and try to match a valid path pattern. The path
* argument should look something like this:
* <ol>
* <li>{feedId}/_/feed.rss
* <li>{feedId}/_/icon.jpg
* <li>{feedId}/_/{itemId}/ruby.mp3
* <li>{identityKey}/{token}/{feedId}/_/feed.rss
* <li>{identityKey}/{token}/{feedId}/_/icon.jpg
* <li>{identityKey}/{token}/{feedId}/_/{itemId}/video.mp4
* <li>coursenode/{courseId}/{nodeId}/{feedId}/_/feed.rss
* <li>coursenode/{courseId}/{nodeId}/{feedId}/_/icon.jpg
* <li>coursenode/{courseId}/{nodeId}/{feedId}/_/{itemId}/{feedId}/week52.mp3
* <li>
* coursenode/{identityKey}/{token}/{courseId}/{nodeId}/{feedId}/_/feed.rss
* <li>
* coursenode/{identityKey}/{token}/{courseId}/{nodeId}/{feedId}/_/icon.jpg
* <li>
* coursenode/{identityKey}/{token}/{courseId}/{nodeId}/{feedId}/_/{itemId}/
* week52.mp3
* </ol>
*
* @param path The requested path
*/
Path(String path) {
this.originalPath = path;
}
/**
* Try to match the path to the patterns and extract the parameters.
*/
public void compile() throws InvalidPathException {
Matcher match = null;
for (Pattern pattern : patterns) {
match = pattern.matcher(originalPath);
if (match.matches()) {
getParameters(match);
break;
}
}
if (!match.matches()) { throw new InvalidPathException(); }
}
/**
* Read all parameters from the matched path.
*
* @param match
*/
private void getParameters(Matcher match) {
Pattern pattern = match.pattern();
if (feedPattern.equals(pattern)) {
// {feedId}/feed.rss
type = FEED;
feedId = Long.parseLong(match.group(1));
// The feed file name is constant, hence not needed
} else if (feedMediaPattern.equals(pattern)) {
// {feedId}/icon.jpg
type = FEED_MEDIA;
feedId = Long.parseLong(match.group(1));
iconFileName = match.group(2);
} else if (itemMediaPattern.equals(pattern)) {
// {feedId}/{itemId}/ruby.mp3
type = ITEM_MEDIA;
feedId = Long.parseLong(match.group(1));
setItemId(match.group(2));
itemFileName = match.group(3);
} else if (authFeedPattern.equals(pattern)) {
// {identityKey}/{token}/{feedId}/feed.rss
type = AUTHENTICATED_FEED;
identityKey = Long.parseLong(match.group(1));
token = match.group(2);
feedId = Long.parseLong(match.group(3));
} else if (authFeedMediaPattern.equals(pattern)) {
// {identityKey}/{token}/{feedId}/icon.jpg
type = AUTHENTICATED_FEED_MEDIA;
identityKey = Long.parseLong(match.group(1));
token = match.group(2);
feedId = Long.parseLong(match.group(3));
iconFileName = match.group(4);
} else if (authItemMediaPattern.equals(pattern)) {
// {identityKey}/{token}/{feedId}/{itemId}/ruby.mp3
type = AUTHENTICATED_ITEM_MEDIA;
identityKey = Long.parseLong(match.group(1));
token = match.group(2);
feedId = Long.parseLong(match.group(3));
setItemId(match.group(4));
itemFileName = match.group(5);
} else if (courseFeedPattern.equals(pattern)) {
// coursenode/{courseId}/{nodeId}/{feedId}/feed.rss
type = COURSE_FEED;
courseId = Long.parseLong(match.group(1));
nodeId = match.group(2);
feedId = Long.parseLong(match.group(3));
} else if (courseFeedMediaPattern.equals(pattern)) {
// coursenode/{courseId}/{nodeId}/{feedId}/icon.jpg
type = COURSE_FEED_MEDIA;
courseId = Long.parseLong(match.group(1));
nodeId = match.group(2);
feedId = Long.parseLong(match.group(3));
iconFileName = match.group(4);
} else if (courseItemMediaPattern.equals(pattern)) {
// coursenode/{courseId}/{nodeId}/{feedId}/{itemId}/ruby.mp3
type = COURSE_ITEM_MEDIA;
courseId = Long.parseLong(match.group(1));
nodeId = match.group(2);
feedId = Long.parseLong(match.group(3));
setItemId(match.group(4));
itemFileName = match.group(5);
} else if (authCourseFeedPattern.equals(pattern)) {
// coursenode/{identityKey}/{token}/{courseId}/{nodeId}/{feedId}/feed.rss
type = AUTHENTICATED_COURSE_FEED;
identityKey = Long.parseLong(match.group(1));
token = match.group(2);
courseId = Long.parseLong(match.group(3));
nodeId = match.group(4);
feedId = Long.parseLong(match.group(5));
} else if (authCourseFeedMediaPattern.equals(pattern)) {
// coursenode/{identityKey}/{token}/{courseId}/{nodeId}/{feedId}/icon.jpg
type = AUTHENTICATED_COURSE_ICON;
identityKey = Long.parseLong(match.group(1));
token = match.group(2);
courseId = Long.parseLong(match.group(3));
nodeId = match.group(4);
feedId = Long.parseLong(match.group(5));
iconFileName = match.group(6);
} else if (authCourseItemMediaPattern.equals(pattern)) {
// coursenode/{identityKey}/{token}/{courseId}/{nodeId}/{feedId}/{itemId}/ruby.mp3
type = AUTHENTICATED_COURSE_ITEM;
identityKey = Long.parseLong(match.group(1));
token = match.group(2);
courseId = Long.parseLong(match.group(3));
nodeId = match.group(4);
feedId = Long.parseLong(match.group(5));
setItemId(match.group(6));
itemFileName = match.group(7);
}
}
/**
* @return true if the path is in cache and it is accessible, meaning access
* has been verified successfully.
*/
public boolean isCachedAndAccessible() {
// return true if path is in the cache and the validation was successful
Boolean accessible = (Boolean) validatedUriCache.get(getKey());
return accessible != null ? accessible : false;
}
/**
* A feed contains many URLs and links etc. The validation of the URL and
* verification of access should only be done once for performance reasons.
* That's where caching comes in. We'll store the URL as the key and give it a
* boolean value whether access has been successfully granted or not.
*/
public void cache(OLATResourceable ores, final boolean accessible) {
final String key = getKey();
CoordinatorManager.getCoordinator().getSyncer().doInSync(ores, new SyncerExecutor() {
public void execute() {
if (validatedUriCache.get(key) == null) {
validatedUriCache.put(key, accessible);
} else {
validatedUriCache.update(key, accessible);
}
}
});
}
/**
* Get the key for caching.
*
* @return the key for caching. I.e. the URL without any file or item
* information.
*/
private String getKey() {
String key = null;
// If the type is ITEM_MEDIA (+ something) remove the item id and the filename
// from the original path.
if (originalPath != null) {
int delimiterIndex = originalPath.indexOf(BASE_PATH_DELIMITER);
key = originalPath.substring(0, delimiterIndex);
}
return key;
}
/**
* @param feedId The feedId to set.
*/
public void setFeedId(Long feedId) {
this.feedId = feedId;
}
/**
* @param courseId The courseId to set.
*/
public void setCourseId(Long courseId) {
this.courseId = courseId;
}
/**
* @return Returns the courseId.
*/
public Long getCourseId() {
return courseId;
}
/**
* @return The feedId.
*/
public Long getFeedId() {
return feedId;
}
/**
* @param itemId The itemId to set.
*/
public void setItemId(String itemId) {
this.itemId = itemId;
}
/**
* @return Returns the itemId.
*/
public String getItemId() {
return itemId;
}
/**
* @param nodeId The nodeId to set.
*/
public void setNodeId(String nodeId) {
this.nodeId = nodeId;
}
/**
* @return Returns the nodeId.
*/
public String getNodeId() {
return nodeId;
}
/**
* @param iconFileName The iconFileName to set.
*/
public void setIconFileName(String iconFileName) {
this.iconFileName = iconFileName;
}
/**
* @return Returns the iconFileName.
*/
public String getIconFileName() {
return iconFileName;
}
/**
* @param itemFileName The itemFileName to set.
*/
public void setItemFileName(String itemFileName) {
this.itemFileName = itemFileName;
}
/**
* @return Returns the itemFileName.
*/
public String getItemFileName() {
return itemFileName;
}
/**
* @param identityKey The identityKey to set.
*/
public void setIdentityKey(Long identityKey) {
this.identityKey = identityKey;
}
/**
* @return Returns the identityKey.
*/
public Long getIdentityKey() {
return identityKey;
}
/**
* @param token The token to set.
*/
public void setToken(String token) {
this.token = token;
}
/**
* @return Returns the token.
*/
public String getToken() {
return token;
}
/**
* @param type The type to set.
*/
public void setType(int type) {
this.type = type;
}
/**
* @return Returns the type.
*/
public int getType() {
return type;
}
/**
* @return true if the path is an RSS feed file.
*/
public boolean isFeedType() {
return type % 10 == FEED;
}
/**
* @return true if the path is the feed icon.
*/
public boolean isIconType() {
return type % 10 == FEED_MEDIA;
}
/**
* @return true if the path is the item media file.
*/
public boolean isItemType() {
return type % 10 == ITEM_MEDIA;
}
/**
* @return true if the path is a course type
*/
public boolean isCourseType() {
return type > COURSE;
}
/**
* @see java.lang.Object#toString()
*/
public String toString() {
return originalPath;
}
/**
* Returns a podcast base URI of the type<br>
* http://myolat.org/olat/[podcast|blog]/[IDKEY/TOKEN]/ORESID
*
* @param feed
* @param identityKey
* @return The feed base uri for the given user (identity)
*/
public static String getFeedBaseUri(Feed feed, Identity identity, Long courseId, String nodeId) {
Manager manager = ManagerFactory.getManager();
boolean isCourseNode = courseId != null && nodeId != null;
final String slash = "/";
StringBuffer uri = new StringBuffer();
uri.append(Settings.getServerContextPathURI());
uri.append(slash);
uri.append(FeedMediaDispatcher.uriPrefixes.get(feed.getResourceableTypeName()));
uri.append(slash);
if (isCourseNode) {
uri.append(COURSE_NODE_INDICATOR);
uri.append(slash);
}
if (identity != null) {
// The identity can be null for guests
String idKey = identity.getKey().toString();
Authentication authentication = manager.findAuthenticationByAuthusername(idKey, TOKEN_PROVIDER);
if (authentication == null) {
// Create an authentication
String token = RandomStringUtils.randomAlphanumeric(6);
authentication = manager.createAndPersistAuthentication(identity, TOKEN_PROVIDER, idKey, token);
}
// If the repository entry allows guest access it is public, thus not
// private.
boolean isPrivate = true;
RepositoryEntry entry = RepositoryManager.getInstance().lookupRepositoryEntry(feed, false);
if (entry != null && entry.getAccess() == RepositoryEntry.ACC_USERS_GUESTS) {
isPrivate = false;
}
if (isPrivate) {
// identity key
uri.append(idKey);
uri.append(slash);
// token
uri.append(authentication.getCredential());
uri.append(slash);
}
}
if (isCourseNode) {
uri.append(courseId);
uri.append(slash);
uri.append(nodeId);
uri.append(slash);
}
// feed id
uri.append(feed.getResourceableId());
// Append base uri delimiter. (Used to identify the root path for caching)
uri.append("/_");
return uri.toString();
}
}