/* * Copyright 2017 OmniFaces * * 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.omnifaces.converter; import static java.lang.String.format; import static org.omnifaces.util.Faces.getViewAttribute; import static org.omnifaces.util.Faces.setViewAttribute; import static org.omnifaces.util.Messages.createError; import static org.omnifaces.util.Utils.isOneOf; import javax.faces.component.UIComponent; import javax.faces.component.UISelectMany; import javax.faces.context.FacesContext; import javax.faces.convert.Converter; import javax.faces.convert.ConverterException; import javax.faces.convert.EnumConverter; import javax.faces.convert.FacesConverter; /** * <p> * The <code>omnifaces.GenericEnumConverter</code> is intended for use in {@link UISelectMany} components whose value is * been bound to a <code>List<E></code> property where <code>E</code> is an enum. Even though JSF has already a * built-in {@link EnumConverter}, this doesn't work for a <code>List<E></code> property as the generic type * information <code>E</code> is lost during runtime. The list would be filled with unconverted <code>String</code> * values instead which may in turn cause <code>ClassCastException</code> during postprocessing in the business logic. * <p> * This can be solved by using a <code>E[]</code> property instead of <code>List<E></code> (e.g. * <code>Role[]</code> in case of a <code>Role</code> enum). If this is however is not an option due to some design * restrictions (e.g. JPA <code>@ElementCollection</code>, etc), then you'd need to create an explicit converter for the * enum type like follows: * <pre> * @FacesConverter("roleConverter") * public class RoleConverter extends EnumConverter { * public RoleConverter() { * super(Role.class); * } * } * </pre> * <pre> * <h:selectManyCheckbox value="#{bean.selectedRoles}" converter="roleConverter"> * <f:selectItems value="#{bean.availableRoles}" /> * </h:selectManyCheckbox> * </pre> * <p> * However, creating a new converter for every single enum type, only and only for use in {@link UISelectMany} with a * <code>List<E></code> property, may be a bit clumsy. This generic enum converter is intended to remove the need * to create a new enum converter every time. * * <h3>Usage</h3> * <p> * This converter is available by converter ID <code>omnifaces.GenericEnumConverter</code>. Just specify it in the * <code>converter</code> attribute of the multi-selection component holding <code><f:selectItems></code>. * example: * <pre> * <h:selectManyCheckbox value="#{bean.selectedEnums}" converter="omnifaces.GenericEnumConverter"> * <f:selectItems value="#{bean.availableEnums}" /> * </h:selectManyCheckbox> * </pre> * * <p><strong>See also</strong>: * <br><a href="http://stackoverflow.com/q/3822058/157882">Use enum in <h:selectManyCheckbox></a> * * @author Bauke Scholtz * @since 1.2 */ @FacesConverter(value = "omnifaces.GenericEnumConverter") public class GenericEnumConverter implements Converter { // Constants ------------------------------------------------------------------------------------------------------ private static final String ATTRIBUTE_ENUM_TYPE = "GenericEnumConverter.%s"; private static final String ERROR_NO_ENUM_TYPE = "Given type ''{0}'' is not an enum."; private static final String ERROR_NO_ENUM_VALUE = "Given value ''{0}'' is not an enum of type ''{1}''."; // Actions -------------------------------------------------------------------------------------------------------- @Override @SuppressWarnings({ "rawtypes", "unchecked" }) public String getAsString(FacesContext context, UIComponent component, Object modelValue) { if (modelValue == null) { return "-"; } if (modelValue instanceof Enum) { Class<Enum> enumType = ((Enum) modelValue).getDeclaringClass(); setViewAttribute(format(ATTRIBUTE_ENUM_TYPE, component.getClientId(context)), enumType); return ((Enum) modelValue).name(); } else { throw new ConverterException(createError(ERROR_NO_ENUM_TYPE, modelValue.getClass())); } } @Override @SuppressWarnings({ "rawtypes", "unchecked" }) public Object getAsObject(FacesContext context, UIComponent component, String submittedValue) { if (isOneOf(submittedValue, null, "", "-")) { return null; } Class<Enum> enumType = getViewAttribute(format(ATTRIBUTE_ENUM_TYPE, component.getClientId(context))); try { return Enum.valueOf(enumType, submittedValue); } catch (IllegalArgumentException e) { throw new ConverterException(createError(ERROR_NO_ENUM_VALUE, submittedValue, enumType), e); } } }