package aop.example;

import aop.example.model.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.IOException;

/**
 * Авторизационный аспект
 * @author Zubairov Renat
 */
public aspect AuthorizationAspect {
	
	/**
	 * pointcut включающий в себя метод с ServletRequest для
	 * того что бы потом можно было бы получить его в объединении
	 * с другим pointcut (паттерн "червоточина")  
	 */
	pointcut requestMethod(ServletRequest request) :
		execution(* aop.example.EntranceFilter.doFilter(ServletRequest, ServletResponse, FilterChain)) 
		&& args(request, ServletResponse, FilterChain);

	/**
	 * pointcut определяющий метод фильтра в котором мы будем отлавливать
	 * исключение
	 */
	public pointcut doFilterMethod(ServletRequest srequest, ServletResponse sresponse, EntranceFilter filter) :
		execution(void aop.example.EntranceFilter.doFilter(ServletRequest, ServletResponse, FilterChain))
			&& args(srequest, sresponse, FilterChain) && this(filter);
	
	
	// все методы производящие чтение информации объектов модели
	pointcut readMethods(Object object) : execution (public * aop.example.model.*.get*(..)) && this(object);

	// все методы производящие добавление объектов модели
	pointcut addMethods(Object object) : execution (public * aop.example.model.*.add*(..)) && this(object);
	
	// все методы производящие удаление объектов модели
	pointcut deleteMethods(Album album) : execution (public * aop.example.model.AlbumList.deleteAlbum(Album)) && args(album);
	
	// методы проверки на доступность чтения (пред-проверка)
	pointcut controlledRead(Object object) : execution(public boolean aop.example.model.Controlled+.isReadable()) && this(object);
	
	// методы проверки на доступность добавления (пред-проверка)
	pointcut controlledAdd(Object object) : execution(public boolean aop.example.model.Controlled+.isAddable()) && this(object);

	// методы проверки на доступность удаления (пред-проверка)
	pointcut controlledDelete(Object object) : execution(public boolean aop.example.model.Controlled+.isDeletable()) && this(object);
	
	// Вызов методов чтения произошедшие в потоке выполнения
	// следующим за вызовом метода фильтра
	// мы объеденили два pointcut - реализация паттерна "червоточина" 
	pointcut readAccess(ServletRequest request, Object object) :
		cflow(requestMethod(request)) && readMethods(object);

	// то же самое только для добавления
	pointcut addAccess(ServletRequest request, Object object) :
		cflow(requestMethod(request)) && addMethods(object);

	// то же самое только для удаления
	pointcut deleteAccess(ServletRequest request, Album album) :
		cflow(requestMethod(request)) && deleteMethods(album);
		
	// пред-проверка на чтение
	pointcut readCheck(ServletRequest request, Object object) :
		cflow(requestMethod(request)) && controlledRead(object);

	// пред-проверка на добавление
	pointcut addCheck(ServletRequest request, Object object) :
		cflow(requestMethod(request)) && controlledAdd(object);

	// пред-проверка на удаление
	pointcut deleteCheck(ServletRequest request, Object object) :
		cflow(requestMethod(request)) && controlledDelete(object);

	/**
	 * Around advice отлавливающий исключение
	 * и отправляющий запрос на страницу с ошибкой
	 */
	void around(ServletRequest srequest, ServletResponse sresponse, EntranceFilter filter) throws IOException, ServletException : doFilterMethod(srequest, sresponse, filter) {
		try {
			// выполняем метод фильтра
			proceed(srequest, sresponse, filter);
		} catch (AuthorizationException e) {
			// ловим исключение
			srequest.setAttribute("error_message", e.getMessage());
			// вперёд на страницу с сообщением об ошибке
			filter.getConfig().getServletContext().getRequestDispatcher("error.vm").forward(srequest, sresponse);
		}
	}

		
	/**
	 * Before advice проверки на чтение
	 */
	before(ServletRequest request, Object object) : readAccess(request, object) {
		if (!AuthHelper.isAbleToRead(extractUser(request), object)) {
			throw new AuthorizationException("Read access not allowed");
		}
	}
	
	/**
	 * Before advice проверки на добавление
	 */
	before(ServletRequest request, Object object) : addAccess(request, object) {
		if (!AuthHelper.isAbleToAdd(extractUser(request), object)) {
			throw new AuthorizationException("Add access not allowed");
		}
	}

	/**
	 * Before advice проверки на удаление
	 */
	before(ServletRequest request, Album album) : deleteAccess(request, album) {
		if (!AuthHelper.isAbleToDelete(extractUser(request), album)) {
			throw new AuthorizationException("Delete access not allowed");
		}
	}
	
	/**
	 * Around advice пред-проверки, здесь мы игнорируем возвращаемое методом
	 * значение, и всё время возвращаем то которое удовлетворяет правилам
	 * авторизации
	 * Мы не обрабатываем остальные пред-проверки т.к. по умолчанию любой может
	 * читать, и все аутентифицированные пользователи могут добавлять
	 */
	boolean around(ServletRequest request, Object object) : deleteCheck(request, object) {
		return AuthHelper.isAbleToDelete(extractUser(request), object); 
	}
	
	/**
	 * Приватная фунция которая вынимает пользователя из запроса
	 */
	private AnonymousUser extractUser(ServletRequest request) {
		return (AnonymousUser)((HttpServletRequest) request).getSession().getAttribute(EntranceFilter.USER_KEY);
	}

}
