diff --git a/common/.classpath b/common/.classpath index 66fbbdbc5cc4b4115ddec3bb62daf7ce4cae25c1..c67ed51f260246203298714f7cb108d6259c0ff5 100644 --- a/common/.classpath +++ b/common/.classpath @@ -35,5 +35,10 @@ <classpathentry kind="lib" path="/libraries/spring/spring-context.jar"/> <classpathentry kind="lib" path="/libraries/spring/spring-aop.jar" sourcepath="/libraries/spring/src.jar"/> <classpathentry kind="lib" path="/libraries/spring/third-party/aopalliance.jar"/> + <classpathentry kind="lib" path="/libraries/jetty7/lib/server/servlet-api-2.5.jar"/> + <classpathentry kind="lib" path="/libraries/spring/spring.jar" sourcepath="/libraries/spring/src.jar"/> + <classpathentry kind="lib" path="/libraries/spring/webmvc/spring-webmvc.jar" sourcepath="/libraries/spring/webmvc/src.jar"/> + <classpathentry kind="lib" path="/libraries/gwt2.4/gwt-servlet.jar"/> + <classpathentry kind="lib" path="/libraries/spring/third-party/stream-supporting-httpinvoker.jar" sourcepath="/libraries/spring/third-party/stream-supporting-httpinvoker-src.zip"/> <classpathentry kind="output" path="targets/classes"/> </classpath> diff --git a/common/source/java/ch/systemsx/cisd/common/servlet/AbstractActionLog.java b/common/source/java/ch/systemsx/cisd/common/servlet/AbstractActionLog.java new file mode 100644 index 0000000000000000000000000000000000000000..d69e358bb8726cee0594f5c3e5c1a2ae9f57327d --- /dev/null +++ b/common/source/java/ch/systemsx/cisd/common/servlet/AbstractActionLog.java @@ -0,0 +1,161 @@ +/* + * Copyright 2008 ETH Zuerich, CISD + * + * 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 ch.systemsx.cisd.common.servlet; + +import javax.servlet.http.HttpSession; + +import org.apache.log4j.Logger; + +import ch.systemsx.cisd.common.logging.LogCategory; +import ch.systemsx.cisd.common.logging.LogFactory; +import ch.systemsx.cisd.common.server.IRemoteHostProvider; + +/** + * Abstract super class of action logs. Action logs are logged by one of the following loggers: + * Authentication logger, access logger, and tracking logger. + * + * @author Franz-Josef Elmer + */ +public abstract class AbstractActionLog implements IActionLog +{ + private static final String FAILED = "FAILED"; + + private static final String OK = "OK"; + + private static final String USER_SESSION_TEMPLATE = "{USER: %s, WEBSESSION: %s} logout%s"; + + private static final String USER_HOST_SESSION_TEMPLATE = + "{USER: %s, HOST: %s, WEBSESSION: %s} "; + + /** Authentication logger. Name is specified by {@link LogCategory#AUTH}. */ + protected final Logger authenticationLog = LogFactory.getLogger(LogCategory.AUTH); + + /** Access logger. Name is specified by {@link LogCategory#ACCESS}. */ + protected final Logger accessLog = LogFactory.getLogger(LogCategory.ACCESS); + + /** Tracking logger. Name is specified by {@link LogCategory#TRACKING}. */ + protected final Logger trackingLog = LogFactory.getLogger(LogCategory.TRACKING); + + protected final IRequestContextProvider requestContextProvider; + + protected final IRemoteHostProvider remoteHostProvider; + + protected static String getSuccessString(final boolean success) + { + return success ? OK : FAILED; + } + + /** + * Creates an instance for the specified request context provider. It is used to provide + * {@link HttpSession} in all methods except {@link #logLogout(HttpSession)}. + * + * @param requestContextProvider + */ + public AbstractActionLog(final IRequestContextProvider requestContextProvider) + { + this.requestContextProvider = requestContextProvider; + this.remoteHostProvider = new RequestContextProviderAdapter(requestContextProvider); + } + + @Override + public void logFailedLoginAttempt(final String userCode) + { + if (authenticationLog.isInfoEnabled()) + { + final String logMessage = + String.format("{USER: %s, HOST: %s} login: FAILED", userCode, + remoteHostProvider.getRemoteHost()); + authenticationLog.info(logMessage); + } + } + + @Override + public void logSuccessfulLogin() + { + if (authenticationLog.isInfoEnabled()) + { + final String userHostSessionDescription = getUserHostSessionDescription(); + authenticationLog.info(userHostSessionDescription + "login: OK"); + } + } + + @Override + public void logLogout(final HttpSession httpSession) + { + if (authenticationLog.isInfoEnabled()) + { + final String userName = getUserCode(httpSession); + final String id = httpSession.getId(); + final long diff = System.currentTimeMillis() - httpSession.getLastAccessedTime(); + final boolean timedOut = diff / 1000.0 >= httpSession.getMaxInactiveInterval(); + final String logoutMsg = + String.format(USER_SESSION_TEMPLATE, userName, id, + timedOut ? LogoutReason.SESSION_TIMEOUT.getLogText() + : LogoutReason.SESSION_LOGOUT.getLogText()); + authenticationLog.info(logoutMsg); + } + } + + @Override + public void logSetSessionUser(String oldUserCode, String newUserCode, final boolean success) + { + if (authenticationLog.isInfoEnabled()) + { + authenticationLog.info(getUserHostSessionDescription() + + String.format("set_user_code to '%s': %s", newUserCode, + getSuccessString(success))); + } + } + + /** + * Returns a short description which contains user code, client host, and session id. + */ + protected String getUserHostSessionDescription() + { + final HttpSession httpSession = getHttpSession(); + final String remoteHost = remoteHostProvider.getRemoteHost(); + final String userName; + final String id; + if (httpSession == null) + { + userName = "UNKNOWN"; + id = "UNKNOWN"; + } else + { + userName = getUserCode(httpSession); + id = httpSession.getId(); + } + return String.format(USER_HOST_SESSION_TEMPLATE, userName, remoteHost, id); + } + + /** + * Extracts the user code from the specified session. + */ + protected abstract String getUserCode(HttpSession httpSession); + + protected HttpSession getHttpSession() + { + try + { + return requestContextProvider.getHttpServletRequest().getSession(); + } catch (RuntimeException ex) + { + return null; + } + } + +} diff --git a/common/source/java/ch/systemsx/cisd/common/servlet/AbstractCrossOriginFilter.java b/common/source/java/ch/systemsx/cisd/common/servlet/AbstractCrossOriginFilter.java new file mode 100644 index 0000000000000000000000000000000000000000..a5ce1f5fe2e82dc4a2383da2ed9a632dfc2e6e7d --- /dev/null +++ b/common/source/java/ch/systemsx/cisd/common/servlet/AbstractCrossOriginFilter.java @@ -0,0 +1,144 @@ +/* + * Copyright 2011 ETH Zuerich, CISD + * + * 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 ch.systemsx.cisd.common.servlet; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * Abstract filter that implements CORS (Cross Origin Resource Sharing) to allow a web page served + * from a given domain to access resources the openBIS AS/DSS. + * <p> + * NOTE: According to the definition of "origin" the openBIS AS and DSS are two different things + * (because they run on different ports). So the {@link AbstractCrossOriginFilter} makes it possible + * to share resources between them e.g. access an openBIS AS resource from a web page served by + * openBIS DSS. + * </p> + * <p> + * For more details on CORS see + * http://www.nczonline.net/blog/2010/05/25/cross-domain-ajax-with-cross-origin-resource-sharing/. + * + * @author Kaloyan Enimanev + */ +public abstract class AbstractCrossOriginFilter implements Filter +{ + protected static final String ORIGIN_HEADER = "Origin"; + + protected static final String ACCESS_CONTROL_ALLOW_ORIGIN_HEADER = + "Access-Control-Allow-Origin"; + + protected static final String ALLOWED_ORIGINS_KEY = "TODO"; + + private static final String ALLOW_ALL_ORIGINS = "*"; + + private FilterConfig filterConfig; + + /** + * Returns the openBIS AS and DSS domains. + */ + protected abstract List<String> getOwnDomains(); + + /** + * Returns a list of configured trusted domains. + */ + protected abstract List<String> getConfiguredTrustedDomains(); + + /** + * Returns all trusted domains. + */ + protected List<String> getAllTrustedDomains() + { + List<String> allowedOrigins = new ArrayList<String>(); + allowedOrigins.addAll(getOwnDomains()); + allowedOrigins.addAll(getConfiguredTrustedDomains()); + return allowedOrigins; + } + + private boolean isAllowedOrigin(String origin) + { + for (String allowedOrigin : getAllTrustedDomains()) + { + if (isMatching(allowedOrigin, origin)) + { + return true; + } + } + return false; + } + + + private boolean isMatching(String allowedOrigin, String origin) + { + if (allowedOrigin.equalsIgnoreCase(origin)) + { + return true; + } + if (ALLOW_ALL_ORIGINS.equals(allowedOrigin)) + { + return true; + } + return false; + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, + FilterChain filterChain) + throws IOException, ServletException + { + + final HttpServletRequest httpRequest = (HttpServletRequest) request; + String originHeader = httpRequest.getHeader(ORIGIN_HEADER); + + if (originHeader != null && isAllowedOrigin(originHeader)) + { + final HttpServletResponse httpResponse = (HttpServletResponse) response; + httpResponse.setHeader(ACCESS_CONTROL_ALLOW_ORIGIN_HEADER, originHeader); + } + + filterChain.doFilter(request, response); + } + + @Override + public void destroy() + { + } + + @Override + public void init(FilterConfig fc) throws ServletException + { + this.filterConfig = fc; + } + + /** + * Return the servlet context. + */ + protected ServletContext getServletContext() + { + return filterConfig.getServletContext(); + } +} diff --git a/common/source/java/ch/systemsx/cisd/common/servlet/CISDContextLoaderListener.java b/common/source/java/ch/systemsx/cisd/common/servlet/CISDContextLoaderListener.java new file mode 100644 index 0000000000000000000000000000000000000000..4259eecad82466c5b5352e22f757f5a3e159cd80 --- /dev/null +++ b/common/source/java/ch/systemsx/cisd/common/servlet/CISDContextLoaderListener.java @@ -0,0 +1,100 @@ +/* + * Copyright 2007 ETH Zuerich, CISD + * + * 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 ch.systemsx.cisd.common.servlet; + +import java.lang.Thread.UncaughtExceptionHandler; +import java.lang.reflect.Field; +import java.util.List; + +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; + +import org.apache.log4j.Logger; +import org.springframework.web.context.ContextLoaderListener; +import org.springframework.web.util.Log4jConfigListener; + +import ch.systemsx.cisd.base.utilities.AbstractBuildAndEnvironmentInfo; +import ch.systemsx.cisd.common.logging.LogCategory; +import ch.systemsx.cisd.common.logging.LogFactory; +import ch.systemsx.cisd.common.logging.LogInitializer; + +/** + * Extension of Spring's <code>ContextLoaderListener</code> which initializes logging via + * {@link LogInitializer#init()} and registers an default handler for uncaught exceptions. + * + * @see Log4jConfigListener + * @see ServletContextListener + * @see UncaughtExceptionHandler + * @author Christian Ribeaud + */ +public final class CISDContextLoaderListener extends ContextLoaderListener +{ + private static final Logger statusLog = + LogFactory.getLogger(LogCategory.STATUS, CISDContextLoaderListener.class); + + // + // ContextLoaderListener + // + + @Override + public final void contextInitialized(final ServletContextEvent event) + { + registerDefaultUncaughtExceptionHandler(); + LogInitializer.init(); + printBuildAndEnvironmentInfo(event); + try + { + super.contextInitialized(event); + } catch (Exception ex) + { + statusLog.error("Couldn't create application context.", ex); + } + } + + private void printBuildAndEnvironmentInfo(final ServletContextEvent event) + { + String nameOfBuildInfoAndEnvironmentClass = event.getServletContext().getInitParameter("infoClass"); + try + { + Class<?> clazz = Class.forName(nameOfBuildInfoAndEnvironmentClass); + Field field = clazz.getField("INSTANCE"); + AbstractBuildAndEnvironmentInfo info = (AbstractBuildAndEnvironmentInfo) field.get(null); + List<String> environmentInfo = info.getEnvironmentInfo(); + for (String line : environmentInfo) + { + statusLog.info(line); + } + } catch (Exception ex) + { + statusLog.warn("Couldn't get build and environment info: " + ex); + } + } + + private void registerDefaultUncaughtExceptionHandler() + { + Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() + { + @Override + public void uncaughtException(Thread thread, Throwable th) + { + statusLog.error(String.format("An unexpected error occured in thread [%s].", + thread.getName()), th); + } + }); + } + +} diff --git a/common/source/java/ch/systemsx/cisd/common/servlet/GWTRPCServiceExporter.java b/common/source/java/ch/systemsx/cisd/common/servlet/GWTRPCServiceExporter.java new file mode 100644 index 0000000000000000000000000000000000000000..73e66ee791b274f3e93715dad3da8575d3d963a2 --- /dev/null +++ b/common/source/java/ch/systemsx/cisd/common/servlet/GWTRPCServiceExporter.java @@ -0,0 +1,284 @@ +/* + * 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 ch.systemsx.cisd.common.servlet; + +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.log4j.Logger; +import org.springframework.beans.factory.BeanNameAware; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.web.context.ServletConfigAware; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.mvc.Controller; + +import com.google.gwt.user.client.rpc.IncompatibleRemoteServiceException; +import com.google.gwt.user.client.rpc.SerializationException; +import com.google.gwt.user.server.rpc.RPC; +import com.google.gwt.user.server.rpc.RPCRequest; +import com.google.gwt.user.server.rpc.RemoteServiceServlet; + +import ch.systemsx.cisd.base.exceptions.CheckedExceptionTunnel; +import ch.systemsx.cisd.common.logging.LogCategory; +import ch.systemsx.cisd.common.logging.LogFactory; +import ch.systemsx.cisd.common.logging.LogInitializer; +import ch.systemsx.cisd.common.shared.basic.IOptionalStackTraceLoggingException; +import ch.systemsx.cisd.common.utilities.ClassUtils; +import ch.systemsx.cisd.common.utilities.MethodUtils; + +/** + * This component publishes an object (see {@link #getService()}) as a service to the <i>GWT</i> + * RPC protocol. + * <p> + * Inspired by <a href="http://gwt-widget.sourceforge.net/">http://gwt-widget.sourceforge.net/</a>. + * </p> + * + * @author Christian Ribeaud + */ +public abstract class GWTRPCServiceExporter extends RemoteServiceServlet implements + InitializingBean, ServletConfigAware, DisposableBean, BeanNameAware, Controller +{ + private static final long serialVersionUID = 1L; + + private static final Logger operationLog = + LogFactory.getLogger(LogCategory.OPERATION, GWTRPCServiceExporter.class); + + private ServletConfig servletConfig; + + private String beanName; + + private Map<Method, Method> methodCache = new HashMap<Method, Method>(); + + private final String invokeMethodOnService(final Method targetMethod, + final Object[] targetParameters, final RPCRequest rpcRequest) + throws SerializationException + { + final Object result = ClassUtils.invokeMethod(targetMethod, getService(), targetParameters); + return RPC.encodeResponseForSuccess(rpcRequest.getMethod(), result, + rpcRequest.getSerializationPolicy()); + } + + private final synchronized Method getMethodToInvoke(final Method decodedMethod) + { + Method method = methodCache.get(decodedMethod); + if (method != null) + { + return method; + } + try + { + method = + getService().getClass().getMethod(decodedMethod.getName(), + decodedMethod.getParameterTypes()); + methodCache.put(decodedMethod, method); + return method; + } catch (final NoSuchMethodException ex) + { + throw new CheckedExceptionTunnel(ex); + } + } + + private final static void logException(final Exception e) + { + operationLog.error(String.format("An '%s' was thrown while processing this call.", e + .getClass().getSimpleName()), e); + } + + /** + * Invoked by {@link #processCall(String)} when RPC throws an + * {@link IncompatibleRemoteServiceException}. This implementation propagates the exception + * back to the client via RPC. + */ + private final String handleIncompatibleRemoteServiceException( + final IncompatibleRemoteServiceException e) throws SerializationException + { + logException(e); + return RPC.encodeResponseForFailure(null, e); + } + + /** + * Handles exceptions thrown by the target service, which are wrapped in + * {@link CheckedExceptionTunnel}s due to + * {@link ClassUtils#invokeMethod(Method, Object, Object...)} invocation. This method is invoked + * by {@link #processCall(String)}. This implementation encodes exceptions as RPC errors and + * returns them. For details on arguments please consult + * {@link #invokeMethodOnService(Method, Object[], RPCRequest)}. + */ + private final String handleInvocationException(final RuntimeException e, + final Method targetMethod, final RPCRequest rpcRequest) throws Exception + { + final Exception cause = CheckedExceptionTunnel.unwrapIfNecessary(e); + logInvocationException(targetMethod, cause); + if (rpcRequest != null) + { + final String failurePayload = + RPC.encodeResponseForFailure(rpcRequest.getMethod(), cause, rpcRequest + .getSerializationPolicy()); + return failurePayload; + } + return RPC.encodeResponseForFailure(null, cause); + } + + private void logInvocationException(Method targetMethod, Exception cause) + { + final String methodDescription = + String.format("Invoking method '%s' failed.", targetMethod == null ? "<unknown>" + : MethodUtils.describeMethod(targetMethod)); + if (cause instanceof IOptionalStackTraceLoggingException) + { + operationLog.error(methodDescription + ": " + cause.getMessage()); + } else + { + operationLog.error(methodDescription, cause); + } + } + + /** + * Invoked by {@link #processCall(String)} for an exception if no suitable exception handler was + * found. This is the outermost exception handler, catching any exceptions not caught by other + * exception handlers or even thrown by those handlers. + */ + private final String handleExporterProcessingException(final Exception e) + { + logException(e); + throw CheckedExceptionTunnel.wrapIfNecessary(e); + } + + /** + * The corresponding <i>GWT</i> service object. + * <p> + * Must be specified in subclasses. + * </p> + */ + protected abstract Object getService(); + + // + // ServletConfigAware + // + + @Override + public final void setServletConfig(final ServletConfig servletConfig) + { + assert servletConfig != null; + if (operationLog.isTraceEnabled()) + { + final String message = + "Setting servlet config for class '" + getClass().getSimpleName() + "'."; + operationLog.trace(message); + } + this.servletConfig = servletConfig; + } + + // + // BeanNameAware + // + + @Override + public final void setBeanName(final String beanName) + { + this.beanName = beanName; + } + + // + // InitializingBean + // + + /** + * Note that {@link #setServletConfig(ServletConfig)} gets called before this method. + */ + @Override + public final void afterPropertiesSet() throws Exception + { + LogInitializer.init(); + if (getService() == null) + { + throw new ServletException("You must specify a service object."); + } + if (operationLog.isTraceEnabled()) + { + final String message = + "All the properties have been set for bean '" + beanName + + "'. Time to initialize this servlet."; + operationLog.trace(message); + } + init(servletConfig); + } + + // + // RemoteServiceServlet + // + + @Override + public final ServletConfig getServletConfig() + { + return servletConfig; + } + + @Override + protected void doUnexpectedFailure(Throwable throwable) + { + operationLog.error("Unexpected throwable", throwable); + super.doUnexpectedFailure(throwable); + } + + + /** + * Overridden from {@link RemoteServiceServlet} and invoked by the servlet code. + */ + @Override + public final String processCall(final String payload) throws SerializationException + { + try + { + RPCRequest request = null; + Method targetMethod = null; + try + { + request = RPC.decodeRequest(payload, getService().getClass(), this); + targetMethod = getMethodToInvoke(request.getMethod()); + final Object[] targetParameters = request.getParameters(); + return invokeMethodOnService(targetMethod, targetParameters, request); + } catch (final RuntimeException e) + { + return handleInvocationException(e, targetMethod, request); + } + } catch (final IncompatibleRemoteServiceException e) + { + return handleIncompatibleRemoteServiceException(e); + } catch (final Exception e) + { + return handleExporterProcessingException(e); + } + } + + // + // Controller + // + + @Override + public final ModelAndView handleRequest(final HttpServletRequest request, + final HttpServletResponse response) throws Exception + { + doPost(request, response); + return null; + } + +} \ No newline at end of file diff --git a/common/source/java/ch/systemsx/cisd/common/servlet/GWTSpringController.java b/common/source/java/ch/systemsx/cisd/common/servlet/GWTSpringController.java new file mode 100644 index 0000000000000000000000000000000000000000..4a042cb23bdc4446c3b60d1e6171654f6c31b3e3 --- /dev/null +++ b/common/source/java/ch/systemsx/cisd/common/servlet/GWTSpringController.java @@ -0,0 +1,150 @@ +/* + * Copyright 2007 ETH Zuerich, CISD + * + * 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 ch.systemsx.cisd.common.servlet; + +import javax.servlet.ServletConfig; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.log4j.Logger; +import org.springframework.beans.factory.BeanNameAware; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.web.context.ServletConfigAware; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.mvc.Controller; + +import com.google.gwt.user.client.rpc.RemoteService; +import com.google.gwt.user.client.rpc.SerializationException; +import com.google.gwt.user.server.rpc.RemoteServiceServlet; + +import ch.systemsx.cisd.common.logging.LogCategory; +import ch.systemsx.cisd.common.logging.LogFactory; +import ch.systemsx.cisd.common.logging.LogInitializer; + +/** + * An abstract <code>RemoteServiceServlet</code> that merges <i>Spring</i>'s {@link Controller} + * and manages this servlet the same way <i>Spring</i> beans are. + * + * @author Christian Ribeaud + */ +public abstract class GWTSpringController extends RemoteServiceServlet implements Controller, + InitializingBean, ServletConfigAware, DisposableBean, BeanNameAware, RemoteService +{ + + private static final Logger operationLog = + LogFactory.getLogger(LogCategory.OPERATION, GWTSpringController.class); + + private static final long serialVersionUID = 1L; + + private ServletConfig servletConfig; + + private String beanName; + + // + // RemoteServiceServlet + // + + @Override + public String processCall(final String payload) throws SerializationException + { + try + { + return super.processCall(payload); + } catch (final Throwable th) + { + operationLog.error("Error processing request.", th); + if (th instanceof Error) + { + throw (Error) th; + } else if (th instanceof RuntimeException) + { + throw (RuntimeException) th; + } else + { + throw new Error("Unexpected error: " + th.getMessage()); + } + } + } + + @Override + public final ServletConfig getServletConfig() + { + return servletConfig; + } + + // + // Controller + // + + @Override + public final ModelAndView handleRequest(final HttpServletRequest request, + final HttpServletResponse response) throws Exception + { + doPost(request, response); + return null; + } + + // + // InitializingBean + // + + /** + * Note that {@link #setServletConfig(ServletConfig)} gets called before this method. + */ + @Override + public final void afterPropertiesSet() throws Exception + { + LogInitializer.init(); + if (operationLog.isTraceEnabled()) + { + final String message = + "All the properties have been set for bean '" + beanName + + "'. Time to initialize this servlet."; + operationLog.trace(message); + } + init(servletConfig); + } + + // + // ServletConfigAware + // + + @Override + public final void setServletConfig(final ServletConfig servletConfig) + { + assert servletConfig != null; + if (operationLog.isTraceEnabled()) + { + final String message = + "Setting servlet config for class '" + getClass().getSimpleName() + "'."; + operationLog.trace(message); + } + this.servletConfig = servletConfig; + } + + // + // BeanNameAware + // + + @Override + public final void setBeanName(final String beanName) + { + this.beanName = beanName; + } + +} \ No newline at end of file diff --git a/common/source/java/ch/systemsx/cisd/common/servlet/IActionLog.java b/common/source/java/ch/systemsx/cisd/common/servlet/IActionLog.java new file mode 100644 index 0000000000000000000000000000000000000000..67993a17c30e09c5be9af8136cea0ef406e2cef0 --- /dev/null +++ b/common/source/java/ch/systemsx/cisd/common/servlet/IActionLog.java @@ -0,0 +1,71 @@ +/* + * Copyright 2008 ETH Zuerich, CISD + * + * 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 ch.systemsx.cisd.common.servlet; + +import javax.servlet.http.HttpSession; + + +/** + * Super interface of all action log classes. Has action methods concerning authentication. + * + * @author Franz-Josef Elmer + */ +public interface IActionLog +{ + public enum LogoutReason + { + SESSION_LOGOUT(""), SESSION_TIMEOUT(" (session timed out)"), USER_DELETED( + " (user was removed)"); + + private final String logText; + + LogoutReason(String logText) + { + this.logText = logText; + } + + public String getLogText() + { + return logText; + } + + } + + /** + * Logs a failed authentication attempt for the specified user code. + */ + public void logFailedLoginAttempt(String userCode); + + /** + * Logs success authentication. + */ + public void logSuccessfulLogin(); + + /** + * Logs a logout. + * + * @param httpSession Session objects which might contain information useful to be logged (e.g. + * user to be logged out). + */ + public void logLogout(HttpSession httpSession); + + /** + * Logs a call to set a new session user. + */ + public void logSetSessionUser(String oldUserCode, String newUserCode, final boolean success); + +} diff --git a/common/source/java/ch/systemsx/cisd/common/servlet/IRequestContextProvider.java b/common/source/java/ch/systemsx/cisd/common/servlet/IRequestContextProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..52425fb6cdc7ed140d0d1c4b977246999b396d23 --- /dev/null +++ b/common/source/java/ch/systemsx/cisd/common/servlet/IRequestContextProvider.java @@ -0,0 +1,32 @@ +/* + * Copyright 2007 ETH Zuerich, CISD + * + * 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 ch.systemsx.cisd.common.servlet; + +import javax.servlet.http.HttpServletRequest; + +/** + * Implementation of this interface provides a way to expose the native {@link HttpServletRequest}. + * + * @author Christian Ribeaud + */ +public interface IRequestContextProvider +{ + + /** Returns the <code>HttpServletRequest</code> object. */ + public HttpServletRequest getHttpServletRequest(); + +} diff --git a/common/source/java/ch/systemsx/cisd/common/servlet/RequestContextProviderAdapter.java b/common/source/java/ch/systemsx/cisd/common/servlet/RequestContextProviderAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..0700dc7545d7d3458eeacd136d542425b0dd86b4 --- /dev/null +++ b/common/source/java/ch/systemsx/cisd/common/servlet/RequestContextProviderAdapter.java @@ -0,0 +1,60 @@ +/* + * Copyright 2008 ETH Zuerich, CISD + * + * 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 ch.systemsx.cisd.common.servlet; + +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.lang.StringUtils; + +import ch.systemsx.cisd.common.server.IRemoteHostProvider; + +/** + * A <code>IRemoteHostProvider</code> implementation which adapts an encapsulated + * <code>IRequestContextProvider</code>. + * + * @author Christian Ribeaud + */ +public final class RequestContextProviderAdapter implements IRemoteHostProvider +{ + private final IRequestContextProvider requestContextProvider; + + public RequestContextProviderAdapter(final IRequestContextProvider requestContextProvider) + { + assert requestContextProvider != null : "Undefined IRequestContextProvider."; + this.requestContextProvider = requestContextProvider; + } + + // + // IRemoteHostProvider + // + + @Override + public final String getRemoteHost() + { + final HttpServletRequest request = requestContextProvider.getHttpServletRequest(); + if (request == null) + { + return UNKNOWN; + } + final String remoteHost = request.getRemoteHost(); + if (StringUtils.isEmpty(remoteHost)) + { + return StringUtils.defaultIfEmpty(request.getRemoteAddr(), UNKNOWN); + } + return remoteHost; + } +} \ No newline at end of file diff --git a/common/source/java/ch/systemsx/cisd/common/servlet/SpringRequestContextProvider.java b/common/source/java/ch/systemsx/cisd/common/servlet/SpringRequestContextProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..fd468904075d196e280186fc28ba35c41f0eecce --- /dev/null +++ b/common/source/java/ch/systemsx/cisd/common/servlet/SpringRequestContextProvider.java @@ -0,0 +1,63 @@ +/* + * Copyright 2007 ETH Zuerich, CISD + * + * 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 ch.systemsx.cisd.common.servlet; + +import javax.servlet.http.HttpServletRequest; + +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +/** + * The <i>Spring</i> implementation of <code>IRequestContextProvider</code>. + * <p> + * This internally uses <code>RequestContextHolder</code> to do its job. + * </p> + * + * @author Christian Ribeaud + */ +public final class SpringRequestContextProvider implements IRequestContextProvider +{ + // This is used only in system tests + private HttpServletRequest request; + + // + // IRequestContextProvider + // + + public void setRequest(HttpServletRequest request) + { + this.request = request; + } + + @Override + public final HttpServletRequest getHttpServletRequest() + { + if (request != null) + { + return request; + } + try + { + ServletRequestAttributes attributes = + (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes(); + return attributes.getRequest(); + } catch (RuntimeException ex) + { + return null; + } + } +} diff --git a/common/source/java/ch/systemsx/cisd/common/spring/ExposablePropertyPlaceholderConfigurer.java b/common/source/java/ch/systemsx/cisd/common/spring/ExposablePropertyPlaceholderConfigurer.java new file mode 100644 index 0000000000000000000000000000000000000000..4f58a6ccc9d5e935c3861de0decb7d41b795d1db --- /dev/null +++ b/common/source/java/ch/systemsx/cisd/common/spring/ExposablePropertyPlaceholderConfigurer.java @@ -0,0 +1,71 @@ +/* + * Copyright 2008 ETH Zuerich, CISD + * + * 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 ch.systemsx.cisd.common.spring; + +import java.util.HashSet; +import java.util.Properties; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer; +import org.springframework.util.StringUtils; + +/** + * Bean that should be used instead of the {@link PropertyPlaceholderConfigurer} if you want to have + * access to the resolved properties not obligatory from the Spring context. e.g. from JSP or so. + * + * @author Christian Ribeaud + */ +public final class ExposablePropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer +{ + /** Standard bean name in an application context file. */ + public static final String PROPERTY_CONFIGURER_BEAN_NAME = "propertyConfigurer"; + + private Properties resolvedProps; + + /** Returns the resolved properties. */ + public final Properties getResolvedProps() + { + return resolvedProps; + } + + // + // PropertyPlaceholderConfigurer + // + + @Override + protected final String convertPropertyValue(final String originalValue) + { + // Can handle null value + return StringUtils.trimWhitespace(originalValue); + } + + @Override + protected final void processProperties( + final ConfigurableListableBeanFactory beanFactoryToProcess, final Properties props) + throws BeansException + { + super.processProperties(beanFactoryToProcess, props); + resolvedProps = new Properties(); + for (final Object key : props.keySet()) + { + final String keyStr = key.toString(); + resolvedProps.setProperty(keyStr, parseStringValue(props.getProperty(keyStr), props, + new HashSet<Object>())); + } + } +} \ No newline at end of file diff --git a/common/source/java/ch/systemsx/cisd/common/spring/HttpInvokerUtils.java b/common/source/java/ch/systemsx/cisd/common/spring/HttpInvokerUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..42f35d1e85b51e005889cea02042fd7e39090ff6 --- /dev/null +++ b/common/source/java/ch/systemsx/cisd/common/spring/HttpInvokerUtils.java @@ -0,0 +1,123 @@ +/* + * Copyright 2009 ETH Zuerich, CISD + * + * 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 ch.systemsx.cisd.common.spring; + +import java.net.InetSocketAddress; +import java.net.ProxySelector; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.List; + +import org.springframework.remoting.httpinvoker.CommonsHttpInvokerRequestExecutor; +import org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean; + +import com.marathon.util.spring.StreamSupportingHttpInvokerProxyFactoryBean; + +/** + * Utility methods for HTTP Invocations. <br> + * This class is used by clients to create service stubs. If a dependency to external libraries is + * added to this class then build scripts used to build the API jar have to be changed as well. + * + * @author Franz-Josef Elmer + */ +public class HttpInvokerUtils +{ + /** + * Creates a service stub for the specified service interface. + * + * @param serviceURL URL providing the service via HTTP tunneling. + * @param serverTimeoutInMillis Service time out in milliseconds. A values of 0 means never + * timeout. + */ + public static <T> T createServiceStub(final Class<T> serviceInterface, final String serviceURL, + final long serverTimeoutInMillis) + { + final HttpInvokerProxyFactoryBean httpInvokerProxy = new HttpInvokerProxyFactoryBean(); + httpInvokerProxy.setBeanClassLoader(serviceInterface.getClassLoader()); + httpInvokerProxy.setServiceUrl(serviceURL); + httpInvokerProxy.setServiceInterface(serviceInterface); + final CommonsHttpInvokerRequestExecutor httpInvokerRequestExecutor = + new CommonsHttpInvokerRequestExecutor(); + httpInvokerRequestExecutor.setReadTimeout((int) serverTimeoutInMillis); + final InetSocketAddress proxyAddressOrNull = tryFindProxy(serviceURL); + if (proxyAddressOrNull != null) + { + httpInvokerRequestExecutor.getHttpClient().getHostConfiguration().setProxy( + proxyAddressOrNull.getHostName(), proxyAddressOrNull.getPort()); + } + httpInvokerProxy.setHttpInvokerRequestExecutor(httpInvokerRequestExecutor); + httpInvokerProxy.afterPropertiesSet(); + return getCastedService(httpInvokerProxy); + } + + public static <T> T createStreamSupportingServiceStub(final Class<T> serviceInterface, + final String serviceURL, final long serverTimeoutInMillis) + { + final StreamSupportingHttpInvokerProxyFactoryBean httpInvokerProxy = + new StreamSupportingHttpInvokerProxyFactoryBean(); + httpInvokerProxy.setBeanClassLoader(serviceInterface.getClassLoader()); + httpInvokerProxy.setServiceUrl(serviceURL); + httpInvokerProxy.setServiceInterface(serviceInterface); + ((CommonsHttpInvokerRequestExecutor) httpInvokerProxy.getHttpInvokerRequestExecutor()) + .setReadTimeout((int) serverTimeoutInMillis); + final InetSocketAddress proxyAddressOrNull = HttpInvokerUtils.tryFindProxy(serviceURL); + if (proxyAddressOrNull != null) + { + ((CommonsHttpInvokerRequestExecutor) httpInvokerProxy.getHttpInvokerRequestExecutor()) + .getHttpClient().getHostConfiguration().setProxy( + proxyAddressOrNull.getHostName(), proxyAddressOrNull.getPort()); + } + httpInvokerProxy.afterPropertiesSet(); + return getCastedService(httpInvokerProxy); + } + + @SuppressWarnings("unchecked") + private final static <T> T getCastedService(final HttpInvokerProxyFactoryBean httpInvokerProxy) + { + return (T) httpInvokerProxy.getObject(); + } + + /** + * Returns the proxy's inet address for <var>serviceURL</var>, or <code>null</code>, if no proxy + * is defined. + */ + public static InetSocketAddress tryFindProxy(String serviceURL) + { + try + { + final ProxySelector selector = ProxySelector.getDefault(); + final List<java.net.Proxy> proxyList = selector.select(new URI(serviceURL)); + for (java.net.Proxy proxy : proxyList) + { + if (java.net.Proxy.Type.HTTP == proxy.type()) + { + return (InetSocketAddress) proxy.address(); + } + } + } catch (IllegalArgumentException e) + { + } catch (URISyntaxException ex) + { + } + return null; + } + + private HttpInvokerUtils() + { + } + +} diff --git a/common/sourceTest/java/ch/systemsx/cisd/common/servlet/AbstractCrossOriginFilterTest.java b/common/sourceTest/java/ch/systemsx/cisd/common/servlet/AbstractCrossOriginFilterTest.java new file mode 100644 index 0000000000000000000000000000000000000000..1d9e86e8783d0d77637fa921c26dead5d2c69e32 --- /dev/null +++ b/common/sourceTest/java/ch/systemsx/cisd/common/servlet/AbstractCrossOriginFilterTest.java @@ -0,0 +1,174 @@ +/* + * Copyright 2011 ETH Zuerich, CISD + * + * 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 ch.systemsx.cisd.common.servlet; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import javax.servlet.FilterChain; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.jmock.Expectations; +import org.jmock.Mockery; +import org.testng.AssertJUnit; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +/** + * @author Kaloyan Enimanev + */ +public class AbstractCrossOriginFilterTest extends AssertJUnit +{ + + private Mockery context; + + private HttpServletRequest request; + + private HttpServletResponse response; + + private FilterChain filterChain; + + @BeforeMethod + public void setUp() + { + context = new Mockery(); + request = context.mock(HttpServletRequest.class); + response = context.mock(HttpServletResponse.class); + filterChain = context.mock(FilterChain.class); + } + + @Test + public void testNoAdditionalConfiguration() throws Exception + { + List<String> ownDomains = Arrays.asList("https://host:8443", "https://host:8444"); + List<String> trustedDomains = Collections.emptyList(); + + CrossOriginFilterImpl filter = new CrossOriginFilterImpl(ownDomains, trustedDomains); + + assertAllowedOrigin(filter, "https://host:8443"); + assertAllowedOrigin(filter, "https://host:8444"); + + assertForbiddenOrigin(filter, "http://host:8444"); + assertForbiddenOrigin(filter, "http://host:8443"); + assertForbiddenOrigin(filter, "https://random-host"); + assertForbiddenOrigin(filter, null); + } + + @Test + public void testAdditionalConfiguration() throws Exception + { + List<String> ownDomains = Arrays.asList("https://host:8443", "https://host:8444"); + List<String> trustedDomains = + Arrays.asList("http://otherhost", "http://thirdhost.com:1234"); + + CrossOriginFilterImpl filter = new CrossOriginFilterImpl(ownDomains, trustedDomains); + + assertAllowedOrigin(filter, "https://host:8443"); + assertAllowedOrigin(filter, "https://host:8444"); + assertAllowedOrigin(filter, "http://otherhost"); + assertAllowedOrigin(filter, "http://thirdhost.com:1234"); + + assertForbiddenOrigin(filter, "http://otherhost:8444"); + assertForbiddenOrigin(filter, "https://thirdhost.com:1234"); + assertForbiddenOrigin(filter, "https://randomhost"); + assertForbiddenOrigin(filter, null); + } + + @Test + public void testWildCard() throws Exception + { + List<String> ownDomains = Arrays.asList("https://host:8443", "https://host:8444"); + List<String> trustedDomains = Arrays.asList("*"); + + CrossOriginFilterImpl filter = new CrossOriginFilterImpl(ownDomains, trustedDomains); + + assertAllowedOrigin(filter, "https://host:8443"); + assertAllowedOrigin(filter, "https://host:8444"); + assertAllowedOrigin(filter, "http://host:8443"); + assertAllowedOrigin(filter, "http://host:8444"); + assertAllowedOrigin(filter, "http://otherhost:8444"); + assertAllowedOrigin(filter, "https://thirdhost.com:1234"); + + assertForbiddenOrigin(filter, null); + } + + private void assertAllowedOrigin(CrossOriginFilterImpl filter, final String origin) + throws Exception + { + + context.checking(new Expectations() { + { + one(request).getHeader(AbstractCrossOriginFilter.ORIGIN_HEADER); + will(returnValue(origin)); + + // origin allowed + one(response).setHeader( + AbstractCrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN_HEADER, origin); + + one(filterChain).doFilter(request, response); + } + }); + filter.doFilter(request, response, filterChain); + context.assertIsSatisfied(); + } + + private void assertForbiddenOrigin(CrossOriginFilterImpl filter, final String origin) + throws Exception + { + + context.checking(new Expectations() + { + { + one(request).getHeader(AbstractCrossOriginFilter.ORIGIN_HEADER); + will(returnValue(origin)); + + one(filterChain).doFilter(request, response); + } + }); + filter.doFilter(request, response, filterChain); + } + + static class CrossOriginFilterImpl extends AbstractCrossOriginFilter + { + + private final List<String> ownDomains; + + private final List<String> trustedDomains; + + public CrossOriginFilterImpl(List<String> ownDomains, List<String> trustedDomains) + { + super(); + this.ownDomains = ownDomains; + this.trustedDomains = trustedDomains; + } + + @Override + protected List<String> getOwnDomains() + { + return ownDomains; + } + + @Override + protected List<String> getConfiguredTrustedDomains() + { + return trustedDomains; + } + } + +} diff --git a/common/sourceTest/java/ch/systemsx/cisd/common/spring/ExposablePropertyPlaceholderConfigurerTest.java b/common/sourceTest/java/ch/systemsx/cisd/common/spring/ExposablePropertyPlaceholderConfigurerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..ca287057896aff97b9852b0533c2bd232ee4472d --- /dev/null +++ b/common/sourceTest/java/ch/systemsx/cisd/common/spring/ExposablePropertyPlaceholderConfigurerTest.java @@ -0,0 +1,68 @@ +/* + * Copyright 2008 ETH Zuerich, CISD + * + * 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 ch.systemsx.cisd.common.spring; + +import static org.testng.AssertJUnit.assertEquals; + +import java.util.Properties; + +import org.jmock.Expectations; +import org.jmock.Mockery; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +/** + * + * + * @author Franz-Josef Elmer + */ +public class ExposablePropertyPlaceholderConfigurerTest +{ + private Mockery context; + private ConfigurableListableBeanFactory beanFactory; + + @BeforeMethod + public void setUp() + { + context = new Mockery(); + beanFactory = context.mock(ConfigurableListableBeanFactory.class); + context.checking(new Expectations() + { + { + ignoring(beanFactory); + } + }); + } + + @Test + public void test() + { + ExposablePropertyPlaceholderConfigurer configurer = new ExposablePropertyPlaceholderConfigurer(); + Properties properties = new Properties(); + properties.setProperty("a", "alpha"); + properties.setProperty("b", "beta"); + properties.setProperty("ab", "${a} ${b}"); + + configurer.processProperties(beanFactory, properties); + Properties resolvedProps = configurer.getResolvedProps(); + + assertEquals("alpha", resolvedProps.get("a")); + assertEquals("beta", resolvedProps.get("b")); + assertEquals("alpha beta", resolvedProps.get("ab")); + } +} diff --git a/common/sourceTest/java/ch/systemsx/cisd/common/test/AbstractDependencyCheckingTestCase.java b/common/sourceTest/java/ch/systemsx/cisd/common/test/AbstractDependencyCheckingTestCase.java new file mode 100644 index 0000000000000000000000000000000000000000..b0110eb7c09402ed7c525064221e58c57a2978da --- /dev/null +++ b/common/sourceTest/java/ch/systemsx/cisd/common/test/AbstractDependencyCheckingTestCase.java @@ -0,0 +1,35 @@ +/* + * Copyright 2011 ETH Zuerich, CISD + * + * 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 ch.systemsx.cisd.common.test; + + +/** + * Abstract super class of dependency checking tests for web servers where classes files + * are in <tt>targets/www/WEB-INF/classes</tt>. + * + * @author Franz-Josef Elmer + */ +public abstract class AbstractDependencyCheckingTestCase extends DependencyCheckingTest +{ + + @Override + protected String getPathToClassesCompiledByEclipse() + { + return "targets/www/WEB-INF/classes"; + } + +}