/* This file is part of Cyclos (www.cyclos.org). A project of the Social Trade Organisation (www.socialtrade.org). Cyclos is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Cyclos 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 General Public License for more details. You should have received a copy of the GNU General Public License along with Cyclos; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package nl.strohalm.cyclos.utils; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import nl.strohalm.cyclos.controls.ActionContext; import nl.strohalm.cyclos.entities.Entity; import nl.strohalm.cyclos.entities.members.Element; import nl.strohalm.cyclos.entities.members.Operator; import nl.strohalm.cyclos.entities.settings.LocalSettings; import nl.strohalm.cyclos.services.alerts.ErrorLogService; import nl.strohalm.cyclos.services.settings.SettingsService; import nl.strohalm.cyclos.services.transactions.exceptions.CreditsException; import nl.strohalm.cyclos.services.transactions.exceptions.MaxAmountPerDayExceededException; import nl.strohalm.cyclos.services.transactions.exceptions.NotEnoughCreditsException; import nl.strohalm.cyclos.services.transactions.exceptions.TransferMinimumPaymentException; import nl.strohalm.cyclos.services.transactions.exceptions.UpperCreditLimitReachedException; import nl.strohalm.cyclos.utils.transaction.CurrentTransactionData; import nl.strohalm.cyclos.utils.validation.ValidationError; import nl.strohalm.cyclos.utils.validation.ValidationException; import org.apache.commons.collections.MapUtils; import org.apache.commons.lang.StringUtils; import org.apache.struts.action.ActionForward; import org.apache.struts.action.ActionMapping; /** * Contains helper methods for struts actions * @author luis */ public final class ActionHelper { /** * Interface for implementations that extracts the by element from an entity. * @author ameyer * */ public interface ByElementExtractor { Element getByElement(Entity entity); } /** * Returns an action forward to go back to the previous action */ public static ActionForward back(final ActionMapping actionMapping) { return actionMapping.findForward("back"); } /** * Extracts the elements from the entities and returns a map that might contain two items: the by element and the type of element. When the * extracted element denotes a system task or an Administrator and the logged user is not an Administrator, only the type of element will appear * in the corresponding map. */ @SuppressWarnings({ "unchecked", "rawtypes" }) public static Collection<Map<String, Object>> getByElements(final ActionContext context, final Collection<? extends Entity> entities, final ByElementExtractor extractor) { if (entities.isEmpty()) { return Collections.EMPTY_LIST; } final Collection<Map<String, Object>> byCollection = new ArrayList<Map<String, Object>>(); for (final Entity entity : entities) { final Element by = extractor.getByElement(entity); final Map map = new HashMap<String, Object>(); if (by == null) { map.put("byType", "SystemTask"); } else if (by.getNature() == Element.Nature.ADMIN) { if (context.isAdmin()) { map.put("by", by); map.put("byType", "Admin"); } else { map.put("byType", "System"); } } else if ((by.getNature() == Element.Nature.OPERATOR) && (context.isMemberOf(EntityHelper.reference(Operator.class, by.getId())) || context.getElement().equals(by))) { map.put("by", by); map.put("byType", "Operator"); } else { map.put("by", by.getAccountOwner()); map.put("byType", "Member"); } byCollection.add(map); } return byCollection; } /** * It returns the request's parameters map and in case of the error action it adds as a parameter the errorKey request's attribute.<br> * This is necessary because the {@link #sendError(ActionMapping, HttpServletRequest, HttpServletResponse, String, Object...)} method sets the * errorKey as an attribute and send a redirect (to the error action) to the client * @see sendError(ActionMapping, HttpServletRequest, HttpServletResponse, String, Object...) */ public static Map<String, String[]> getParameterMap(final HttpServletRequest request) { final String uri = request.getRequestURI(); @SuppressWarnings("unchecked") final Map<String, String[]> clientParameterMap = request.getParameterMap(); // in case of the error action we must retrieve the error key as an attribute's request and not as a parameter if (uri.endsWith("/error")) { final HttpSession session = request.getSession(false); if (session != null) { final Map<String, String[]> map = new HashMap<String, String[]>(); map.putAll(clientParameterMap); map.put("errorKey", new String[] { (String) request.getAttribute("errorKey") }); return map; } } return clientParameterMap; } public static ActionForward handleValidationException(final ActionMapping actionMapping, final HttpServletRequest request, final HttpServletResponse response, final ValidationException e) { if (e == null) { return null; } String key = "error.validation"; List<Object> args = Collections.emptyList(); if (!e.getGeneralErrors().isEmpty()) { final ValidationError error = e.getGeneralErrors().iterator().next(); key = error.getKey(); args = error.getArguments(); } else if (!e.getErrorsByProperty().isEmpty()) { final Entry<String, Collection<ValidationError>> entry = e.getErrorsByProperty().entrySet().iterator().next(); final Collection<ValidationError> errors = entry.getValue(); if (!errors.isEmpty()) { // We must show the validation error in a friendly way final String propertyName = entry.getKey(); final ValidationError error = errors.iterator().next(); key = error.getKey(); args = new ArrayList<Object>(); // First, check if there's a fixed display name for the property... String propertyLabel = e.getPropertyDisplayName(propertyName); if (StringUtils.isEmpty(propertyLabel)) { // ... it doesn't. Check if there's a message key... final String propertyKey = e.getPropertyKey(propertyName); if (StringUtils.isNotEmpty(propertyKey)) { // ... the key is set! Get the property label from the message bundle. final MessageHelper messageHelper = SpringHelper.bean(request.getSession().getServletContext(), MessageHelper.class); propertyLabel = messageHelper.message(e.getPropertyKey(propertyName)); } else { // ... we're out of luck! There's no property key. Use the raw property name as label, which is ugly! propertyLabel = propertyName; } } // The first message argument is always the property label args.add(propertyLabel); if (error.getArguments() != null) { // If there are more, add them as well. args.addAll(error.getArguments()); } } } // With the key and arguments, we can show a friendly message to the user return sendError(actionMapping, request, response, key, args.toArray()); } /** * Return a redirect for the ActionForward with the specified parameter */ public static ActionForward redirectWithParam(final HttpServletRequest request, final ActionForward forward, final String name, final Object value) { return redirectWithParams(request, forward, Collections.singletonMap(name, value)); } /** * Return a redirect for the ActionForward with the specified parameters */ public static ActionForward redirectWithParams(final HttpServletRequest request, ActionForward forward, final Map<String, Object> params) { if (forward == null) { return null; } final LocalSettings settings = SpringHelper.bean(request.getSession().getServletContext(), SettingsService.class).getLocalSettings(); forward = new ActionForward(forward); final StringBuilder path = new StringBuilder(); path.append(forward.getPath()); if (MapUtils.isNotEmpty(params)) { path.append('?'); for (final Entry<String, Object> entry : params.entrySet()) { final Object value = entry.getValue(); try { path.append(entry.getKey()).append('=').append(URLEncoder.encode(value == null ? "" : value.toString(), settings.getCharset())); } catch (final UnsupportedEncodingException e) { } path.append('&'); } if (path.charAt(path.length() - 1) == '&') { path.setLength(path.length() - 1); } forward.setPath(path.toString()); } forward.setRedirect(true); return forward; } /** * Sends an error message to the error page via a translation key * @return The ActionForward to the error page */ public static ActionForward sendError(final ActionMapping actionMapping, final HttpServletRequest request, final HttpServletResponse response, final String key, final Object... arguments) { final HttpSession session = request.getSession(); session.setAttribute("errorKey", key); session.setAttribute("errorArguments", arguments); return actionMapping.findForward("error"); } /** * Sends a direct error message to the error page * @return The ActionForward to the error page */ public static ActionForward sendErrorWithMessage(final ActionMapping actionMapping, final HttpServletRequest request, final HttpServletResponse response, final String message) { final HttpSession session = request.getSession(); session.setAttribute("errorMessage", message); return actionMapping.findForward("error"); } /** * Sends a message to the next page */ public static void sendMessage(final HttpServletRequest request, final HttpServletResponse response, final String key, final Object... arguments) { final HttpSession session = request.getSession(); session.setAttribute("messageKey", key); session.setAttribute("messageArguments", arguments); response.addCookie(new Cookie("showMessage", "true")); } /** * Throws an exception compatible to Servlet's exceptions */ public static void throwException(final Throwable th) throws IOException, ServletException { if (th instanceof RuntimeException) { throw (RuntimeException) th; } else if (th instanceof ServletException) { throw (ServletException) th; } else if (th instanceof IOException) { throw (IOException) th; } else { throw new ServletException(th); } } private ErrorLogService errorLogService; private SettingsService settingsService; /** * Generate an error log for the given exception, on a separate transaction */ public void generateLog(final HttpServletRequest request, final ServletContext servletContext, final Throwable error) { CurrentTransactionData.setError(error); // Create a defensive copy of the parameters map @SuppressWarnings("unchecked") final Map<String, Object> parameters = new HashMap<String, Object>(request.getParameterMap()); errorLogService.insert(error, request.getRequestURI(), parameters); } /** * Returns an ActionForward that corresponds to the given path and nature. */ public ActionForward getForwardFor(final Element.Nature nature, final String actionName, final boolean redirect) { final ActionForward actionForward = new ActionForward("/do/" + nature.toString().toLowerCase() + "/" + actionName); actionForward.setRedirect(redirect); return actionForward; } /** * Given a credits exception, resolve it's error key */ public String resolveErrorKey(final CreditsException exception) { if (exception instanceof MaxAmountPerDayExceededException) { final MaxAmountPerDayExceededException e = (MaxAmountPerDayExceededException) exception; final Calendar date = e.getDate(); if (date == null || DateHelper.sameDay(date, Calendar.getInstance())) { return "payment.error.maxAmountOnDayExceeded"; } else { return "payment.error.maxAmountOnDayExceeded.at"; } } else if (exception instanceof NotEnoughCreditsException) { if (((NotEnoughCreditsException) exception).isOriginalAccount()) { return "payment.error.enoughCredits"; } else { return "payment.error.enoughCreditsOtherAccount"; } } else if (exception instanceof TransferMinimumPaymentException) { return "payment.error.transferMinimum"; } else if (exception instanceof UpperCreditLimitReachedException) { return "payment.error.upperCreditLimit"; } else { return "error.general"; } } public Object[] resolveParameters(final CreditsException exception) { if (exception instanceof MaxAmountPerDayExceededException) { final MaxAmountPerDayExceededException e = (MaxAmountPerDayExceededException) exception; return new Object[] { e.getTransferType().getName(), settingsService.getLocalSettings().getRawDateConverter().toString(e.getDate()) }; } else if (exception instanceof NotEnoughCreditsException) { // TODO: This may cause lazy init exception return new Object[] { exception.getAccount().getType().getName() }; } else if (exception instanceof TransferMinimumPaymentException) { final TransferMinimumPaymentException e = (TransferMinimumPaymentException) exception; return new Object[] { e.getMinimunPayment() }; } else if (exception instanceof UpperCreditLimitReachedException) { final UpperCreditLimitReachedException e = (UpperCreditLimitReachedException) exception; return new Object[] { exception.getAccount().getType().getName(), e.getUpperLimit() }; } else { return new Object[] {}; } } public void setErrorLogService(final ErrorLogService errorLogService) { this.errorLogService = errorLogService; } public void setSettingsService(final SettingsService settingsService) { this.settingsService = settingsService; } }