Commit c2911cef authored by Matija Obreza's avatar Matija Obreza
Browse files

Merge branch 'fixes-for-2.2' into 'master'

Fixes for 2.2

See merge request genesys-pgr/genesys-server!110
parents f595c3c5 c56fee41
/**
* Copyright 2016 Global Crop Diversity Trust
*
* 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.genesys2.server.servlet.filter;
import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import org.apache.commons.lang.StringUtils;
import org.genesys2.spring.config.NewGUIViewResolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.util.UriUtils;
/**
* The {@code NewGUIFilter} allows for having URL-based versioned user
* interfaces. Any URL prefixed with {@code /version/} is stripped of the
* version information and the version data stored. The decorators are loaded
* before this filter and will appropriately render appropriately.
*
* @author Matija Obreza
*/
public class NewGUIFilter implements Filter {
private static final Logger LOG = LoggerFactory.getLogger(NewGUIFilter.class);
public static final String REQUEST_GUIVERSION_ATTR = LocaleURLFilter.class.getName() + ".GUIVERSION";
private static final Pattern newGuiPattern = Pattern.compile("/(\\d+)(/.+)");
private String[] excludedPaths;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
String excludePaths = filterConfig.getInitParameter("exclude-paths");
if (StringUtils.isNotBlank(excludePaths)) {
String[] ex = excludePaths.split("\\s+");
for (String e : ex) {
LOG.info("Excluding path: {}", e);
}
this.excludedPaths = ex;
}
}
@Override
public void destroy() {
if (LOG.isDebugEnabled()) {
LOG.debug("Destroying NewGUIFilter");
}
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
final HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;
HttpServletResponse httpResponse = (HttpServletResponse) servletResponse;
final String originalUrl = httpRequest.getRequestURI().substring(httpRequest.getContextPath().length());
final String origianlUrlDecoded = UriUtils.decode(originalUrl, "UTF-8");
if (LOG.isTraceEnabled()) {
LOG.trace("Incoming URL: {}", originalUrl);
}
final Matcher matcher = newGuiPattern.matcher(originalUrl);
if (matcher.matches()) {
final String guiPrefix = "/" + matcher.group(1);
final String remainingUrl = matcher.group(2);
if (LOG.isDebugEnabled()) {
LOG.trace("URL matches! prefix={} remaining={}", guiPrefix, remainingUrl);
LOG.debug("Proxying request to remaining URL {}", remainingUrl);
}
HttpServletRequestWrapper guiRequest = new HttpServletRequestWrapper(httpRequest) {
@Override
public String getServletPath() {
String servletPath = super.getServletPath();
// servletPath is URL decoded, must use origianlUrlDecoded.
if (origianlUrlDecoded.equals(servletPath)) {
if (LOG.isDebugEnabled())
LOG.debug("servletPath={} remaining={}", servletPath, remainingUrl);
return remainingUrl;
}
return servletPath;
}
@Override
public String getRequestURI() {
String requestURI = super.getRequestURI();
// requestURI is URL encoded, must use originalUrl.
if (originalUrl.equals(requestURI)) {
if (LOG.isDebugEnabled())
LOG.debug("requestURI={} remaining={}", requestURI, remainingUrl);
return remainingUrl;
}
return requestURI;
}
};
HttpServletResponseWrapper guiResponse = new HttpServletResponseWrapper(httpResponse) {
private boolean isExcluded(String url) {
for (String excludedPath : excludedPaths) {
if (url.startsWith(excludedPath)) {
if (LOG.isTraceEnabled()) {
LOG.trace("Excluded={} matches {}", excludedPath, url);
}
return true;
}
}
return url.startsWith("?");
}
@Override
public String encodeURL(String url) {
if (isExcluded(url)) {
return super.encodeURL(url);
} else {
String encodedURL = super.encodeURL(guiPrefix + url);
if (LOG.isDebugEnabled()) {
LOG.debug("encodeURL {} to {}", url, encodedURL);
}
return encodedURL;
}
}
@Override
@Deprecated
public String encodeUrl(String url) {
if (isExcluded(url)) {
return super.encodeUrl(url);
} else {
String encodedURL = super.encodeUrl(guiPrefix + url);
if (LOG.isDebugEnabled()) {
LOG.debug("encodeUrl {} to {}", url, encodedURL);
}
return encodedURL;
}
}
@Override
public String encodeRedirectURL(String url) {
if (isExcluded(url)) {
return super.encodeRedirectURL(url);
} else {
String encodedURL = super.encodeRedirectURL(guiPrefix + url);
if (LOG.isDebugEnabled()) {
LOG.debug("encodeRedirectURL {} to {}", url, encodedURL);
}
return encodedURL;
}
}
};
try {
try {
final int guiVersion = Integer.parseInt(matcher.group(1));
httpRequest.setAttribute(REQUEST_GUIVERSION_ATTR, guiVersion);
LocalGUIVersion.set(guiVersion);
} catch (NumberFormatException e) {
LOG.info("Invalid GUI version in {}: {}", matcher.group(1), e.getMessage());
}
filterChain.doFilter(guiRequest, guiResponse);
} finally {
LocalGUIVersion.remove();
}
} else {
if (LOG.isTraceEnabled()) {
LOG.trace("No match on url {}", originalUrl);
}
filterChain.doFilter(servletRequest, servletResponse);
}
}
/**
* A ThreadLocal utility to manage the GUI version information. Used by
* {@link NewGUIViewResolver}.
*/
public static class LocalGUIVersion {
// Thread local variable containing each thread's ID
private static final ThreadLocal<Integer> threadGuiVersion = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return 0;
};
};
// Returns the current thread's unique ID, assigning it if necessary
public static void set(int version) {
threadGuiVersion.set(version);
}
public static void remove() {
threadGuiVersion.remove();
}
// Returns the current thread's unique ID, assigning it if necessary
public static int get() {
return threadGuiVersion.get();
}
}
}
......@@ -19,6 +19,7 @@ package org.genesys2.spring.config;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import com.hazelcast.config.Config;
......@@ -91,7 +92,7 @@ public abstract class HazelcastConfig {
}
@Value("${hazelcast.members}")
private List<String> hazelcastMembers;
private String[] hazelcastMembers;
@Bean
public HazelcastInstance hazelcast(final ManagedContext managedContext) {
......@@ -112,45 +113,58 @@ public abstract class HazelcastConfig {
network.setPort(hazelPort);
network.setPortAutoIncrement(true);
if (StringUtils.isNotBlank(hazelPublicAddress)) {
System.out.println("Hazelcast: Using public address " + hazelPublicAddress);
try {
final InetAddress ipAddress = InetAddress.getByName(hazelPublicAddress);
LOG.warn("Public ip={} siteLocal={}", ipAddress, ipAddress.isSiteLocalAddress());
network.setPublicAddress(ipAddress.getHostAddress());
} catch (final UnknownHostException e) {
LOG.warn("Could not resolve {}, error {}", hazelPublicAddress, e.getMessage());
network.setPublicAddress(hazelPublicAddress);
}
}
if (StringUtils.isNotBlank(hazelInterfaces)) {
network.getInterfaces().setEnabled(true);
for (final String hazelInterface : hazelInterfaces.split(",")) {
final String trimmed = hazelInterface.trim();
if (StringUtils.isNotBlank(trimmed)) {
System.out.println("Hazelcast: Adding single interface " + trimmed);
try {
final InetAddress ipAddress = InetAddress.getByName(hazelPublicAddress);
LOG.warn("Interface ip={} ={}", ipAddress, ipAddress.isSiteLocalAddress());
network.getInterfaces().addInterface(ipAddress.toString());
final InetAddress ipAddress = InetAddress.getByName(trimmed);
LOG.warn("Interface ip={} ={}", ipAddress.getHostAddress(), ipAddress.isSiteLocalAddress());
network.getInterfaces().addInterface(ipAddress.getHostAddress());
} catch (final UnknownHostException e) {
LOG.warn("Could not resolve {}, error {}", hazelPublicAddress, e.getMessage());
LOG.warn("Could not resolve {}, error {}", trimmed, e.getMessage());
network.getInterfaces().addInterface(trimmed);
}
}
}
}
if (StringUtils.isNotBlank(hazelPublicAddress)) {
System.out.println("Hazelcast: Using public address " + hazelPublicAddress);
try {
final InetAddress ipAddress = InetAddress.getByName(hazelPublicAddress);
LOG.warn("Public ip={} siteLocal={}", ipAddress, ipAddress.isSiteLocalAddress());
network.setPublicAddress(ipAddress.getHostAddress());
} catch (final UnknownHostException e) {
LOG.warn("Could not resolve {}, error {}", hazelPublicAddress, e.getMessage());
network.setPublicAddress(hazelPublicAddress);
}
} else {
try {
hazelPublicAddress = InetAddress.getLocalHost().getHostName();
System.out.println("Hazelcast: Using public address " + hazelPublicAddress);
final InetAddress ipAddress = InetAddress.getByName(InetAddress.getLocalHost().getHostName());
LOG.warn("Public ip={} siteLocal={}", ipAddress, ipAddress.isSiteLocalAddress());
network.setPublicAddress(ipAddress.getHostAddress());
} catch (final UnknownHostException e) {
LOG.warn("Could not resolve {}, error {}", hazelPublicAddress, e.getMessage());
network.setPublicAddress(hazelPublicAddress);
}
}
final JoinConfig join = network.getJoin();
if (!hasConfiguredMembers()) {
LOG.warn("Enabling Hazelcast multicast, members={}", hazelcastMembers);
LOG.warn("Enabling Hazelcast multicast, members={}", Arrays.toString(hazelcastMembers));
join.getMulticastConfig().setEnabled(true);
final TcpIpConfig tcpIpConfig = join.getTcpIpConfig();
tcpIpConfig.setEnabled(false);
} else {
join.getMulticastConfig().setEnabled(false);
LOG.warn("Enabling TCP/IP discovery for members={}", hazelcastMembers);
LOG.warn("Enabling TCP/IP discovery for members={}", Arrays.toString(hazelcastMembers));
final TcpIpConfig tcpIpConfig = join.getTcpIpConfig();
tcpIpConfig.setEnabled(true);
tcpIpConfig.setConnectionTimeoutSeconds(20);
......@@ -179,8 +193,8 @@ public abstract class HazelcastConfig {
}
private boolean hasConfiguredMembers() {
LOG.debug("HZ members={}", hazelcastMembers);
return (this.hazelcastMembers != null) && (this.hazelcastMembers.size() != 0) && (this.hazelcastMembers.get(0).length() > 0);
LOG.debug("HZ members={}", Arrays.toString(hazelcastMembers));
return (this.hazelcastMembers != null) && (this.hazelcastMembers.length != 0) && (this.hazelcastMembers[0].length() > 0);
}
@Bean
......
/*
* Copyright 2017 Global Crop Diversity Trust
*
* 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.genesys2.spring.config;
import java.io.File;
import java.util.Locale;
import org.genesys2.server.servlet.filter.NewGUIFilter;
import org.genesys2.server.servlet.filter.NewGUIFilter.LocalGUIVersion;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
/**
* Using the GUI version captured in {@link NewGUIFilter}, this view resolver
* checks for view (.jsp) files in
* {@code getPrefix() + "/" + LocalGUIVersion.get() + viewName + getSuffix()}
* location. For example the /admin/index view will be resolved to
* /WEB-INF/jsp/admin/index.jsp with the standard resolver.
*
* The {@code NewGUIViewResolver} first checks if the versioned view exists at
* {@code /WEB-INF/jsp/1/admin/index.jsp} and returns /1/admin/index if the
* relevant .jsp file exists.
*
* @author Matija Obreza
*
*/
public class NewGUIViewResolver extends InternalResourceViewResolver {
private static final Logger LOG = LoggerFactory.getLogger(NewGUIViewResolver.class);
@Override
protected Object getCacheKey(final String viewName, final Locale locale) {
if (LocalGUIVersion.get() == 0) {
return super.getCacheKey(viewName, locale);
}
final File file = new File(getServletContext().getRealPath(getPrefix() + "/" + LocalGUIVersion.get() + viewName + getSuffix()));
if (LOG.isDebugEnabled()) {
LOG.debug("Checking versioned view in {}", file.getAbsolutePath());
}
if (file.exists()) {
if (LOG.isInfoEnabled()) {
LOG.info("Using versioned view at /{}", LocalGUIVersion.get() + viewName);
}
return super.getCacheKey("/" + LocalGUIVersion.get() + viewName, locale);
} else {
if (LOG.isInfoEnabled()) {
LOG.info("Versioned view not found at /{}", LocalGUIVersion.get() + viewName);
}
}
return super.getCacheKey(viewName, locale);
}
@Override
protected View createView(final String viewName, final Locale locale) throws Exception {
if (LocalGUIVersion.get() == 0) {
if (LOG.isTraceEnabled()) {
LOG.trace("LocalGUIVersion.get() returns 0");
}
return super.createView(viewName, locale);
}
// If this resolver is not supposed to handle the given view,
// return null to pass on to the next resolver in the chain.
if (!canHandle(viewName, locale)) {
return null;
}
// Check for special "redirect:" prefix.
if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
super.createView(viewName, locale);
}
// Check for special "forward:" prefix.
if (viewName.startsWith(FORWARD_URL_PREFIX)) {
super.createView(viewName, locale);
}
// Check for getPrefix() + LocalGUIVersion.get() + viewName +
// getSuffix() file
final File file = new File(getServletContext().getRealPath(getPrefix() + "/" + LocalGUIVersion.get() + viewName + getSuffix()));
if (LOG.isDebugEnabled()) {
LOG.debug("Checking versioned view in {}", file.getAbsolutePath());
}
if (file.exists()) {
if (LOG.isInfoEnabled()) {
LOG.info("Using versioned view at /{}", LocalGUIVersion.get() + viewName);
}
return super.createView("/" + LocalGUIVersion.get() + viewName, locale);
} else {
if (LOG.isInfoEnabled()) {
LOG.info("Versioned view not found at /{}", LocalGUIVersion.get() + viewName);
}
}
// Else fall back to superclass implementation: calling loadView.
return super.createView(viewName, locale);
}
}
......@@ -136,7 +136,7 @@ public class WebConfiguration extends WebMvcConfigurerAdapter {
@Bean
public InternalResourceViewResolver jstlViewResolver() {
final InternalResourceViewResolver resolver = new NewGUIViewResolver();
final InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/jsp");
resolver.setSuffix(".jsp");
resolver.setExposeContextBeansAsAttributes(true);
......
......@@ -27,7 +27,6 @@ import javax.servlet.SessionTrackingMode;
import com.hazelcast.web.SessionListener;
import org.genesys2.server.servlet.filter.LocaleURLFilter;
import org.genesys2.server.servlet.filter.NewGUIFilter;
import org.sitemesh.builder.SiteMeshFilterBuilder;
import org.sitemesh.config.ConfigurableSiteMeshFilter;
import org.sitemesh.webapp.contentfilter.BasicSelector;
......@@ -76,10 +75,10 @@ public class WebInitializer extends AbstractAnnotationConfigDispatcherServletIni
super.onStartup(servletContext);
servletContext.setSessionTrackingModes(EnumSet.of(SessionTrackingMode.COOKIE));
registerFilters(servletContext);
// Hazelcast session listener
servletContext.addListener(new SessionListener());
registerFilters(servletContext);
}
private void registerFilters(final ServletContext servletContext) {
......@@ -125,12 +124,6 @@ public class WebInitializer extends AbstractAnnotationConfigDispatcherServletIni
final FilterRegistration.Dynamic sitemeshFilter = servletContext.addFilter("sitemesh", sitemeshWithErrors);
sitemeshFilter.addMappingForUrlPatterns(null, false, "/*");
// New GUI filter configuration
// TODO Upgrade to @WebFilter
final FilterRegistration.Dynamic newGUIFilter = servletContext.addFilter("newGUIFilter", NewGUIFilter.class);
newGUIFilter.setInitParameter("exclude-paths", "/html /login-attempt");
newGUIFilter.addMappingForUrlPatterns(null, false, "/*");
// GZip filter configuration
final FilterRegistration.Dynamic gZIPFilter = servletContext.addFilter("gZIPFilter", GzipFilter.class);
gZIPFilter.addMappingForUrlPatterns(null, false, "/html/*");
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment