org.xwiki.contrib.ratelimiter.script.RateLimiterScriptService.java Source code

Java tutorial

Introduction

Here is the source code for org.xwiki.contrib.ratelimiter.script.RateLimiterScriptService.java

Source

/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.xwiki.contrib.ratelimiter.script;

import java.util.Locale;
import java.util.concurrent.TimeUnit;

import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Provider;

import org.joda.time.Period;
import org.joda.time.format.PeriodFormat;
import org.joda.time.format.PeriodFormatter;
import org.xwiki.component.annotation.Component;
import org.xwiki.contrib.ratelimiter.RateLimiter;
import org.xwiki.contrib.ratelimiter.RateLimiterBuilder;
import org.xwiki.contrib.ratelimiter.RateLimiterService;
import org.xwiki.contrib.ratelimiter.RateLimiterServiceFactory;
import org.xwiki.contrib.ratelimiter.internal.RateLimiterServiceActionListener;
import org.xwiki.contrib.ratelimiter.internal.RateLimiterServiceLogger;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.observation.EventListener;
import org.xwiki.observation.ObservationManager;
import org.xwiki.script.service.ScriptService;
import org.xwiki.security.authorization.ContextualAuthorizationManager;
import org.xwiki.security.authorization.Right;
import org.xwiki.velocity.VelocityManager;

import com.xpn.xwiki.XWikiContext;
import com.xpn.xwiki.web.XWikiRequest;

/**
 * Script service for creating and using {@link RateLimiterService}.
 *
 * @version $Id$
 */
@Component
@Named("ratelimiter")
public class RateLimiterScriptService implements ScriptService {
    @Inject
    private Provider<RateLimiterBuilder> builderProvider;

    @Inject
    private RateLimiterServiceFactory factory;

    @Inject
    private RateLimiterServiceLogger rateLimiterServiceLogger;

    @Inject
    private ContextualAuthorizationManager contextualAuthorizationManager;

    @Inject
    private Provider<XWikiContext> contextProvider;

    @Inject
    private ObservationManager observationManager;

    @Inject
    private VelocityManager velocityManager;

    private Object getCurrentConsumed() {
        return contextProvider.get().getDoc().getDocumentReference().getWikiReference();
    }

    private static String getRemoteAddress(XWikiRequest request) {
        String ipAddress = request.getHeader("X-FORWARDED-FOR");
        return (ipAddress != null) ? ipAddress.split(",", 1)[0] : request.getRemoteAddr();
    }

    private Object getCurrentConsumer() {
        DocumentReference user = contextProvider.get().getUserReference();
        return user == null ? getRemoteAddress(contextProvider.get().getRequest()) : user;
    }

    /**
     * @return a new {@link RateLimiterBuilder} ready for building a new rate limiter template.
     */
    public RateLimiterBuilder getBuilder() {
        return builderProvider.get();
    }

    /**
     * Create a new {@link RateLimiterService} based on the provided template.
     * Programming rights is required to use this method.
     *
     * @param limiterTemplate a rate limiter to be used as a template for creating limiter of this service.
     * @return a new {@link RateLimiterService} ready for use.
     */
    public RateLimiterService createRateLimiterService(RateLimiter limiterTemplate) {
        if (contextualAuthorizationManager.hasAccess(Right.PROGRAM)) {
            return factory.create(limiterTemplate);
        }
        return null;
    }

    /**
     * Initialize or reinitialize the action rate limiting service with the given template. If the provided template
     * is null, the service is deactivated.
     *
     * @param limiterTemplate a rate limiter to be used as a template for creating limiter of the default service.
     */
    public void setActionRateLimiter(RateLimiter limiterTemplate) {
        if (contextualAuthorizationManager.hasAccess(Right.PROGRAM)) {
            EventListener listener = observationManager.getListener(RateLimiterServiceActionListener.NAME);
            if (listener != null) {
                if (listener instanceof RateLimiterServiceActionListener) {
                    ((RateLimiterServiceActionListener) listener).getService().clearCache();
                }
                observationManager.removeListener(RateLimiterServiceActionListener.NAME);
            }

            if (limiterTemplate != null) {
                observationManager.addListener(new RateLimiterServiceActionListener(factory.create(limiterTemplate),
                        velocityManager, contextualAuthorizationManager));
            }
        }
    }

    /**
     * @return the current rate limiter service.
     */
    public RateLimiterService getService() {
        EventListener listener = observationManager.getListener(RateLimiterServiceActionListener.NAME);
        if (listener != null) {
            return ((RateLimiterServiceActionListener) listener).getService();
        }
        return null;
    }

    /**
     * Consume as the current user on the current wiki, limiters are created on the fly as needed, and events are
     * sent when exhaustion happen.
     *
     * @param amount the amount being consumed.
     * @return true if the consumption was successful.
     */
    public boolean consume(long amount) {
        RateLimiterService service = getService();
        return service == null || service.consume(getCurrentConsumer(), getCurrentConsumed(), amount);
    }

    /**
     * Consume as the current user on the current wiki, limiters are created on the fly as needed, and events are
     * sent when exhaustion happen.
     *
     * @param consumer the entity consuming.
     * @param consumed the entity being consumed.
     * @param amount the amount being consumed.
     * @return true if the consumption was successful.
     */
    public boolean consume(Object consumer, Object consumed, long amount) {
        if (contextualAuthorizationManager.hasAccess(Right.PROGRAM)) {
            RateLimiterService service = getService();
            return service == null || service.consume(consumer, consumed, amount);
        }
        return true;
    }

    /**
     * @return the current consumable amount for the current user on the current wiki.
     */
    public long getAvailableAmount() {
        RateLimiterService service = getService();
        return service == null ? Long.MAX_VALUE
                : service.getRateLimiter(getCurrentConsumer(), getCurrentConsumed()).getAvailableAmount();
    }

    /**
     * @param consumer the entity consuming.
     * @param consumed the entity being consumed.
     * @return the current consumable amount for the current user on the current wiki.
     */
    public long getAvailableAmount(Object consumer, Object consumed) {
        if (contextualAuthorizationManager.hasAccess(Right.PROGRAM)) {
            RateLimiterService service = getService();
            return service == null ? Long.MAX_VALUE
                    : service.getRateLimiter(consumer, consumed).getAvailableAmount();
        }
        return Long.MAX_VALUE;
    }

    /**
     * Return in seconds the time to wait before a given amount can be consumed by the current user on the current wiki.
     *
     * @param amount the amount that should be consumable.
     * @param unit the {@link TimeUnit} to use for reporting the return value.
     * @return the time to wait or 0 if the amount is consumable now.
     */
    public long getWaitingTime(long amount, TimeUnit unit) {
        RateLimiterService service = getService();
        return service == null ? 0
                : service.getRateLimiter(getCurrentConsumer(), getCurrentConsumed()).getWaitingTime(amount, unit);
    }

    /**
     * Return in seconds the time to wait before a given amount can be consumed by given user on a given resource.
     *
     * @param consumer the entity consuming.
     * @param consumed the entity being consumed.
     * @param amount the amount that should be consumable.
     * @param unit the {@link TimeUnit} to use for reporting the return value.
     * @return the time to wait or 0 if the amount is consumable now.
     */
    public long getWaitingTime(Object consumer, Object consumed, long amount, TimeUnit unit) {
        if (contextualAuthorizationManager.hasAccess(Right.PROGRAM)) {
            RateLimiterService service = getService();
            return service == null ? 0 : service.getRateLimiter(consumer, consumed).getWaitingTime(amount, unit);
        }
        return 0;
    }

    /**
     * Return as a human readable formatted string the time to wait before a given amount can be consumed by the
     * current user on the current wiki.
     *
     * @param amount the amount that should be consumable.
     * @param locale the locale used to format the result.
     * @return the time to wait as a formatted string.
     */
    public String getFormattedWaitingTime(long amount, Locale locale) {
        Period period = new Period(getWaitingTime(amount, TimeUnit.MILLISECONDS));
        PeriodFormatter formatter = PeriodFormat.wordBased(locale);
        return formatter.print(period);
    }

    /**
     * Return as a human readable formatted string the time to wait before a given amount can be consumed by the
     * current user on the current wiki.
     *
     * @param consumer the entity consuming.
     * @param consumed the entity being consumed.
     * @param amount the amount that should be consumable.
     * @param locale the locale used to format the result.
     * @return the time to wait as a formatted string.
     */
    public String getFormattedWaitingTime(Object consumer, Object consumed, long amount, Locale locale) {
        if (contextualAuthorizationManager.hasAccess(Right.PROGRAM)) {
            Period period = new Period(getWaitingTime(consumer, consumed, amount, TimeUnit.MILLISECONDS));
            PeriodFormatter formatter = PeriodFormat.wordBased(locale);
            return formatter.print(period);
        }
        return null;
    }

    /**
     * Retrieve the rate limiter currently applied.
     * Please note that consuming on that limiter is not a good idea since it will not trigger event, and
     * might not be persisted (in case you received a NOLIMIT instance), see {@link #consume(long)}.
     * Programming right is required.
     *
     * @return the limiter or {@link RateLimiter#NOLIMIT} if none is available for the given entity.
     */
    public RateLimiter getRateLimiter() {
        RateLimiterService service = getService();
        if (contextualAuthorizationManager.hasAccess(Right.PROGRAM)) {
            if (service == null) {
                return RateLimiter.NOLIMIT;
            }
            return service.getRateLimiter(getCurrentConsumer(), getCurrentConsumed());
        }
        return null;
    }

    /**
     * @return the filename of the rate limiter log.
     */
    public String getLogFileName() {
        return rateLimiterServiceLogger.getLogFileName();
    }
}