diff --git a/config/alfresco/web-api-application-context.xml b/config/alfresco/web-api-application-context.xml index ea2b66a6af..51162f69c4 100644 --- a/config/alfresco/web-api-application-context.xml +++ b/config/alfresco/web-api-application-context.xml @@ -14,6 +14,12 @@ + + + + + + diff --git a/source/java/org/alfresco/web/api/APIServiceRegistry.java b/source/java/org/alfresco/web/api/APIServiceRegistry.java index 277fcbee41..34bbc4344b 100644 --- a/source/java/org/alfresco/web/api/APIServiceRegistry.java +++ b/source/java/org/alfresco/web/api/APIServiceRegistry.java @@ -51,6 +51,7 @@ public class APIServiceRegistry { // retrieve service authenticator MethodInterceptor authenticator = (MethodInterceptor)appContext.getBean("web.api.Authenticator"); + MethodInterceptor serviceLogger = (MethodInterceptor)appContext.getBean("web.api.ServiceLogger"); // register all API Services // NOTE: An API Service is one registered in Spring which is of type APIServiceImpl @@ -70,19 +71,31 @@ public class APIServiceRegistry } // wrap API Service in appropriate interceptors (e.g. authentication) - if (service.getRequiredAuthentication() != APIRequest.RequiredAuthentication.None) + if (serviceLogger != null && authenticator != null) { - if (authenticator == null) + ProxyFactory authFactory = new ProxyFactory((APIService)service); + + // authentication + if (service.getRequiredAuthentication() != APIRequest.RequiredAuthentication.None) { - throw new APIException("Web API Authenticator not specified"); + if (authenticator == null) + { + throw new APIException("Web API Authenticator not specified"); + } + RegexpMethodPointcutAdvisor advisor = new RegexpMethodPointcutAdvisor(".*execute", authenticator); + authFactory.addAdvisor(advisor); + } + + // logging + if (serviceLogger != null) + { + RegexpMethodPointcutAdvisor advisor = new RegexpMethodPointcutAdvisor(".*execute", serviceLogger); + authFactory.addAdvisor(advisor); } - RegexpMethodPointcutAdvisor advisor = new RegexpMethodPointcutAdvisor(".*execute", authenticator); - ProxyFactory authFactory = new ProxyFactory((APIService)service); - authFactory.addAdvisor(advisor); service = (APIService)authFactory.getProxy(); } - + // register service methods.add(method); uris.add(httpUri); diff --git a/source/java/org/alfresco/web/api/APIServlet.java b/source/java/org/alfresco/web/api/APIServlet.java index a85f6fbee4..7da8b841d5 100644 --- a/source/java/org/alfresco/web/api/APIServlet.java +++ b/source/java/org/alfresco/web/api/APIServlet.java @@ -68,43 +68,45 @@ public class APIServlet extends BaseServlet */ protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { + long start = System.currentTimeMillis(); APIRequest request = new APIRequest(req); APIResponse response = new APIResponse(res); - - // - // Execute appropriate service - // - // TODO: Handle errors (with appropriate HTTP error responses) - APIRequest.HttpMethod method = request.getHttpMethod(); - String uri = request.getPathInfo(); - - if (logger.isDebugEnabled()) - logger.debug("Processing request (" + request.getHttpMethod() + ") " + request.getRequestURL() + (request.getQueryString() != null ? "?" + request.getQueryString() : "")); - - APIService service = apiServiceRegistry.get(method, uri); - if (service != null) + try { + // + // Execute appropriate service + // + // TODO: Handle errors (with appropriate HTTP error responses) + + APIRequest.HttpMethod method = request.getHttpMethod(); + String uri = request.getPathInfo(); + if (logger.isDebugEnabled()) - logger.debug("Mapped to service " + service.getName()); + logger.debug("Processing request (" + request.getHttpMethod() + ") " + request.getRequestURL() + (request.getQueryString() != null ? "?" + request.getQueryString() : "")); - long start = System.currentTimeMillis(); - service.execute(request, response); - long end = System.currentTimeMillis(); - - if (logger.isDebugEnabled()) - logger.debug("Service " + service.getName() + " executed in " + (end - start) + "ms"); + APIService service = apiServiceRegistry.get(method, uri); + if (service != null) + { + // TODO: Wrap in single transaction + service.execute(request, response); + } + else + { + if (logger.isDebugEnabled()) + logger.debug("Request does not map to service."); + + response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + // TODO: add appropriate error detail + } } - else - { - if (logger.isDebugEnabled()) - logger.debug("Request does not map to service."); - - response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); - // TODO: add appropriate error detail - } - // TODO: exception handling + finally + { + long end = System.currentTimeMillis(); + if (logger.isDebugEnabled()) + logger.debug("Processed request (" + request.getHttpMethod() + ") " + request.getRequestURL() + (request.getQueryString() != null ? "?" + request.getQueryString() : "") + " in " + (end - start) + "ms"); + } } } diff --git a/source/java/org/alfresco/web/api/BasicAuthenticator.java b/source/java/org/alfresco/web/api/BasicAuthenticator.java index f009d9747b..478a34603b 100644 --- a/source/java/org/alfresco/web/api/BasicAuthenticator.java +++ b/source/java/org/alfresco/web/api/BasicAuthenticator.java @@ -54,6 +54,7 @@ public class BasicAuthenticator implements MethodInterceptor throws Throwable { boolean authorized = false; + String currentUser = null; Object retVal = null; Object[] args = invocation.getArguments(); APIRequest request = (APIRequest)args[0]; @@ -61,6 +62,14 @@ public class BasicAuthenticator implements MethodInterceptor try { + // + // Determine if user already authenticated + // + + currentUser = AuthenticationUtil.getCurrentUserName(); + if (logger.isDebugEnabled()) + logger.debug("Current authentication: " + (currentUser == null ? "unauthenticated" : "authenticated as " + currentUser)); + // // validate credentials // @@ -75,16 +84,18 @@ public class BasicAuthenticator implements MethodInterceptor logger.debug("Authorization provided (overrides Guest login): " + (authorization != null && authorization.length() > 0)); } + // authenticate as guest, if service allows if (((authorization == null || authorization.length() == 0) || isGuest) && service.getRequiredAuthentication().equals(APIRequest.RequiredAuthentication.Guest)) { if (logger.isDebugEnabled()) logger.debug("Authenticating as Guest"); - // authenticate as guest, if service allows authenticationService.authenticateAsGuest(); authorized = true; } + + // authenticate as specified by HTTP Basic Authentication else if (authorization != null && authorization.length() > 0) { try @@ -137,15 +148,15 @@ public class BasicAuthenticator implements MethodInterceptor // execute API service or request authorization // - if (logger.isDebugEnabled()) - logger.debug("Authenticated: " + authorized); - if (authorized) { retVal = invocation.proceed(); } else { + if (logger.isDebugEnabled()) + logger.debug("Requesting authorization credentials"); + APIResponse response = (APIResponse)args[1]; response.setStatus(401); response.setHeader("WWW-Authenticate", "Basic realm=\"Alfresco\""); @@ -153,12 +164,17 @@ public class BasicAuthenticator implements MethodInterceptor } finally { - // clear authentication - // TODO: Consider case where authentication is set before this method is called. - // That shouldn't be the case for the web api. + // reset authentication if (authorized) { authenticationService.clearCurrentSecurityContext(); + if (currentUser != null) + { + AuthenticationUtil.setCurrentUser(currentUser); + } + + if (logger.isDebugEnabled()) + logger.debug("Authentication reset: " + (currentUser == null ? "unauthenticated" : "authenticated as " + currentUser)); } } diff --git a/source/java/org/alfresco/web/api/ServiceLogger.java b/source/java/org/alfresco/web/api/ServiceLogger.java new file mode 100644 index 0000000000..3f574d686e --- /dev/null +++ b/source/java/org/alfresco/web/api/ServiceLogger.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * 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.alfresco.web.api; + +import org.alfresco.i18n.I18NUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + + +/** + * API Service Logger + * + * @author davidc + */ +public class ServiceLogger implements MethodInterceptor +{ + // Logger + private static final Log logger = LogFactory.getLog(ServiceLogger.class); + + /* (non-Javadoc) + * @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation) + */ + public Object invoke(MethodInvocation invocation) + throws Throwable + { + Object retVal = null; + + if (logger.isDebugEnabled()) + { + APIService service = (APIService)invocation.getThis(); + String user = AuthenticationUtil.getCurrentUserName(); + String locale = I18NUtil.getLocale().toString(); + logger.debug("Invoking service " + service.getName() + (user == null ? " (unauthenticated)" : " (authenticated as " + user + ")" + " (" + locale + ")")); + long start = System.currentTimeMillis(); + retVal = invocation.proceed(); + long end = System.currentTimeMillis(); + logger.debug("Service " + service.getName() + " executed in " + (end - start) + "ms"); + } + else + { + retVal = invocation.proceed(); + } + + return retVal; + } + +} diff --git a/source/java/org/alfresco/web/api/services/TextSearch.java b/source/java/org/alfresco/web/api/services/TextSearch.java index ed4b50e072..2f10e4772e 100644 --- a/source/java/org/alfresco/web/api/services/TextSearch.java +++ b/source/java/org/alfresco/web/api/services/TextSearch.java @@ -21,8 +21,10 @@ import java.io.PrintWriter; import java.io.StringWriter; import java.util.Date; import java.util.HashMap; +import java.util.Locale; import java.util.Map; +import org.alfresco.i18n.I18NUtil; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.StoreRef; @@ -30,6 +32,7 @@ import org.alfresco.service.cmr.repository.TemplateImageResolver; import org.alfresco.service.cmr.repository.TemplateNode; import org.alfresco.service.cmr.repository.TemplateService; import org.alfresco.service.cmr.search.ResultSet; +import org.alfresco.service.cmr.search.SearchParameters; import org.alfresco.service.cmr.search.SearchService; import org.alfresco.service.descriptor.DescriptorService; import org.alfresco.util.ApplicationContextHelper; @@ -37,7 +40,6 @@ import org.alfresco.util.GUID; import org.alfresco.web.api.APIException; import org.alfresco.web.api.APIRequest; import org.alfresco.web.api.APIResponse; -import org.alfresco.web.api.APIServlet; import org.alfresco.web.api.APIRequest.HttpMethod; import org.alfresco.web.api.APIRequest.RequiredAuthentication; import org.alfresco.web.ui.common.Utils; @@ -54,7 +56,7 @@ import org.springframework.context.ApplicationContext; public class TextSearch extends APIServiceImpl { // Logger - private static final Log logger = LogFactory.getLog(APIServlet.class); + private static final Log logger = LogFactory.getLog(TextSearch.class); // search parameters // TODO: allow configuration of search store @@ -128,12 +130,19 @@ public class TextSearch extends APIServiceImpl { // NOTE: use default itemsPerPage } + Locale locale = I18NUtil.getLocale(); + String language = req.getParameter("l"); + if (language != null && language.length() > 0) + { + // NOTE: Simple conversion from XML Language Id to Java Locale Id + locale = new Locale(language.replace("-", "_")); + } // // execute the search // - SearchResult results = search(searchTerms, startPage, itemsPerPage); + SearchResult results = search(searchTerms, startPage, itemsPerPage, locale); // // render the results @@ -166,7 +175,7 @@ public class TextSearch extends APIServiceImpl * @param startPage * @return */ - private SearchResult search(String searchTerms, int startPage, int itemsPerPage) + private SearchResult search(String searchTerms, int startPage, int itemsPerPage, Locale locale) { SearchResult searchResult = null; ResultSet results = null; @@ -181,13 +190,24 @@ public class TextSearch extends APIServiceImpl // execute query if (logger.isDebugEnabled()) + { + logger.debug("Search parameters: searchTerms=" + searchTerms + ", startPage=" + startPage + ", itemsPerPage=" + itemsPerPage + ", search locale=" + locale.toString()); logger.debug("Issuing lucene search: " + query); - - results = searchService.query(SEARCH_STORE, SearchService.LANGUAGE_LUCENE, query); + } + + SearchParameters parameters = new SearchParameters(); + parameters.addStore(SEARCH_STORE); + parameters.setLanguage(SearchService.LANGUAGE_LUCENE); + parameters.setQuery(query); + if (locale != null) + { + parameters.addLocale(locale); + } + results = searchService.query(parameters); int totalResults = results.length(); if (logger.isDebugEnabled()) - logger.debug("Results: " + totalResults + " rows"); + logger.debug("Results: " + totalResults + " rows (limited: " + results.getResultSetMetaData().getLimitedBy() + ")"); // are we out-of-range int totalPages = (totalResults / itemsPerPage); @@ -200,6 +220,7 @@ public class TextSearch extends APIServiceImpl // construct search result searchResult = new SearchResult(); searchResult.setSearchTerms(searchTerms); + searchResult.setLocale(locale); searchResult.setItemsPerPage(itemsPerPage); searchResult.setStartPage(startPage); searchResult.setTotalPages(totalPages); @@ -234,6 +255,7 @@ public class TextSearch extends APIServiceImpl { private String id; private String searchTerms; + private Locale locale; private int itemsPerPage; private int totalPages; private int totalResults; @@ -323,6 +345,24 @@ public class TextSearch extends APIServiceImpl this.searchTerms = searchTerms; } + public Locale getLocale() + { + return locale; + } + + /** + * @return XML 1.0 Language Identification + */ + public String getLocaleId() + { + return locale.toString().replace('_', '-'); + } + + /*package*/ void setLocale(Locale locale) + { + this.locale = locale; + } + public String getId() { if (id == null) @@ -382,17 +422,17 @@ public class TextSearch extends APIServiceImpl " ${search.totalResults}\n" + " ${search.startIndex}\n" + " ${search.itemsPerPage}\n" + - " \n" + - " \n" + - " \n" + - " \n" + + " \n" + + " \n" + + " \n" + + " \n" + "<#if search.startPage > 1>" + - " \n" + + " \n" + "" + "<#if search.startPage < search.totalPages>" + - " \n" + + " \n" + "" + - " \n" + + " \n" + " \n" + "<#list search.results as row>" + " \n" + @@ -437,15 +477,15 @@ public class TextSearch extends APIServiceImpl " \n" + "" + " \n" + - " first" + + " first" + "<#if search.startPage > 1>" + - " previous" + + " previous" + "" + - " ${search.startPage}" + + " ${search.startPage}" + "<#if search.startPage < search.totalPages>" + - " next" + + " next" + "" + - " last" + + " last" + " \n" + "\n"; @@ -461,7 +501,7 @@ public class TextSearch extends APIServiceImpl " OR " + "" + "" + - ")" + + ") " + "(" + "<#list 1..terms?size as i>" + "TEXT:${terms[i - 1]}" + @@ -503,7 +543,7 @@ public class TextSearch extends APIServiceImpl */ private void test() { - SearchResult result = search("alfresco tutorial", 1, 5); + SearchResult result = search("alfresco tutorial", 1, 5, I18NUtil.getLocale()); Map searchModel = new HashMap(7, 1.0f); Map request = new HashMap();