Java tutorial
/* * Copyright 2009-2015 the CodeLibs Project and the Others. * * 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. */ /* * Copyright 2014-2015 the original author or authors. * * 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 org.codelibs.fess.web.base; import java.lang.reflect.Method; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; import java.util.Map; import java.util.TimeZone; import java.util.function.Function; import java.util.stream.Collector; import java.util.stream.Collectors; import javax.annotation.Resource; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.struts.action.ActionMessages; import org.codelibs.fess.entity.LoginInfo; import org.codelibs.fess.lasta.core.direction.FessConfig; import org.codelibs.fess.lasta.web.action.FessJspPath; import org.codelibs.fess.lasta.web.action.FessMessages; import org.codelibs.robot.dbflute.optional.OptionalObject; import org.codelibs.sastruts.core.SSCConstants; import org.dbflute.bhv.proposal.callback.ExecutedSqlCounter; import org.dbflute.bhv.proposal.callback.TraceableSqlAdditionalInfoProvider; import org.dbflute.exception.EntityAlreadyDeletedException; import org.dbflute.exception.EntityAlreadyExistsException; import org.dbflute.exception.EntityAlreadyUpdatedException; import org.dbflute.helper.HandyDate; import org.dbflute.hook.AccessContext; import org.dbflute.hook.CallbackContext; import org.dbflute.hook.SqlFireHook; import org.dbflute.hook.SqlStringFilter; import org.dbflute.saflute.core.exception.ApplicationBaseException; import org.dbflute.saflute.core.exception.ExceptionTranslator; import org.dbflute.saflute.core.magic.ThreadCacheContext; import org.dbflute.saflute.core.time.TimeManager; import org.dbflute.saflute.db.dbflute.accesscontext.AccessContextArranger; import org.dbflute.saflute.db.dbflute.accesscontext.AccessContextResource; import org.dbflute.saflute.db.dbflute.accesscontext.PreparedAccessContext; import org.dbflute.saflute.db.dbflute.callbackcontext.RomanticTraceableSqlFireHook; import org.dbflute.saflute.db.dbflute.callbackcontext.RomanticTraceableSqlStringFilter; import org.dbflute.saflute.web.action.RootAction; import org.dbflute.saflute.web.action.api.ApiManager; import org.dbflute.saflute.web.action.api.ApiResult; import org.dbflute.saflute.web.action.callback.ActionCallback; import org.dbflute.saflute.web.action.callback.ActionExecuteMeta; import org.dbflute.saflute.web.action.exception.ForcedIllegalTransitionApplicationException; import org.dbflute.saflute.web.action.exception.ForcedRequest404NotFoundException; import org.dbflute.saflute.web.action.exception.GetParameterNotFoundException; import org.dbflute.saflute.web.action.exception.MessageKeyApplicationException; import org.dbflute.saflute.web.servlet.request.RequestManager; import org.dbflute.saflute.web.servlet.session.SessionManager; import org.dbflute.saflute.web.servlet.taglib.MyErrorsTag; import org.dbflute.saflute.web.servlet.taglib.MyHtmlMessagesTag; import org.dbflute.util.DfTypeUtil; /** * @author jflute */ public abstract class FessBaseAction extends RootAction implements ActionCallback, FessJspPath { // =================================================================================== // Definition // ========== private static final Log LOG = LogFactory.getLog(FessBaseAction.class); // =================================================================================== // Attribute // ========= @Resource protected FessConfig fessConfig; /** The manager of time. (NotNull) */ @Resource protected TimeManager timeManager; /** The manager of session. (NotNull) */ @Resource protected SessionManager sessionManager; /** The translator of exception. (NotNull) */ @Resource protected ExceptionTranslator exceptionTranslator; /** The manager of API. (NotNull) */ @Resource protected ApiManager apiManager; // =================================================================================== // Login // ======= protected OptionalObject<LoginInfo> getLoginInfo() { return OptionalObject.of(sessionManager.getAttribute(SSCConstants.USER_INFO)); } // =================================================================================== // Message // ======= /** * Create the action messages basically for session global errors or messages. * @return The new-created action messages provided from Struts. (NotNull) */ protected ActionMessages createActionMessages() { // should be overridden as type-safe properties return new ActionMessages(); } // ----------------------------------------------------- // Errors // ------ /** * Save message as (global) action errors. (after deleting existing messages) <br> * This message will be deleted immediately after display if you use {@link MyErrorsTag}. * @param messageKey The message key to be saved. (NotNull) * @param args The varying array of arguments for the message. (NullAllowed, EmptyAllowed) */ protected void saveErrors(String messageKey, Object... args) { sessionManager.saveErrors(messageKey, args); } /** * Save message as (global) action errors. (after deleting existing messages) <br> * This message will be deleted immediately after display if you use {@link MyErrorsTag}. * @param errors The action message for errors. (NotNull, EmptyAllowed: removes existing errors) */ protected void saveErrors(ActionMessages errors) { sessionManager.saveErrors(errors); } /** * Add message as (global) action errors to rear of existing messages. <br> * This message will be deleted immediately after display if you use {@link MyErrorsTag}. * @param messageKey The message key to be added. (NotNull) * @param args The varying array of arguments for the message. (NullAllowed, EmptyAllowed) */ protected void addErrors(String messageKey, Object... args) { sessionManager.addErrors(messageKey, args); } /** * Does it have messages as (global or specified property) action errors at least one? * @return The determination, true or false. */ protected boolean hasErrors() { return sessionManager.hasErrors(); } /** * Get action message from (global) action errors. * @return The object for action message. (NullAllowed: if no errors in session) */ protected ActionMessages getErrors() { return sessionManager.getErrors(); } /** * Clear (global) action errors from session. */ protected void clearErrors() { sessionManager.clearErrors(); } // ----------------------------------------------------- // Messages // -------- /** * Save message as (global) action messages. (after deleting existing messages) <br> * This message will be deleted immediately after display if you use {@link MyHtmlMessagesTag}. * @param messageKey The message key to be saved. (NotNull) * @param args The varying array of arguments for the message. (NullAllowed, EmptyAllowed) */ protected void saveMessages(String messageKey, Object... args) { sessionManager.saveMessages(messageKey, args); } /** * Save message as (global) action messages. (after deleting existing messages) <br> * This message will be deleted immediately after display if you use {@link MyHtmlMessagesTag}. * @param messages The action message for messages. (NotNull, EmptyAllowed: removes existing messages) */ protected void saveMessages(ActionMessages messages) { sessionManager.saveMessages(messages); } /** * Add message as (global) action messages to rear of existing messages. <br> * This message will be deleted immediately after display if you use {@link MyHtmlMessagesTag}. * @param messageKey The message key to be added. (NotNull) * @param args The varying array of arguments for the message. (NullAllowed, EmptyAllowed) */ protected void addMessages(String messageKey, Object... args) { sessionManager.addMessages(messageKey, args); } /** * Does it have messages as (global or specified property) action messages at least one? * @return The determination, true or false. */ protected boolean hasMessages() { return sessionManager.hasMessages(); } /** * Get action message from (global) action errors. * @return The object for action message. (NullAllowed: if no messages in session) */ protected ActionMessages getMessages() { return sessionManager.getMessages(); } /** * Clear (global) action messages from session. */ protected void clearMessages() { sessionManager.clearMessages(); } // =================================================================================== // Callback // ======== // [typical callback process] // read the source code for the details // (because of no comment here) // ----------------------------------------------------- // Before // ------ @Override public final String godHandActionPrologue(final ActionExecuteMeta executeMeta) { // fixed process arrangeThreadCacheContextBasicItem(executeMeta); arrangePreparedAccessContext(executeMeta); arrangeCallbackContext(executeMeta); // should be after access-context (using access context's info) arrangeThreadCacheContextLoginItem(executeMeta); return null; } protected void arrangeThreadCacheContextBasicItem(ActionExecuteMeta executeMeta) { if (ThreadCacheContext.exists()) { // basically true, just in case ThreadCacheContext.registerRequestPath(requestManager.getRoutingOriginRequestPathAndQuery()); ThreadCacheContext.registerEntryMethod(executeMeta.getActionMethod()); } } protected void arrangeThreadCacheContextLoginItem(ActionExecuteMeta executeMeta) { if (ThreadCacheContext.exists()) { // basically true, just in case ThreadCacheContext.registerUserBean(getLoginInfo().orElse(null)); // basically for asynchronous } } @Override public String godHandBefore(ActionExecuteMeta executeMeta) { // you can override return null; } @Override public String callbackBefore(ActionExecuteMeta executeMeta) { // you can override return null; } // ----------------------------------------------------- // on Success // ---------- @Override public String godHandSuccessMonologue(ActionExecuteMeta executeMeta) { return null; } // ----------------------------------------------------- // on Failure // ---------- @Override public String godHandExceptionMonologue(ActionExecuteMeta executeMeta) { // fixed process return handleActionException(executeMeta); } protected String handleActionException(ActionExecuteMeta executeMeta) { final RuntimeException cause = executeMeta.getFailureCause(); RuntimeException translated = null; try { translateException(cause); } catch (RuntimeException e) { translated = e; } final RuntimeException handlingEx = translated != null ? translated : cause; final String nextPath = handleApplicationException(executeMeta, handlingEx); if (nextPath != null) { return nextPath; } if (translated != null) { throw translated; } return null; } protected void translateException(RuntimeException cause) { exceptionTranslator.translateException(cause); } // ----------------------------------------------------- // Finally // ------- @Override public void callbackFinally(ActionExecuteMeta executeMeta) { // you can override } @Override public void godHandFinally(ActionExecuteMeta executeMeta) { // you can override } @Override public final void godHandActionEpilogue(ActionExecuteMeta executeMeta) { // fixed process if (executeMeta.isForwardToJsp()) { arrangeNoCacheResponseWhenJsp(executeMeta); } handleSqlCount(executeMeta); clearCallbackContext(); clearPreparedAccessContext(); } protected void arrangeNoCacheResponseWhenJsp(ActionExecuteMeta executeMeta) { responseManager.addNoCache(); } // ----------------------------------------------------- // Adjustment // ---------- @Override protected String movedPermanently(String redirectUrl) { // e.g. godHandBefore() needs dummy to stop execution super.movedPermanently(redirectUrl); return responseResolved(); } /** * Return as response resolved. <br> * Basically used in action callback or action execute. <br> * You should use this to stop execution in callback. * <pre> * ... // resolve response by other way * return responseResolved(); // stop execution after here * </pre> * @return The dummy value that means resolved. (NotNull) */ protected String responseResolved() { return ActionCallback.RESPONSE_RESOLVED_DUMMY_FORWARD; } // =================================================================================== // Access Context // ============== /** * Arrange prepared access context for DBFlute, which is used for common columns setup. <br> * This is called by callback process so you should NOT call this directly in your action. * @param executeMeta The meta of action execute. (NotNull) */ protected void arrangePreparedAccessContext(ActionExecuteMeta executeMeta) { // called by callback final AccessContextArranger arranger = createAccessContextArranger(); final AccessContextResource resource = createAccessContextResource(executeMeta); final AccessContext accessContext = arranger.arrangePreparedAccessContext(resource); PreparedAccessContext.setAccessContextOnThread(accessContext); } /** * Create the arranger of access context. * @return The instance of arranger. (NotNull) */ protected AccessContextArranger createAccessContextArranger() { return new AccessContextArranger() { public AccessContext arrangePreparedAccessContext(final AccessContextResource resource) { final AccessContext context = new AccessContext(); // uses provider to synchronize it with transaction time context.setAccessLocalDateTimeProvider(() -> { return timeManager.getCurrentLocalDateTime(); }); // uses provider to synchronize it with login status in session context.setAccessUserProvider(() -> { return buildAccessUserTrace(resource); }); return context; } private String buildAccessUserTrace(AccessContextResource resource) { return getLoginInfo().map(info -> info.getUsername()).orElse("system"); } }; } /** * Create the resource of access context. * @param executeMeta The meta of action execute. (NotNull) * @return The new-created resource of access context. (NotNull) */ protected AccessContextResource createAccessContextResource(ActionExecuteMeta executeMeta) { final Method method = executeMeta.getActionMethod(); final String classTitle = DfTypeUtil.toClassTitle(method.getDeclaringClass()); return new AccessContextResource(classTitle, method); } /** * Clear prepared access context. <br> * This is called by callback process so you should NOT call this directly in your action. */ protected void clearPreparedAccessContext() { // called by callback PreparedAccessContext.clearAccessContextOnThread(); } // =================================================================================== // Callback Context // ================ /** * Arrange callback context for DBFlute, which is used for several purpose. <br> * This is called by callback process so you should NOT call this directly in your action. * @param executeMeta The meta of action execute. (NotNull) */ protected void arrangeCallbackContext(final ActionExecuteMeta executeMeta) { final SqlFireHook sqlFireHook = createSqlFireHook(executeMeta); CallbackContext.setSqlFireHookOnThread(sqlFireHook); final SqlStringFilter filter = createSqlStringFilter(executeMeta); CallbackContext.setSqlStringFilterOnThread(filter); } /** * Create the filter of SQL string for DBFlute. * @param executeMeta The meta of action execute. (NotNull) * @return The hook of SQL fire. (NullAllowed: if null, no hook) */ protected SqlFireHook createSqlFireHook(ActionExecuteMeta executeMeta) { return newRomanticTraceableSqlFireHook(); } protected RomanticTraceableSqlFireHook newRomanticTraceableSqlFireHook() { return new RomanticTraceableSqlFireHook(); } /** * Create the filter of SQL string for DBFlute. * @param executeMeta The meta of action execute. (NotNull) * @return The filter of SQL string. (NullAllowed: if null, no filter) */ protected SqlStringFilter createSqlStringFilter(final ActionExecuteMeta executeMeta) { final Method actionMethod = executeMeta.getActionMethod(); return newRomanticTraceableSqlStringFilter(actionMethod, new TraceableSqlAdditionalInfoProvider() { @Override public String provide() { // lazy because it may be auto-login later return buildSqlMarkingAdditionalInfo(); } }); } protected RomanticTraceableSqlStringFilter newRomanticTraceableSqlStringFilter(Method actionMethod, TraceableSqlAdditionalInfoProvider additionalInfoProvider) { return new RomanticTraceableSqlStringFilter(actionMethod, additionalInfoProvider); } /** * Build string for additional info of SQL marking. * @return The string expression of additional info. (NullAllowed: if null, no additional info) */ protected String buildSqlMarkingAdditionalInfo() { return "{" + getLoginInfo().map(info -> String.valueOf(info.isAdministrator())).orElse("*No login") + "}"; // it doesn't contain user ID for SQL cache in DBMS } /** * Handle count of SQL execution in the request. * @param executeMeta The meta of action execute. (NotNull) */ protected void handleSqlCount(final ActionExecuteMeta executeMeta) { final CallbackContext context = CallbackContext.getCallbackContextOnThread(); if (context == null) { return; } final SqlStringFilter filter = context.getSqlStringFilter(); if (filter == null || !(filter instanceof ExecutedSqlCounter)) { return; } final ExecutedSqlCounter counter = ((ExecutedSqlCounter) filter); final int limitCountOfSql = getLimitCountOfSql(executeMeta); if (limitCountOfSql >= 0 && counter.getTotalCountOfSql() > limitCountOfSql) { handleTooManySqlExecution(executeMeta, counter); } final String exp = counter.toLineDisp(); requestManager.setAttribute(RequestManager.KEY_DBFLUTE_SQL_COUNT, exp); // logged by logging filter } /** * Handle too many SQL executions. * @param executeMeta The meta of action execute. (NotNull) * @param sqlCounter The counter object for SQL executions. (NotNull) */ protected void handleTooManySqlExecution(final ActionExecuteMeta executeMeta, final ExecutedSqlCounter sqlCounter) { final String actionDisp = buildActionDisp(executeMeta); LOG.warn("*Too many SQL executions: " + sqlCounter.getTotalCountOfSql() + " in " + actionDisp); } protected String buildActionDisp(ActionExecuteMeta executeMeta) { final Method method = executeMeta.getActionMethod(); final Class<?> declaringClass = method.getDeclaringClass(); return declaringClass.getSimpleName() + "." + method.getName() + "()"; } /** * Get the limit count of SQL execution. <br> * You can override if you need. * @param executeMeta The meta of action execute. (NotNull) * @return The max count allowed for SQL executions. (MinusAllowed: if minus, no check) */ protected int getLimitCountOfSql(ActionExecuteMeta executeMeta) { return 30; // as default } /** * Clear callback context. <br> * This is called by callback process so you should NOT call this directly in your action. */ protected void clearCallbackContext() { CallbackContext.clearSqlStringFilterOnThread(); CallbackContext.clearSqlFireHookOnThread(); } // =================================================================================== // Application Exception // ===================== /** * Handle the application exception thrown by (basically) action execute. <br> * Though this is same as global-exceptions settings of Struts, * There is more flexibility than the function so you can set it here. <br> * This is called by callback process so you should NOT call this directly in your action. * @param executeMeta The meta of action execute. (NotNull) * @param cause The exception thrown by (basically) action execute, might be translated. (NotNull) * @return The forward path. (NullAllowed: if not null, it goes to the path) */ protected String handleApplicationException(ActionExecuteMeta executeMeta, RuntimeException cause) { // called by callback final String forwardTo = doHandleApplicationException(executeMeta, cause); if (forwardTo != null && executeMeta.isApiAction()) { return dispatchApiApplicationException(executeMeta, cause); } return forwardTo; } protected String doHandleApplicationException(ActionExecuteMeta executeMeta, RuntimeException cause) { String forwardTo = null; if (cause instanceof ApplicationBaseException) { final ApplicationBaseException appEx = (ApplicationBaseException) cause; if (appEx instanceof GetParameterNotFoundException) { forwardTo = handleGetParameterNotFoundException((GetParameterNotFoundException) appEx); } else if (appEx instanceof MessageKeyApplicationException) { forwardTo = handleErrorsApplicationException((MessageKeyApplicationException) appEx); } else { forwardTo = handleSpecialApplicationException(appEx); } if (forwardTo == null) { forwardTo = handleUnknownApplicationException(appEx); } reflectEmbeddedApplicationMessagesIfExists(appEx); // override existing messages if exists } else { if (cause instanceof EntityAlreadyDeletedException) { forwardTo = handleEntityAlreadyDeletedException((EntityAlreadyDeletedException) cause); } else if (cause instanceof EntityAlreadyUpdatedException) { forwardTo = handleEntityAlreadyUpdatedException((EntityAlreadyUpdatedException) cause); } else if (cause instanceof EntityAlreadyExistsException) { forwardTo = handleEntityAlreadyExistsException((EntityAlreadyExistsException) cause); } } if (forwardTo != null) { showApplicationExceptionHandling(cause, forwardTo); } return forwardTo; } protected void showApplicationExceptionHandling(RuntimeException cause, String forwardTo) { if (LOG.isDebugEnabled()) { // not show forwardTo because of forwarding log later final StringBuilder sb = new StringBuilder(); sb.append("...Handling application exception:"); sb.append("\n_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/"); sb.append("\n[Application Exception]"); sb.append("\n").append(cause.getMessage()); final ActionMessages errors = getErrors(); if (errors != null) { sb.append("\n").append(errors.toString()); } buildApplicationExceptionStackTrace(cause, sb); sb.append("\n_/_/_/_/_/_/_/_/_/_/"); LOG.debug(sb.toString()); } } protected void buildApplicationExceptionStackTrace(RuntimeException cause, StringBuilder sb) { final StackTraceElement[] stackTrace = cause.getStackTrace(); if (stackTrace == null) { // just in case return; } int index = 0; for (StackTraceElement element : stackTrace) { if (index > 10) { // not all because it's not error break; } final String className = element.getClassName(); final String fileName = element.getFileName(); // might be null final int lineNumber = element.getLineNumber(); final String methodName = element.getMethodName(); sb.append("\n at ").append(className).append(".").append(methodName); sb.append("(").append(fileName); if (lineNumber >= 0) { sb.append(":").append(lineNumber); } sb.append(")"); ++index; } } protected void reflectEmbeddedApplicationMessagesIfExists(ApplicationBaseException appEx) { final String errorsKey = appEx.getErrorKey(); if (errorsKey != null) { if (LOG.isDebugEnabled()) { LOG.debug("...Saving embedded application message as action error: " + errorsKey); } saveErrors(errorsKey, appEx.getErrorArgs()); } } protected String dispatchApiApplicationException(ActionExecuteMeta executeMeta, RuntimeException cause) { final ActionMessages errors = getErrors(); // might be null in minor case final ApiResult result = apiManager.prepareApplicationExceptionResult(errors, executeMeta, cause); apiManager.writeJsonResponse(result); return apiManager.forwardToApiResolvedDummy(); } // ----------------------------------------------------- // DBFlute Entity // -------------- protected String handleEntityAlreadyDeletedException(EntityAlreadyDeletedException cause) { saveErrors(getErrorsAppAlreadyDeletedKey()); return getErrorMessageAlreadyDeletedJsp(); } protected String getErrorsAppAlreadyDeletedKey() { return FessMessages.ERRORS_APP_ALREADY_DELETED; } protected String getErrorMessageAlreadyDeletedJsp() { return getErrorMessageJsp(); // as default } protected String handleEntityAlreadyUpdatedException(EntityAlreadyUpdatedException cause) { saveErrors(getErrorsAppAlreadyUpdatedKey()); return getErrorMessageAlreadyUpdatedJsp(); } protected String getErrorsAppAlreadyUpdatedKey() { return FessMessages.ERRORS_APP_ALREADY_UPDATED; } protected String getErrorMessageAlreadyUpdatedJsp() { return getErrorMessageJsp(); // as default } protected String handleEntityAlreadyExistsException(EntityAlreadyExistsException cause) { saveErrors(getErrorsAppAlreadyExistsKey()); return getErrorMessageAlreadyExistsJsp(); } protected String getErrorsAppAlreadyExistsKey() { return FessMessages.ERRORS_APP_ALREADY_EXISTS; } protected String getErrorMessageAlreadyExistsJsp() { return getErrorMessageJsp(); // as default } // ----------------------------------------------------- // Get Parameter // ------------- protected String handleGetParameterNotFoundException(GetParameterNotFoundException appEx) { saveErrors(getErrorsAppIllegalTransitionKey()); return getErrorMessageJsp(); } protected String getErrorsAppIllegalTransitionKey() { return FessMessages.ERRORS_APP_ILLEGAL_TRANSITION; } protected String getErrorMessageJsp() { return path_Error_ErrorMessageJsp; } // ----------------------------------------------------- // Message Key // ----------- protected String handleErrorsApplicationException(MessageKeyApplicationException appEx) { // no save here because of saved later //saveErrors(appEx.getErrors()); return getErrorMessageJsp(); } // ----------------------------------------------------- // Special // ------- protected String handleSpecialApplicationException(ApplicationBaseException appEx) { // you can override return null; } // ----------------------------------------------------- // Unknown // ------- protected String handleUnknownApplicationException(ApplicationBaseException appEx) { return null; // basically no way } // =================================================================================== // Assert Helper // ============= /** * Assert the condition is true or it throws illegal transition forcedly. <br> * You can use this in your action process against strange request parameters. * @param condition Your determination, true or false. (false: illegal transition) */ protected void assertTrueOrForcedIllegalTransition(boolean condition) { if (!condition) { throw new ForcedIllegalTransitionApplicationException(getErrorsAppIllegalTransitionKey()); } } /** * Assert the condition is true or it throws 404 not found forcedly. <br> * You can use this in your action process against invalid URL parameters. * @param condition Your determination, true or false. (false: 404 not found) */ protected void assertTrueOrForcedRequest404NotFound(boolean condition) { if (!condition) { String msg = "from Forced 404 NotFound assertion"; // debug message throw new ForcedRequest404NotFoundException(msg); } } // =================================================================================== // Conversion Helper // ================= // ----------------------------------------------------- // Collectors // ---------- protected <T> Collector<T, ?, List<T>> toList() { return Collectors.toList(); } protected <T, K, U> Collector<T, ?, Map<K, U>> toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper) { return Collectors.toMap(keyMapper, valueMapper); } // ----------------------------------------------------- // String Date // ----------- protected String toStringDate(LocalDate localDate) { return localDate != null ? doConvertToDisp(localDate) : null; } protected String toStringDate(LocalDateTime localDateTime) { return localDateTime != null ? doConvertToStringDate(localDateTime) : null; } private String doConvertToDisp(LocalDate localDate) { return new HandyDate(localDate, getConversionTimeZone()).toDisp(getStringDatePattern()); } private String doConvertToStringDate(LocalDateTime localDateTime) { return new HandyDate(localDateTime, getConversionTimeZone()).toDisp(getStringDatePattern()); } protected String toStringDateTime(LocalDateTime localDateTime) { return localDateTime != null ? doConvertToStringDateTime(localDateTime) : null; } private String doConvertToStringDateTime(LocalDateTime localDateTime) { return new HandyDate(localDateTime, getConversionTimeZone()).toDisp(getStringDateTimePattern()); } // ----------------------------------------------------- // Local Date // ---------- protected LocalDate toLocalDate(String dateExp) { if (dateExp == null || dateExp.isEmpty()) { return null; } TimeZone userTimeZone = getConversionTimeZone(); return new HandyDate(dateExp, userTimeZone).getLocalDate(); } protected LocalDateTime toLocalDateTime(String dateTimeExp) { if (dateTimeExp == null || dateTimeExp.isEmpty()) { return null; } TimeZone userTimeZone = getConversionTimeZone(); return new HandyDate(dateTimeExp, userTimeZone).getLocalDateTime(); } // ----------------------------------------------------- // Conversion Resource // ------------------- protected String getStringDatePattern() { return "yyyy/MM/dd"; } protected String getStringDateTimePattern() { return "yyyy/MM/dd HH:mm:ss"; } protected TimeZone getConversionTimeZone() { return requestManager.getUserTimeZone(); } }