/** Tests the workflow method. */ @Test public void testSimpleParameters() throws IOException, ServletException { Action action = new Action(); Map<String, String[]> values = new HashMap<String, String[]>(); values.put("user.addresses['home'].city", array("Boulder")); values.put("user.age", array("32")); values.put("user.inches", array("tall")); values.put("user.age@dateFormat", array("MM/dd/yyyy")); values.put( "user.name", array("")); // This should be stripped out and the ExpressionEvaluator never called for it final HttpServletRequest request = EasyMock.createStrictMock(HttpServletRequest.class); EasyMock.expect(request.getParameterMap()).andReturn(values); EasyMock.replay(request); ExpressionEvaluator expressionEvaluator = EasyMock.createNiceMock(ExpressionEvaluator.class); expressionEvaluator.setValue( eq("user.addresses['home'].city"), same(action), aryEq(array("Boulder")), eq(new HashMap<String, String>())); expressionEvaluator.setValue( eq("user.age"), same(action), aryEq(array("32")), eq(map("dateFormat", "MM/dd/yyyy"))); expressionEvaluator.setValue( eq("user.inches"), same(action), aryEq(array("tall")), eq(new HashMap<String, String>())); expectLastCall().andThrow(new ConversionException()); EasyMock.replay(expressionEvaluator); ActionInvocation invocation = EasyMock.createStrictMock(ActionInvocation.class); EasyMock.expect(invocation.action()).andReturn(action); EasyMock.replay(invocation); ActionInvocationStore actionInvocationStore = EasyMock.createStrictMock(ActionInvocationStore.class); EasyMock.expect(actionInvocationStore.getCurrent()).andReturn(invocation); EasyMock.replay(actionInvocationStore); MessageStore messageStore = EasyMock.createStrictMock(MessageStore.class); messageStore.addConversionError( eq("user.inches"), eq("org.example.domain.Action"), eq(new HashMap<String, String>()), eq("tall")); EasyMock.replay(messageStore); WorkflowChain chain = EasyMock.createStrictMock(WorkflowChain.class); chain.continueWorkflow(); EasyMock.replay(chain); DefaultParameterWorkflow workflow = new DefaultParameterWorkflow( request, actionInvocationStore, messageStore, expressionEvaluator); workflow.perform(chain); EasyMock.verify( request, expressionEvaluator, invocation, actionInvocationStore, messageStore, chain); }
/** * Determines the key. If the attribute contains a <code>keyExpr</code>, it is used against the * object to get the key. Otherwise, the object is just converted to a String. * * @param itemsValue The current value from the items collection/array/map used to determine the * key. * @param key The key from a items Map or null if items is not a Map. * @param valueExpr The valueExpr attribute. * @return The key and never null. If the Object is null, this returns an empty String. */ private Object makeValue(Object itemsValue, Object key, String valueExpr) { if (itemsValue == null) { return ""; } if (valueExpr != null) { Object value = expressionEvaluator.getValue(valueExpr, itemsValue); if (value != null) { return value; } } if (key != null) { return key; } return itemsValue; }
/** * Handles the items and value. Here's the skinny: * * <ul> * <li>If items is null, just inserts an empty Map in the attributes under <code>options</code> * <li>If items is a Collection, loops over it and creates options. The selected state of the * options are based on whether or not the value is a Collection or an array or null or just * a plain Object. In the collection/array case, if the current items value is in the * collection the option is selected. In the plain object case, if the current items value * is equal it is selected. Otherwise, it isn't selected. Also, this handles the text and * key using the expression attributes or the current items value. * <li>If items is a Map, loops over it and creates options. The selected state of the options * are based on whether or not the value is a Collection or an array or null or just a plain * Object. In the collection/array case, if the current items value is in the collection the * option is selected. In the plain object case, if the current items value is equal it is * selected. Otherwise, it isn't selected. Also, this handles the text and key using the key * from the items Map, the expression attributes or the current items value. * </ul> */ @Override protected Map<String, Object> makeParameters() { Map<String, Object> parameters = super.makeParameters(); Map<String, Option> options = new LinkedHashMap<String, Option>(); // Handle the header option String headerValue = (String) attributes.remove("headerValue"); String headerL10n = (String) attributes.remove("headerL10n"); if (headerValue != null) { String message = ""; if (headerL10n != null) { String bundleName = determineBundleName(attributes); if (bundleName == null) { throw new IllegalStateException( "Unable to locate the localized text for a header " + "option in the select box named [" + attributes.get("name") + "]. If you " + "don't have an action class for the URL, you must define the bundle used " + "to localize the select using the bundle attribute."); } message = messageProvider.getMessage(bundleName, headerL10n); } options.put(headerValue, new Option(message, false)); } // Grab the value Object beanValue = currentAction() != null ? expressionEvaluator.getValue((String) attributes.get("name"), currentAction()) : null; // Next, let's handle the items here. I'll create a Map that contains a simple inner class // that determines if the option is selected or not. This will allow me to get the text // as well String valueExpr = (String) attributes.remove("valueExpr"); String textExpr = (String) attributes.remove("textExpr"); String l10nExpr = (String) attributes.remove("l10nExpr"); Object items = attributes.remove("items"); if (items != null) { if (items instanceof Collection) { Collection c = (Collection) items; for (Object o : c) { Object value = makeValue(o, null, valueExpr); options.put( value.toString(), makeOption(o, value, beanValue, attributes, textExpr, l10nExpr)); } } else if (items instanceof Map) { Map<?, ?> m = (Map<?, ?>) items; for (Map.Entry entry : m.entrySet()) { Object value = makeValue(entry.getValue(), entry.getKey(), valueExpr); Option option = makeOption(entry.getValue(), value, beanValue, attributes, textExpr, l10nExpr); options.put(value.toString(), option); } } else if (items.getClass().isArray()) { int length = Array.getLength(items); for (int i = 0; i < length; i++) { Object itemsValue = Array.get(items, i); Object value = makeValue(itemsValue, null, valueExpr); Option option = makeOption(itemsValue, value, beanValue, attributes, textExpr, l10nExpr); options.put(value.toString(), option); } } } parameters.put("options", options); return parameters; }
/** * Makes an option. If the attributes contains a <code>l10nExpr</code>, it is used with the Object * to get a message from the {@link org.jcatapult.l10n.MessageProvider}. If that doesn't exist and * a <code>textExpr</code> does, it is used to get the text for the option from the object. * Otherwise, the object is converted to a String for the text. Also, if the object exists in the * given Collection the option is set to selected. * * @param itemsValue The current value from the items collection/array/map. * @param value The value of the option. This could have been from the items Map or the valueExpr * evaluation. * @param beanValue The value from the bean, used to determine selected state. * @param attributes used to get the text for the option. * @param textExpr The textExpr attribute. * @param l10nExpr The l10nExpr attribute. * @return The option and never null. */ private Option makeOption( Object itemsValue, Object value, Object beanValue, Map<String, Object> attributes, String textExpr, String l10nExpr) { if (itemsValue == null) { return new Option("", false); } String text = null; if (l10nExpr != null) { String bundleName = determineBundleName(attributes); if (bundleName == null) { throw new IllegalStateException( "Unable to locate the localized text for an option " + "in the select input for the field named [" + attributes.get("name") + "]. If " + "you don't have an action class for the URL, you must define the bundle used " + "to localize the select using the bundle attribute."); } Object l10nKey = expressionEvaluator.getValue(l10nExpr, itemsValue); if (l10nKey != null) { text = messageProvider.getMessage(bundleName, l10nKey.toString()); } } if (text == null) { if (textExpr != null) { text = expressionEvaluator.getValue(textExpr, itemsValue).toString(); } } if (text == null) { text = itemsValue.toString(); } if (beanValue == null) { return new Option(text, false); } if (beanValue instanceof Collection) { return new Option(text, ((Collection) beanValue).contains(value)); } if (beanValue.getClass().isArray()) { int length = Array.getLength(beanValue); for (int i = 0; i < length; i++) { Object arrayValue = Array.get(beanValue, i); if (arrayValue != null && arrayValue.equals(value)) { return new Option(text, true); } } } // FreeMarker wraps all maps into Map<String, Object> and does it incorrectly at that. // This is a hack to ensure that when keys are compared, it works for Maps. Most of the time // the class of the value from the bean and the value for the ListInput will be the same, // such as Integer. boolean equal; if (value != null) { if (beanValue.getClass().isInstance(value)) { equal = beanValue.equals(value); } else { equal = beanValue.toString().equals(value.toString()); } } else { if (beanValue.getClass().isInstance(itemsValue)) { equal = beanValue.equals(itemsValue); } else { equal = beanValue.toString().equals(itemsValue.toString()); } } return new Option(text, equal); }