// Copyright (c) 2003-present, Jodd Team (http://jodd.org) // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // 1. Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // // 2. Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. package jodd.madvoc.component; import jodd.log.Logger; import jodd.log.LoggerFactory; import jodd.madvoc.ActionConfig; import jodd.madvoc.ActionRequest; import jodd.madvoc.MadvocException; import jodd.madvoc.injector.Target; import jodd.madvoc.meta.RenderWith; import jodd.madvoc.result.ActionResult; import jodd.madvoc.result.Result; import jodd.petite.meta.PetiteInject; import jodd.typeconverter.TypeConverterManager; import java.util.HashSet; import java.util.Map; import java.util.HashMap; import java.util.Set; /** * Manager for Madvoc result handlers. */ public class ResultsManager { private static final Logger log = LoggerFactory.getLogger(ResultsManager.class); @PetiteInject protected ContextInjectorComponent contextInjectorComponent; @PetiteInject protected MadvocConfig madvocConfig; public ResultsManager() { this.stringResults = new HashMap<>(); this.allResults = new HashMap<>(); this.typeResults = new HashMap<>(); } // ---------------------------------------------------------------- container protected final Map<String, ActionResult> stringResults; protected final Map<Class, ActionResult> typeResults; protected final Map<Class<? extends ActionResult>, ActionResult> allResults; /** * Returns all action results. */ public Set<ActionResult> getAllActionResults() { Set<ActionResult> set = new HashSet<>(allResults.size()); set.addAll(allResults.values()); return set; } /** * Registers an action result handler and returns created {@link jodd.madvoc.result.ActionResult} if * result with same type doesn't exist. Otherwise, returns existing result and created one will be ignored. */ public ActionResult register(Class<? extends ActionResult> resultClass) { return register(createResult(resultClass)); } /** * Registers new action result instance. If action result of the same class is already * registered, registration will be skipped. If result for the same result type or * same target class exist, it will be replaced! However, default Jodd results will * <i>never</i> replace other results. After the registration, results are initialized. */ protected ActionResult register(ActionResult result) { Class<? extends ActionResult> actionResultClass = result.getClass(); // check existing ActionResult existingResult = allResults.get(actionResultClass); if (existingResult != null) { if (log.isDebugEnabled()) { log.debug("ActionResult already registered: " + actionResultClass); } return existingResult; } // + string hook String resultName = result.getResultName(); if (resultName != null) { existingResult = stringResults.get(resultName); if (existingResult != null) { // the same result name exist if (!resultMayReplaceExistingOne(actionResultClass)) { if (log.isDebugEnabled()) { log.debug("ActionResult already registered: " + actionResultClass); } return existingResult; } // allow only one action result per result type allResults.remove(existingResult.getClass()); } if (log.isInfoEnabled()) { log.debug("ActionResult registered: " + resultName + " -> " + actionResultClass); } stringResults.put(resultName, result); } // + type result Class resultValueType = result.getResultValueType(); if (resultValueType != null && resultValueType != String.class) { existingResult = typeResults.get(resultValueType); if (existingResult != null) { if (!resultMayReplaceExistingOne(actionResultClass)) { if (log.isDebugEnabled()) { log.debug("ActionResult already registered: " + actionResultClass); } return existingResult; } // allow only one action result per result type allResults.remove(existingResult.getClass()); } if (log.isInfoEnabled()) { log.debug("ActionResult registered: " + resultValueType + " -> " + actionResultClass); } typeResults.put(resultValueType, result); } // + all results if (log.isInfoEnabled()) { log.debug("ActionResult registered: " + actionResultClass); } allResults.put(actionResultClass, result); // + init initializeResult(result); return result; } /** * Returns <code>true</code> if action result can replace existing one. * This rule makes sure that Jodd's default results never replace custom * results. This rule is important since result are found on classpath * and can be registered without any order. */ protected boolean resultMayReplaceExistingOne(Class<? extends ActionResult> actionResultClass) { String packageName = actionResultClass.getPackage().getName(); return !packageName.startsWith("jodd."); } // ---------------------------------------------------------------- lookup /** * Lookups for action result and {@link #register(Class) registers} it if missing. */ private ActionResult lookupAndRegisterIfMissing(Class<? extends ActionResult> actionResultClass) { ActionResult actionResult = allResults.get(actionResultClass); if (actionResult == null) { actionResult = register(actionResultClass); } return actionResult; } /** * Lookups for {@link jodd.madvoc.result.ActionResult action result handler} * based on current {@link jodd.madvoc.ActionRequest action request} and action method * result object. Lookup performs the following in given order: * <ul> * <li>if result object is <code>null</code>, check if {@link jodd.madvoc.result.Result} is used</li> * <li>check result definition in <code>@Action</code> annotation</li> * <li>if result is not a <code>String</code>, check if it is annotated with {@link jodd.madvoc.meta.RenderWith} annotation</li> * <li>if result is not a <code>String</code>, find ActionResult for matching result object type</li> * <li>if action result still not found, call <code>toString</code> on result object and parse it</li> * </ul> */ public ActionResult lookup(ActionRequest actionRequest, Object resultObject) { ActionResult actionResult = null; // + special class: result if (resultObject == null) { Result result = actionRequest.getResult(); if (result != null) { // read Result, if used; if not, values will be null Class<? extends ActionResult> actionResultClass = result.getActionResult(); resultObject = result.getResultValue(); if (resultObject == null) { resultObject = result.value(); } if (actionResultClass != null) { actionResult = lookupAndRegisterIfMissing(actionResultClass); } } } if (actionResult == null) { // + still not found, read @Action value ActionConfig actionConfig = actionRequest.getActionConfig(); Class<? extends ActionResult> actionResultClass = actionConfig.getActionResult(); if (actionResultClass != null) { actionResult = lookupAndRegisterIfMissing(actionResultClass); } } if (actionResult == null && resultObject != null) { Class resultType = resultObject.getClass(); if (resultType != String.class) { // + still not found, read @RenderWith value if exist RenderWith renderWith = resultObject.getClass().getAnnotation(RenderWith.class); if (renderWith != null) { actionResult = lookupAndRegisterIfMissing(renderWith.value()); } if (actionResult == null) { // + still not found, lookup for type actionResult = typeResults.get(resultObject.getClass()); } } } if (actionResult == null) { // + still not found, toString() ActionResult defaultActionResult = lookupAndRegisterIfMissing(madvocConfig.getDefaultActionResult()); if (stringResults.isEmpty()) { // no string results registered, carry on with the defaults. actionResult = defaultActionResult; } else { String resultValue = resultObject != null ? resultObject.toString() : null; String resultName = null; // first check result value if (resultValue != null) { int columnIndex = resultValue.indexOf(':'); if (columnIndex != -1) { resultName = resultValue.substring(0, columnIndex); resultValue = resultValue.substring(columnIndex + 1); } } if (resultName != null) { actionResult = stringResults.get(resultName); } else { actionResult = defaultActionResult; } if (actionResult.getResultName() != null) { // convert remaining of the string to result object // only when action result is string result try { Class targetClass = actionResult.getResultValueType(); if (targetClass == null || targetClass == String.class) { resultObject = resultValue; } else { resultObject = TypeConverterManager.convertType(resultValue, targetClass); } } catch (Exception ex) { resultObject = resultValue; } } } } // set action result object into action request! actionRequest.setActionResult(resultObject); return actionResult; } // ---------------------------------------------------------------- init /** * Initializes action result. */ protected void initializeResult(ActionResult result) { contextInjectorComponent.injectContext(new Target(result)); result.init(); } // ---------------------------------------------------------------- create /** * Creates new {@link jodd.madvoc.result.ActionResult}. */ protected ActionResult createResult(Class<? extends ActionResult> actionResultClass) { try { return actionResultClass.newInstance(); } catch (Exception ex) { throw new MadvocException("Invalid Madvoc result: " + actionResultClass, ex); } } }