at.molindo.notify.servlet.NotifyFilterBean.java Source code

Java tutorial

Introduction

Here is the source code for at.molindo.notify.servlet.NotifyFilterBean.java

Source

/**
 * 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;
    }

}