Java tutorial
/* * ==================================================================== * Copyright (c) 2005-2012 sventon project. All rights reserved. * * This software is licensed as described in the file LICENSE, which * you should have received as part of this distribution. The terms * are also available at http://www.sventon.org. * If newer versions of this license are posted there, you may use a * newer version instead, at your option. * ==================================================================== */ package org.sventon.web.ctrl.template; import org.springframework.validation.BindException; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.view.RedirectView; import org.sventon.AuthenticationException; import org.sventon.SVNConnection; import org.sventon.SventonException; import org.sventon.appl.RepositoryConfiguration; import org.sventon.cache.CacheGateway; import org.sventon.diff.DiffException; import org.sventon.model.*; import org.sventon.web.UserRepositoryContext; import org.sventon.web.command.BaseCommand; import org.sventon.web.ctrl.AbstractBaseController; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.net.ConnectException; import java.net.NoRouteToHostException; import java.util.HashMap; import java.util.List; import java.util.Map; import static org.springframework.web.bind.ServletRequestUtils.*; /** * Abstract base class for use by controllers wishing to make use of basic * plumbing functionality such as authorization and basic application configuration. * <p/> * This abstract controller is based on the GoF Template pattern, the method to * implement for extending controllers is * <code>{@link #svnHandle(SVNConnection, org.sventon.web.command.BaseCommand, long, UserRepositoryContext, * HttpServletRequest, HttpServletResponse, BindException)}</code>. * <p/> * Workflow for this controller: * <ol> * <li>The controller inspects the application configuration object to see if it's * user id and password have been provided during setup. If credentials are * configured they will be used for authorized repository access, if they do not * exist the controller will try to set up the repository with anonymous access. * If this fails the user will be forwarded to an error page. * <li>The controller configures the <code>SVNRepository</code> object and * calls the extending class' * {@link #svnHandle(SVNConnection, org.sventon.web.command.BaseCommand, long, UserRepositoryContext, * HttpServletRequest, HttpServletResponse, BindException)} * method with the given {@link org.sventon.web.command.BaseCommand} * containing request parameters. * <li>After the call returns, the controller adds additional information to * the the model (see below) and forwards the request to the view returned * together with the model by the * {@link #svnHandle(SVNConnection, org.sventon.web.command.BaseCommand, long, org.sventon.web.UserRepositoryContext, * HttpServletRequest, HttpServletResponse, BindException)} * method. * </ol> * <b>Input arguments</b><br> * Input to this argument is wrapped in a * <code>{@link org.sventon.web.command.BaseCommand}</code> object by the * Spring framework. If the extending controller is configured in the Spring * config file with a validator for the <code>BaseCommand</code> it will be * checked for binding errors. If binding errors were detected an exception * model will be created an control forwarded to an error view respectively. * <b>Exception handling</b> * <dl> * <dt>Authentication exception * <dd>If a SVN authentication exception occurs during the call the request * will be forwarded to the 'authenticationfailure.jsp' page. * <dt>Other SVN exceptions * <dd>Other SVN exceptions are currently forwarded to a generic error handling * page. * </dl> * * @author patrik@sventon.org * @author jesper@sventon.org */ public abstract class AbstractTemplateController extends AbstractBaseController { /** * Gateway class for accessing the caches. */ private CacheGateway cacheGateway; /** * Maximum number of revisions. */ private int maxRevisionsCount; /** * Maximum number of entries per page. */ private int maxEntriesCount; /** * Destination view name. */ private String viewName; /** * Cached available charsets. */ private AvailableCharsets availableCharsets; /** * Mode for searching entries. */ public static final String ENTRIES_SEARCH_MODE = "entries"; /** * Mode for searching log messages. */ public static final String LOGMESSAGES_SEARCH_MODE = "logMessages"; /** * Request parameter controlling charset. */ private static final String CHARSET_REQUEST_PARAMETER = "charset"; /** * Request parameter controlling search mode. */ private static final String SEARCH_MODE_REQUEST_PARAMETER = "searchMode"; /** * Request parameter controlling revision count. */ private static final String REVISION_COUNT_REQUEST_PARAMETER = "revcount"; @Override public final ModelAndView handle(final HttpServletRequest request, final HttpServletResponse response, final Object cmd, final BindException errors) { final BaseCommand command = (BaseCommand) cmd; logger.debug("'command' set to: " + command); // If application config is not ok - redirect to config if (!application.isConfigured()) { logger.debug("sventon not configured, redirecting to '/repos/listconfigs'"); return new ModelAndView(new RedirectView("/repos/listconfigs", true)); } if (command.getName() == null || !application.getRepositoryNames().contains(command.getName())) { logger.debug("RepositoryName [" + command.getName() + "] does not exist, redirecting to '/repos/list'"); return new ModelAndView(new RedirectView("/repos/list", true)); } if (errors.hasErrors()) { return prepareExceptionModelAndView(errors, command, getApplicationModel(command)); } SVNConnection connection = null; try { final RepositoryConfiguration configuration = application.getConfiguration(command.getName()); final UserRepositoryContext repositoryContext = UserRepositoryContext.getContext(request, command.getName()); final boolean showLatestRevInfo = getBooleanParameter(request, "showlatestrevinfo", false); connection = createConnection(configuration, repositoryContext); final Long headRevision = getRepositoryService().getLatestRevision(connection); command.setRevision( getRepositoryService().translateRevision(connection, command.getRevision(), headRevision)); parseAndUpdateSortParameters(command, repositoryContext); parseAndUpdateLatestRevisionsDisplayCount(request, repositoryContext); parseAndUpdateCharsetParameter(request, repositoryContext); parseAndUpdateSearchModeParameter(request, repositoryContext); final ModelAndView modelAndView = svnHandle(connection, command, headRevision, repositoryContext, request, response, errors); // It's ok for svnHandle to return null in cases like GetFileController. if (needModelPopulation(modelAndView)) { final Map<String, Object> model = new HashMap<String, Object>(getApplicationModel(command)); model.put("userRepositoryContext", repositoryContext); model.put("useCache", configuration.isCacheUsed()); model.put("repositoryURL", configuration.getRepositoryDisplayUrl()); model.put("isEntryTrayEnabled", configuration.isEntryTrayEnabled()); model.put("isZipDownloadsAllowed", configuration.isZippedDownloadsAllowed()); model.put("headRevision", headRevision); model.put("isHead", command.getRevisionNumber() == headRevision); if (showLatestRevInfo) { model.put("revisions", getLatestRevisions(command, connection, repositoryContext)); } modelAndView.addAllObjects(model); } return modelAndView; } catch (AuthenticationException ae) { logger.debug(ae.getMessage()); return prepareAuthenticationRequiredView(request, getApplicationModel(command)); } catch (DiffException ex) { logger.warn(ex.getMessage()); errors.reject(null, ex.getMessage()); return prepareExceptionModelAndView(errors, command, getApplicationModel(command)); } catch (Exception ex) { logger.error("Exception", ex); final Throwable cause = ex.getCause(); if (cause instanceof NoRouteToHostException || cause instanceof ConnectException) { errors.reject("error.message.no-route-to-host"); } else { errors.reject(null, ex.getMessage()); } return prepareExceptionModelAndView(errors, command, getApplicationModel(command)); } finally { if (connection != null) { connection.closeSession(); } } } protected Map<String, Object> getApplicationModel(final BaseCommand command) { final Map<String, Object> applicationModel = new HashMap<String, Object>(); applicationModel.put("baseURL", application.getBaseURL()); applicationModel.put("isUpdating", application.isUpdating(command.getName())); applicationModel.put("repositoryNames", application.getRepositoryNames()); applicationModel.put("isEditableConfig", application.isEditableConfig()); applicationModel.put("charsets", availableCharsets.getCharsets()); applicationModel.put("maxRevisionsCount", getMaxRevisionsCount()); applicationModel.put("command", command); return applicationModel; } // If the view is a RedirectView it's model has already been populated private boolean needModelPopulation(ModelAndView modelAndView) { return modelAndView != null && !(modelAndView.getView() instanceof RedirectView); } private List<LogEntry> getLatestRevisions(BaseCommand command, SVNConnection connection, UserRepositoryContext repositoryContext) throws SventonException { logger.debug("Fetching [" + repositoryContext.getLatestRevisionsDisplayCount() + "] latest revisions for display"); return getRepositoryService().getLatestRevisions(connection, command.getName(), repositoryContext.getLatestRevisionsDisplayCount()); } /** * Creates a repository connection. * * @param configuration Configuration * @param userRepositoryContext Context * @return Connection * @throws SventonException if a subversion error occur. */ protected SVNConnection createConnection(final RepositoryConfiguration configuration, final UserRepositoryContext userRepositoryContext) throws SventonException { final SVNConnection connection; final RepositoryName repositoryName = configuration.getName(); final SVNURL svnurl = configuration.getSVNURL(); if (configuration.isAccessControlEnabled()) { connection = connectionFactory.createConnection(repositoryName, svnurl, userRepositoryContext.getCredentials()); } else { connection = connectionFactory.createConnection(repositoryName, svnurl, configuration.getUserCredentials()); } return connection; } /** * Parses the parameter controlling what charset to use. * * @param request The request. * @param userContext The UserContext instance to update. */ private void parseAndUpdateCharsetParameter(final HttpServletRequest request, final UserRepositoryContext userContext) { final String charset = getStringParameter(request, CHARSET_REQUEST_PARAMETER, null); if (charset != null) { userContext.setCharset(charset); } else if (userContext.getCharset() == null) { userContext.setCharset(availableCharsets.getDefaultCharset()); } } /** * Parses the parameter controlling what search mode to use. * * @param request The request. * @param userContext The UserContext instance to update. */ private void parseAndUpdateSearchModeParameter(final HttpServletRequest request, final UserRepositoryContext userContext) { final String searchMode = getStringParameter(request, SEARCH_MODE_REQUEST_PARAMETER, null); if (searchMode != null) { userContext.setSearchMode(searchMode); } else if (userContext.getSearchMode() == null) { userContext.setSearchMode(ENTRIES_SEARCH_MODE); } } /** * Parses the parameter controlling how many revisions should be displayed in the * <i>latest commit info</i> DIV. * * @param request The request. * @param userContext The UserContext instance to update. */ private void parseAndUpdateLatestRevisionsDisplayCount(final HttpServletRequest request, final UserRepositoryContext userContext) { final int latestRevisionsDisplayCount = getIntParameter(request, REVISION_COUNT_REQUEST_PARAMETER, 0); if (latestRevisionsDisplayCount <= getMaxRevisionsCount() && latestRevisionsDisplayCount >= 0) { if (latestRevisionsDisplayCount > 0) { userContext.setLatestRevisionsDisplayCount(latestRevisionsDisplayCount); } } else { throw new IllegalArgumentException("Illegal revision count: " + latestRevisionsDisplayCount); } } /** * Parses sort mode and type parameters from the request instance and * updates the <code>UserContext</code> instance. * * @param command The Command. * @param userContext The UserContext instance to update. */ protected final void parseAndUpdateSortParameters(final BaseCommand command, final UserRepositoryContext userContext) { if (command.getSortType() != null) { userContext.setSortType(command.getSortType()); } else if (userContext.getSortType() == null) { userContext.setSortType(DirEntryComparator.SortType.FULL_NAME); } if (command.getSortMode() != null) { userContext.setSortMode(command.getSortMode()); } else if (userContext.getSortMode() == null) { userContext.setSortMode(DirEntrySorter.SortMode.ASC); } } /** * Prepares the authentication model. This sets up a model and redirect view with * all stuff needed to redirect control to the login page. * * @param request Request. * @param model Pre-populated model. * @return Redirect view for logging in, with original request info stored in * session to enable the authentication control to proceed with * original request once the user is authenticated. */ private ModelAndView prepareAuthenticationRequiredView(final HttpServletRequest request, Map<String, Object> model) { final Map<String, Object> authenticationModel = new HashMap<String, Object>(model); authenticationModel.put("parameters", request.getParameterMap()); authenticationModel.put("action", request.getRequestURL()); logger.debug("Forwarding to 'authenticationRequired' view"); return new ModelAndView("error/authenticationRequired", authenticationModel); } /** * Prepares the exception model and view with basic data * needed to for displaying a useful error message. * * @param exception Bind exception from Spring MVC validation. * @param command Command object. * @param applicationModel Pre-populated model. * @return The packaged model and view. */ @SuppressWarnings("unchecked") final ModelAndView prepareExceptionModelAndView(final BindException exception, final BaseCommand command, final Map<String, Object> applicationModel) { final RepositoryConfiguration repositoryConfiguration = application.getConfiguration(command.getName()); final Map<String, Object> model = exception.getModel(); model.putAll(applicationModel); model.put("repositoryURL", repositoryConfiguration != null ? repositoryConfiguration.getRepositoryDisplayUrl() : ""); logger.debug("Exception model: " + model); return new ModelAndView("goto", model); } /** * Abstract method to be implemented by the controller sub classing this * controller. This is where the actual work takes place. See class * documentation for info on workflow and on how all this works together. * * @param connection Reference to the repository, prepared with authentication * if applicable. * @param command Command (basically request parameters submitted in user * request) * @param headRevision The head revision. * @param userRepositoryContext The user's context instance for this repository. * @param request Servlet request. * @param response Servlet response. * @param exception BindException, could be used by the subclass to add error * messages to the exception. * @return Model and view to render. * @throws Exception Thrown if exception occurs during SVN operations. */ protected abstract ModelAndView svnHandle(final SVNConnection connection, final BaseCommand command, final long headRevision, final UserRepositoryContext userRepositoryContext, final HttpServletRequest request, final HttpServletResponse response, final BindException exception) throws Exception; /** * Sets the cache gateway instance. * * @param cacheGateway The cache gateway instance. */ public final void setCacheGateway(final CacheGateway cacheGateway) { this.cacheGateway = cacheGateway; } /** * Gets the cache instance. * * @return The instance. */ final CacheGateway getCache() { return cacheGateway; } /** * Sets the maximum number of revisions. * * @param maxRevisionsCount Max count. */ public final void setMaxRevisionsCount(final int maxRevisionsCount) { this.maxRevisionsCount = maxRevisionsCount; } /** * Gets the maximum number of revisions a user can choose to display * in the <i>latest commit info</i> DIV. * * @return Count */ final int getMaxRevisionsCount() { return maxRevisionsCount; } /** * Sets the maximum number of entries per page. * * @param maxEntriesCount Max count. */ public final void setMaxEntriesCount(final int maxEntriesCount) { this.maxEntriesCount = maxEntriesCount; } /** * Gets the maximum number of entries per page. * * @return Count */ protected final int getMaxEntriesCount() { return maxEntriesCount; } /** * Sets the available charsets. * * @param availableCharsets Charsets */ public final void setAvailableCharsets(final AvailableCharsets availableCharsets) { this.availableCharsets = availableCharsets; } /** * Sets the destination view name. * * @param viewName View name. */ public void setViewName(final String viewName) { this.viewName = viewName; } /** * Gets the destination view name. * * @return View name. */ public String getViewName() { return viewName; } }