Java tutorial
/* * 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.HttpURLConnection; 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", HttpURLConnection.HTTP_UNAUTHORIZED); 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", HttpURLConnection.HTTP_UNAUTHORIZED); 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 || MultitenantConstants.SUPER_TENANT_DOMAIN_NAME.equals(tenantDomain)) { 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", HttpURLConnection.HTTP_UNAUTHORIZED); 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 = MultitenantConstants.SUPER_TENANT_ID; if (tenantDomain != null && !MultitenantConstants.SUPER_TENANT_DOMAIN_NAME.equals(tenantDomain)) { 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", HttpURLConnection.HTTP_INTERNAL_ERROR); 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(APPConstants.PARAMETER_SPLIT_PATH, 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; } else if (discriminator.equals("ratings") && hasColon) { context.setAttribute("ratingUser", suffix); type = RATINGS_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(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, RegistryConstants.DEFAULT_CHARSET_ENCODING); } 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. CurrentSession.setUserRealm(registry.getUserRealm()); CurrentSession.setUser(registry.getUserName()); try { if (!AuthorizationUtils.authorize(RegistryUtils.getAbsolutePath(registry.getRegistryContext(), uri), ActionConstants.GET)) { final StringResponseContext response = new StringResponseContext("Unauthorized", HttpURLConnection.HTTP_UNAUTHORIZED); 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", HttpURLConnection.HTTP_UNAUTHORIZED); 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 } finally { CurrentSession.removeUser(); CurrentSession.removeUserRealm(); } 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", HttpURLConnection.HTTP_NOT_MODIFIED); 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", HttpURLConnection.HTTP_NOT_MODIFIED); 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 MultitenantConstants.SUPER_TENANT_ID; } 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); } } }