Java tutorial
/** * Copyright 2010 Molindo GmbH * * 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 at.molindo.notify.servlet; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; import java.net.URL; import java.net.URLDecoder; import java.net.URLEncoder; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.web.filter.GenericFilterBean; import at.molindo.notify.INotifyService.NotifyException; import at.molindo.notify.channel.IPullChannel; import at.molindo.notify.channel.IPullChannel.PullException; import at.molindo.notify.confirm.IConfirmationService; import at.molindo.notify.confirm.IConfirmationService.ConfirmationException; import at.molindo.notify.model.Confirmation; import at.molindo.notify.model.IChannelPreferences; import at.molindo.notify.model.IParams; import at.molindo.notify.model.IRequestConfigurable; import at.molindo.notify.model.Notification.Type; import at.molindo.notify.model.ParamValue; import at.molindo.notify.model.Params; import at.molindo.utils.data.StringUtils; import at.molindo.utils.io.CharsetUtils; public class NotifyFilterBean extends GenericFilterBean implements INotifyUrlFactory { private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(NotifyFilterBean.class); static final String PARAMTER_BASE_URL_PROPERTY = "baseUrlProperty"; static final String PARAMTER_BASE_URL = "baseUrl"; static final String DEFAULT_MOUNT_PATH = "notify"; static final String PARAMTER_MOUNT_PATH = "mountPath"; static final String DEFAULT_PULL_PREFIX = "pull"; static final String PARAMTER_PULL_PREFIX = "pullPrefix"; static final String DEFAULT_CONFIRM_PREFIX = "confirm"; static final String PARAMTER_CONFIRM_PREFIX = "confirmPrefix"; private String _baseUrl; private String _baseUrlProperty; private String _mountPath; private String _pullPrefix; private Pattern _pullPattern; private String _confirmPrefix; private Pattern _confirmPattern; private final Map<String, IPullChannel> _channels = new HashMap<String, IPullChannel>(); private IConfirmationService _confirmationService; @Override public void initFilterBean() throws ServletException { { if (!StringUtils.empty(_baseUrlProperty)) { String baseUrl = System.getProperty(_baseUrlProperty); if (!StringUtils.empty(baseUrl)) { _baseUrl = baseUrl; } } if (StringUtils.empty(_baseUrl)) { throw new ServletException(String.format("parameter %s is required", PARAMTER_BASE_URL)); } try { _baseUrl = StringUtils.stripTrailing(new URL(_baseUrl).toString(), "/"); } catch (MalformedURLException e) { throw new ServletException( String.format("illegal value for parameter %s: '%s'", PARAMTER_BASE_URL, _baseUrl), e); } } { if (StringUtils.empty(_mountPath)) { _mountPath = DEFAULT_MOUNT_PATH; } _mountPath = StringUtils.stripTrailing(StringUtils.startWith(_mountPath, "/"), "/"); } { if (StringUtils.empty(_pullPrefix)) { _pullPrefix = DEFAULT_PULL_PREFIX; } _pullPrefix = StringUtils.endWith(StringUtils.startWith(_pullPrefix, "/"), "/"); _pullPattern = Pattern.compile("^" + Pattern.quote(_pullPrefix) + "([^/?]+)/([^/?]+).*$"); } { if (StringUtils.empty(_confirmPrefix)) { _confirmPrefix = DEFAULT_CONFIRM_PREFIX; } _confirmPrefix = StringUtils.endWith(StringUtils.startWith(_confirmPrefix, "/"), "/"); _confirmPattern = Pattern.compile("^" + Pattern.quote(_confirmPrefix) + "([^/?]+)/?$"); } } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain); } public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { String path = request.getRequestURI(); if (StringUtils.empty(path) || !path.startsWith(_mountPath)) { response.sendError(404); return; } path = path.substring(_mountPath.length()); if (path.startsWith(_pullPrefix)) { pull(request, response, path); } else if (path.startsWith(_confirmPrefix)) { if (!confirm(request, response, path)) { chain.doFilter(request, response); } } else { chain.doFilter(request, response); } } protected void pull(HttpServletRequest request, HttpServletResponse response, String path) throws IOException { Matcher m = _pullPattern.matcher(path); if (!m.matches()) { response.sendError(404); return; } String channelId = decode(m.group(1)); String userId = decode(m.group(2)); if (StringUtils.empty(userId) || StringUtils.empty(channelId)) { response.sendError(404); return; } IPullChannel channel = getChannel(channelId); if (channel == null) { response.sendError(404); return; } IChannelPreferences prefs = channel.newDefaultPreferences(); if (prefs instanceof IRequestConfigurable) { try { Map<?, ?> queryParams = request.getParameterMap(); for (Map.Entry<?, ?> e : queryParams.entrySet()) { Object value = e.getValue(); if (value != null && value.getClass().isArray()) { String[] vals = (String[]) value; value = vals.length > 0 ? vals[0] : null; } ((IRequestConfigurable) prefs).setParam((String) e.getKey(), (String) value); } } catch (NotifyException e) { response.sendError(404); return; } } if (channel.getNotificationTypes().contains(Type.PRIVATE) && !channel.isAuthorized(userId, prefs)) { response.sendError(403); } else if (channel.isConfigured(new Params(prefs.getParams()))) { try { String output = channel.pull(userId, prefs); if (StringUtils.empty(output)) { response.sendError(404); } else { response.getWriter().write(output); } } catch (PullException e) { log.info("failed to pull notifications", e); response.sendError(500); } } else { response.sendError(404); } } protected boolean confirm(HttpServletRequest request, HttpServletResponse response, String path) throws IOException, ServletException { Matcher m = _confirmPattern.matcher(path); if (!m.matches()) { return false; } String key = decode(m.group(1)); String confirmPath; try { confirmPath = _confirmationService.confirm(key); if (confirmPath != null) { response.sendRedirect(confirmPath); } else { // handled, but not a valid confirmation response.sendError(404); } return true; } catch (ConfirmationException e) { // not handled by confirmation service, follow filter chain return false; } } private String decode(String string) { try { return URLDecoder.decode(string, CharsetUtils.UTF_8.name()); } catch (UnsupportedEncodingException e) { throw new RuntimeException("UTF-8 not supported?", e); } } private String encode(String string) { try { return URLEncoder.encode(string, CharsetUtils.UTF_8.name()); } catch (UnsupportedEncodingException e) { throw new RuntimeException("UTF-8 not supported?", e); } } private IPullChannel getChannel(String channelId) { return _channels.get(channelId); } @Override public String toPullPath(String channelId, String userId, IParams params) { IPullChannel channel = getChannel(channelId); if (channel == null) { return null; } IChannelPreferences cPrefs = channel.newDefaultPreferences(); IParams fullParams = new Params().setAll(cPrefs.getParams()).setAll(params); StringBuilder buf = new StringBuilder(_baseUrl); buf.append(_mountPath).append(_pullPrefix).append(channelId).append("/").append(userId); buf.append("?"); for (final ParamValue pv : fullParams) { final String encodedName = encode(pv.getName()); final String value = pv.getStringValue(); final String encodedValue = value != null ? encode(value) : ""; buf.append(encodedName); buf.append("="); buf.append(encodedValue); buf.append("&"); } buf.setLength(buf.length() - 1); return buf.toString(); } @Override public String toConfirmPath(Confirmation confirmation) { return _baseUrl + _mountPath + _confirmPrefix + confirmation.getKey(); } public String getBaseUrl() { return _baseUrl; } public void setBaseUrl(String baseUrl) { _baseUrl = baseUrl; } public String getBaseUrlProperty() { return _baseUrlProperty; } public void setBaseUrlProperty(String baseUrlProperty) { _baseUrlProperty = baseUrlProperty; } public String getMountPath() { return _mountPath; } public void setMountPath(String mountPath) { _mountPath = mountPath; } public String getPullPrefix() { return _pullPrefix; } public void setPullPrefix(String pullPrefix) { _pullPrefix = pullPrefix; } public String getConfirmPrefix() { return _confirmPrefix; } public void setConfirmPrefix(String confirmPrefix) { _confirmPrefix = confirmPrefix; } public void setChannels(Set<IPullChannel> channels) { _channels.clear(); for (IPullChannel channel : channels) { _channels.put(channel.getId(), channel); } } public void setConfirmationService(IConfirmationService confirmationService) { _confirmationService = confirmationService; } }