/*
* Copyright (c) 2007, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.wso2.carbon.registry.app;
import org.apache.abdera.protocol.Request;
import org.apache.abdera.protocol.Resolver;
import org.apache.abdera.protocol.server.RequestContext;
import org.apache.abdera.protocol.server.ResponseContext;
import org.apache.abdera.protocol.server.Target;
import org.apache.abdera.protocol.server.TargetType;
import org.apache.abdera.protocol.server.context.EmptyResponseContext;
import org.apache.abdera.protocol.server.impl.SimpleTarget;
import org.apache.abdera.protocol.server.servlet.ServletRequestContext;
import org.apache.axiom.om.util.Base64;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.CarbonConstants;
import org.wso2.carbon.registry.app.targets.ResourceTarget;
import org.wso2.carbon.registry.app.targets.ResponseTarget;
import org.wso2.carbon.registry.core.ActionConstants;
import org.wso2.carbon.registry.core.Collection;
import org.wso2.carbon.registry.core.RegistryConstants;
import org.wso2.carbon.registry.core.Resource;
import org.wso2.carbon.registry.core.ResourceImpl;
import org.wso2.carbon.registry.core.config.RegistryContext;
import org.wso2.carbon.registry.core.exceptions.RegistryException;
import org.wso2.carbon.registry.core.exceptions.ResourceNotFoundException;
import org.wso2.carbon.registry.core.jdbc.EmbeddedRegistryService;
import org.wso2.carbon.registry.core.secure.AuthorizationFailedException;
import org.wso2.carbon.registry.core.session.CurrentSession;
import org.wso2.carbon.registry.core.session.UserRegistry;
import org.wso2.carbon.registry.core.utils.AuthorizationUtils;
import org.wso2.carbon.registry.core.utils.RegistryUtils;
import org.wso2.carbon.user.core.UserStoreException;
import org.wso2.carbon.user.core.service.RealmService;
import org.wso2.carbon.user.core.tenant.TenantManager;
import org.wso2.carbon.utils.multitenancy.MultitenantConstants;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* Class that is capable of resolving requests made via the Atom API, and identify how to process
* them. Some of the target types are provided by Abdera itself, whereas some others are defined.
*/
public class RegistryResolver implements Resolver<Target> {
private static Log log = LogFactory.getLog(RegistryResolver.class);
/**
* The target type for tags.
*/
public static final TargetType TAGS_TYPE = TargetType.get("tags", true);
/**
* The target type for logs.
*/
public static final TargetType LOGS_TYPE = TargetType.get("logs", true);
/**
* The target type for ratings.
*/
public static final TargetType RATINGS_TYPE = TargetType.get("ratings", true);
/**
* The target type for rename operations.
*/
public static final TargetType RENAME_TYPE = TargetType.get("rename", true);
/**
* The target type for copy operations.
*/
public static final TargetType COPY_TYPE = TargetType.get("copy", true);
/**
* The target type for move operations.
*/
public static final TargetType MOVE_TYPE = TargetType.get("move", true);
/**
* The target type for comments.
*/
public static final TargetType COMMENTS_TYPE = TargetType.get("comments", true);
/**
* The target type for tag urls.
*/
public static final TargetType TAG_URL_TYPE = TargetType.get("tagURL", true);
/**
* The target type for associations.
*/
public static final TargetType ASSOCIATIONS_TYPE = TargetType.get("associations", true);
/**
* The target type for restore operations.
*/
public static final TargetType RESTORE_TYPE = TargetType.get("restore", true);
/**
* The target type for aspects.
*/
public static final TargetType ASPECT_TYPE = TargetType.get("aspect", true);
/**
* The target type for versions.
*/
public static final TargetType VERSIONS_TYPE = TargetType.get("versions", true);
/**
* The target type for check points.
*/
public static final TargetType CHECKPOINT_TYPE = TargetType.get("checkpoint", true);
/**
* The target type for queries..
*/
public static final TargetType QUERY_TYPE = TargetType.get("query", true);
/**
* The target type for import requests.
*/
public static final TargetType IMPORT_TYPE = TargetType.get("import", true);
/**
* The target type for delete requests.
*/
public static final TargetType DELETE_TYPE = TargetType.get("delete", true);
/**
* The target type for custom collections.
*/
public static final TargetType COLLECTION_CUSTOM_TYPE = TargetType.get("col-custom", true);
/**
* The target type for dump.
*/
public static final TargetType DUMP_TYPE = TargetType.get("dump", true);
private EmbeddedRegistryService embeddedRegistryService;
private String basePath;
public RegistryResolver(EmbeddedRegistryService embeddedRegistryService, String basePath) {
this.embeddedRegistryService = embeddedRegistryService;
this.basePath = basePath;
}
private static Map<String, TargetType> types;
static {
types = new HashMap<String, TargetType>();
types.put("tags", TAGS_TYPE);
types.put("logs", LOGS_TYPE);
types.put("ratings", RATINGS_TYPE);
types.put("comments", COMMENTS_TYPE);
types.put("rename", RENAME_TYPE);
types.put("copy", COPY_TYPE);
types.put("move", MOVE_TYPE);
types.put("tagURL", TAG_URL_TYPE);
types.put("associations", ASSOCIATIONS_TYPE);
types.put("restore", RESTORE_TYPE);
types.put("versions", VERSIONS_TYPE);
types.put("checkpoint", CHECKPOINT_TYPE);
types.put("query", QUERY_TYPE);
types.put("application/resource-import", IMPORT_TYPE);
types.put("dump", DUMP_TYPE);
}
/**
* Method to identify the response target for the request.
*
* @param request the request.
*
* @return the response target.
*/
public Target resolve(Request request) {
RequestContext context = (RequestContext) request;
final ServletRequestContext requestContext;
if (context instanceof ServletRequestContext) {
requestContext = (ServletRequestContext) request;
} else {
requestContext = null;
}
if (embeddedRegistryService == null) {
if (requestContext != null) {
embeddedRegistryService =
(EmbeddedRegistryService) requestContext.getRequest().getSession()
.getServletContext().getAttribute("registry");
}
if (embeddedRegistryService == null) {
String msg = "Error in retrieving the embedded registry service.";
log.error(msg);
}
}
//TODO (reg-sep)
UserRegistry registry = null;
String uri = context.getUri().toString();
String loggedIn = null;
if (requestContext != null) {
loggedIn = ((ServletRequestContext) request).getRequest().getParameter("loggedIn");
}
if (loggedIn != null) {
String loggedUser =
(String) requestContext.getRequest().getSession().getServletContext()
.getAttribute("logged-user");
try {
registry = embeddedRegistryService.getRegistry(loggedUser);
uri = uri.substring(0, uri.lastIndexOf("?"));
} catch (RegistryException e) {
final StringResponseContext response =
new StringResponseContext("Unauthorized", 401);
response.setHeader("WWW-Authenticate", "Basic realm=\"WSO2-Registry\"");
return new ResponseTarget(context, response);
}
}
if (registry == null) {
// Set up secure registry instance
String authorizationString = request.getAuthorization();
if (authorizationString != null) {
// splitting the Authorization string "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="
String values[] = authorizationString.split("\\ ");
if (values == null || values.length == 0) {
final StringResponseContext response =
new StringResponseContext("Unauthorized", 401);
response.setHeader("WWW-Authenticate", "Basic realm=\"WSO2-Registry\"");
return new ResponseTarget(context, response);
} else if ("Basic".equals(values[0])) {
try {
// Decode username/password
authorizationString = new String(Base64.decode(values[1]));
values = authorizationString.split("\\:");
String userName = values[0];
String password = values[1];
String tenantDomain =
(String) ((ServletRequestContext) request).getRequest().
getAttribute(MultitenantConstants.TENANT_DOMAIN);
int tenantId;
String userNameAlong;
if (tenantDomain == null) {
tenantId = getTenantId(userName);
userNameAlong = getUserName(userName);
} else {
tenantId = getTenantIdFromDomain(tenantDomain);
userNameAlong = userName;
}
registry = embeddedRegistryService.getRegistry(userNameAlong,
password, tenantId);
} catch (Exception e) {
final StringResponseContext response =
new StringResponseContext("Unauthorized", 401);
response.setHeader("WWW-Authenticate", "Basic realm=\"WSO2-Registry\"");
return new ResponseTarget(context, response);
}
} else {
// TODO - return an ExceptionTarget which contains the authentication problem
// return new ExceptionTarget(400, "Only basic authentication is supported!");
return null;
}
} else {
String tenantDomain = (String) requestContext.getRequest().
getAttribute(MultitenantConstants.TENANT_DOMAIN);
int calledTenantId = 0;
if (tenantDomain != null) {
if (RegistryContext.getBaseInstance().getRealmService() == null) {
String msg = "Error in getting the tenant manager. " +
"The realm service is not available.";
log.error(msg);
return new ResponseTarget(context, new EmptyResponseContext(400, msg));
}
TenantManager tenantManager =
RegistryContext.getBaseInstance().getRealmService().
getTenantManager();
try {
calledTenantId = tenantManager.getTenantId(tenantDomain);
} catch (org.wso2.carbon.user.api.UserStoreException e) {
String msg =
"Error in converting tenant domain to the id for tenant domain: " +
tenantDomain + ".";
log.error(msg, e);
return new ResponseTarget(context, new EmptyResponseContext(400, msg));
}
try {
if (!tenantManager.isTenantActive(calledTenantId)) {
// the tenant is not active.
String msg =
"The tenant is not active. tenant domain: " + tenantDomain +
".";
log.error(msg);
return new ResponseTarget(context, new EmptyResponseContext(400, msg));
}
} catch (org.wso2.carbon.user.api.UserStoreException e) {
String msg =
"Error in converting tenant domain to the id for tenant domain: " +
tenantDomain + ".";
log.error(msg, e);
return new ResponseTarget(context, new EmptyResponseContext(400, msg));
}
RegistryContext.getBaseInstance().
getRealmService().getBootstrapRealmConfiguration();
}
String anonUser = CarbonConstants.REGISTRY_ANONNYMOUS_USERNAME;
try {
registry = embeddedRegistryService.getRegistry(anonUser, calledTenantId);
} catch (RegistryException e) {
String msg = "Error in creating the registry.";
log.error(msg, e);
return new ResponseTarget(context, new EmptyResponseContext(400, msg));
}
}
}
// Squirrel this away so the adapter can get it later (after all that work we just did!)
context.setAttribute("userRegistry", registry);
final String method = context.getMethod();
/*
Following code moved further down
try {
uri = URLDecoder.decode(uri, "utf-8");
} catch (UnsupportedEncodingException e) {
log.error(e);
return null;
} */
if (uri.startsWith(RegistryConstants.PATH_SEPARATOR
+ RegistryConstants.REGISTRY_INSTANCE
+ RegistryConstants.PATH_SEPARATOR
+ RegistryConstants.REGISTRY_INSTANCE)) {
//if ROOT war is renamed to 'registry', uri will look like following,
//'/registry/registry/foo/bar'
//Hence,we need to remove the first 'registry'
uri = uri.replaceFirst(RegistryConstants.PATH_SEPARATOR
+ RegistryConstants.REGISTRY_INSTANCE, "");
}
// URI will start with the baseURI, which we need to strip off.
String[] excludeStartArr = {basePath + APPConstants.ATOM,
basePath + APPConstants.RESOURCE, basePath + "/tags"};
if (basePath == null) {
log.error("Base path is null. Aborting the operation.");
final StringResponseContext response =
new StringResponseContext("Internal Server Error", 500);
return new ResponseTarget(context, response);
} else if (!basePath.equals("")) {
for (String excludeStartStr : excludeStartArr) {
// URI will start with the baseURI, which we need to strip off.
if (uri.indexOf(excludeStartStr) > -1 &&
uri.length() > uri.indexOf(excludeStartStr) + basePath.length()) {
uri = uri.substring(uri.indexOf(excludeStartStr) + basePath.length());
break;
}
}
}
if (!uri.startsWith(
RegistryConstants.PATH_SEPARATOR + RegistryConstants.REGISTRY_INSTANCE)) {
uri = uri.substring(uri.indexOf(RegistryConstants.PATH_SEPARATOR));
}
context.setAttribute("pathInfo", uri);
String[] parts = splitPath(uri); // splits with "\;"
boolean hasColon = false;
TargetType type = null;
// Let's just see if this is an import first - in which case we can just send it
// on through.
if (parts.length > 1) {
String discriminator = parts[1];
// If this is a version request, don't do anything special. Otherwise process.
if (discriminator.startsWith("version:")) {
if (parts.length > 2) {
// Make sure this is a restore.
if (parts[2].equals("restore")) {
type = RESTORE_TYPE;
uri = parts[0] + RegistryConstants.URL_SEPARATOR + parts[1];
} else if (parts[2].equals(APPConstants.ASSOCIATIONS)) {
type = ASSOCIATIONS_TYPE;
uri = parts[0] + RegistryConstants.URL_SEPARATOR + parts[1];
} else {
// There's an extra semicolon here somewhere.
return null;
}
}
} else {
// Store the split URL for later
context.setAttribute("splitPath", parts);
int idx = discriminator.indexOf("?");
if (idx > -1) {
discriminator = discriminator.substring(0, idx);
}
String suffix = null;
idx = discriminator.indexOf(":");
if (idx > -1) {
suffix = discriminator.substring(idx + 1, discriminator.length());
discriminator = discriminator.substring(0, idx);
hasColon = true;
}
if (discriminator.startsWith("aspect")) {
type = ASPECT_TYPE;
} else {
type = types.get(discriminator);
}
if (discriminator.equals("tag") && method.equals("DELETE") && hasColon) {
context.setAttribute("tagName", suffix);
type = DELETE_TYPE;
} else if (discriminator.equals("comment") && method.equals("DELETE") && hasColon) {
context.setAttribute("commentId", suffix);
type = DELETE_TYPE;
}
// If we have a discriminator that we don't understand, return a 404
if (type == null) {
return null;
}
// For the rest of this code, we'll want the "raw" resource URI
if (!hasColon || !(type.equals(COMMENTS_TYPE) || type.equals(RATINGS_TYPE) ||
type.equals(TAGS_TYPE))) {
uri = parts[0];
}
if (hasColon && (type.equals(RATINGS_TYPE) || type.equals(TAGS_TYPE))) {
type = null;
}
}
}
int idx = uri.indexOf("?");
if (idx > -1) {
String queryString = uri.substring(idx + 1, uri.length());
context.setAttribute("queryString", queryString);
uri = uri.substring(0, idx);
}
try {
uri = URLDecoder.decode(uri, "utf-8");
} catch (UnsupportedEncodingException e) {
log.error(e);
return null;
}
boolean isMedia = false;
if (uri.startsWith(APPConstants.RESOURCE)) {
uri = uri.substring(APPConstants.RESOURCE.length());
isMedia = true;
} else if (uri.startsWith(APPConstants.ATOM)) {
uri = uri.substring(APPConstants.ATOM.length());
} else if (uri.startsWith("/tags") && (uri.length() == 5 || uri.charAt(5) == '/')) {
return new SimpleTarget(TAG_URL_TYPE, context);
} else {
return null;
}
if (uri.length() == 0) {
uri = "/";
}
// See if we're asking for a paginated collection
String startParam = context.getParameter("start");
String pageLenParam = context.getParameter("pageLen");
int start = (startParam == null) ? -1 : Integer.parseInt(startParam);
int pageLen = (pageLenParam == null) ? -1 : Integer.parseInt(pageLenParam);
Resource resource = null;
if (type != null && type.equals(DUMP_TYPE) &&
method != null && method.equals("POST")) {
// for restoring a dump we don't need to have available resource
// here we will create a fake resource to store the path
resource = new ResourceImpl();
((ResourceImpl) resource).setPath(uri);
} else {
// in a restore, path don't need to exist.
try {
CurrentSession.setUserRealm(registry.getUserRealm());
CurrentSession.setUser(registry.getUserName());
if (!AuthorizationUtils.authorize(RegistryUtils.getAbsolutePath(
registry.getRegistryContext(), uri),
ActionConstants.GET)) {
final StringResponseContext response =
new StringResponseContext("Unauthorized", 401);
response.setHeader("WWW-Authenticate", "Basic realm=\"WSO2-Registry\"");
return new ResponseTarget(context, response);
} else if (start > -1 || pageLen > -1) {
resource = registry.get(uri, start, pageLen);
} else {
resource = registry.get(uri);
}
} catch (AuthorizationFailedException e) {
final StringResponseContext response =
new StringResponseContext("Unauthorized", 401);
response.setHeader("WWW-Authenticate", "Basic realm=\"WSO2-Registry\"");
return new ResponseTarget(context, response);
} catch (ResourceNotFoundException e) {
// If this is a straight-ahead POST to a non-existent directory, create it?
if (method.equals("POST") && parts.length == 1) {
// Need to create it.
try {
Collection c = registry.newCollection();
registry.put(uri, c);
resource = registry.get(uri);
} catch (RegistryException e1) {
log.error(e1);
return null;
}
}
if (resource == null) {
return null;
}
} catch (RegistryException e) {
return null; // return 404
}
if (method.equals("GET")) {
// eTag based conditional get
String ifNonMatchValue = context.getHeader("if-none-match");
if (ifNonMatchValue != null) {
String currentETag = Utils.calculateEntityTag(resource);
if (ifNonMatchValue.equals(currentETag)) {
/* the version is not modified */
ResponseContext response = new StringResponseContext("Not Modified", 304);
return new ResponseTarget(context, response);
}
}
// date based conditional get
long ifModifiedSinceValue = 0;
Date ifModifiedSince = context.getDateHeader("If-Modified-Since");
if (ifModifiedSince != null) {
ifModifiedSinceValue = ifModifiedSince.getTime();
}
if (ifModifiedSinceValue > 0) {
long lastModifiedValue = resource.getLastModified().getTime();
// convert the time values from milliseconds to seconds
ifModifiedSinceValue /= 1000;
lastModifiedValue /= 1000;
/* condition to check we have latest updates in terms of dates */
if (ifModifiedSinceValue >= lastModifiedValue) {
/* no need to response with data */
ResponseContext response = new StringResponseContext("Not Modified", 304);
return new ResponseTarget(context, response);
}
}
}
}
context.setAttribute("MyResolver", this);
if (type == null) {
if (method.equals("DELETE")) {
// Unfortunately, deletions aren't quite handled the way we want them
// in AbstractEntityCollectionProvider, so for now we're using an
// extensionRequest to get it done.
type = DELETE_TYPE;
} else {
if (resource instanceof Collection) {
if (method.equals("HEAD") || method.equals("PUT")) {
// Abdera doesn't handle HEAD or PUT on collections yet. this should
// go away once that's fixed!
type = COLLECTION_CUSTOM_TYPE;
} else {
type = TargetType.TYPE_COLLECTION;
}
} else {
type = isMedia ? TargetType.TYPE_MEDIA : TargetType.TYPE_ENTRY;
}
}
}
return new ResourceTarget(type, context, resource);
}
/**
* This method will split the path into parts around the path separator, returning an array of
* the parts.
*
* @param path URL path
*
* @return String array of the split string
*/
private String[] splitPath(String path) {
return path.split("\\" + RegistryConstants.URL_SEPARATOR);
}
@SuppressWarnings("unused")
public String getBasePath() {
return basePath;
}
public static String getUserName(String userNameWithDomain) throws RegistryException {
int atIndex = userNameWithDomain.indexOf("@");
if (atIndex == -1) {
// no domain in the inserted test
return userNameWithDomain;
}
return userNameWithDomain.substring(atIndex + 1);
}
public static int getTenantId(String userNameWithDomain) throws RegistryException {
int atIndex = userNameWithDomain.indexOf("@");
if (atIndex == -1) {
// no domain
return 0;
}
String domain = userNameWithDomain.substring(atIndex + 1, userNameWithDomain.length());
return getTenantIdFromDomain(domain);
}
private static int getTenantIdFromDomain(String domain) throws RegistryException {
RegistryContext registryContext = RegistryContext.getBaseInstance();
RealmService realmService = registryContext.getRealmService();
if (realmService == null) {
String msg = "Error in getting the tenant manager. The realm service is not available.";
log.error(msg);
throw new RegistryException(msg);
}
TenantManager tenantManager = realmService.getTenantManager();
try {
return tenantManager.getTenantId(domain);
} catch (org.wso2.carbon.user.api.UserStoreException e) {
String msg = "Error in getting the tenant manager.";
log.error(msg, e);
throw new RegistryException(msg, e);
}
}
}