/* * $Id$ * * License Agreement. * * Rich Faces - Natural Ajax for Java Server Faces (JSF) * * Copyright (C) 2007 Exadel, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License version 2.1 as published by the Free Software Foundation. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ package org.richfaces.cdk; import java.util.Collection; import java.util.HashSet; import java.util.NoSuchElementException; import javax.faces.component.UIComponentBase; import javax.faces.component.behavior.ClientBehaviorBase; import javax.faces.render.Renderer; import javax.faces.view.facelets.BehaviorHandler; import javax.faces.view.facelets.ComponentHandler; import javax.faces.view.facelets.ConverterHandler; import javax.xml.validation.ValidatorHandler; import org.richfaces.cdk.annotations.TagType; import org.richfaces.cdk.apt.DummyPropertyImpl; import org.richfaces.cdk.apt.SourceUtils; import org.richfaces.cdk.apt.SourceUtils.BeanProperty; import org.richfaces.cdk.model.BehaviorModel; import org.richfaces.cdk.model.ClassName; import org.richfaces.cdk.model.ComponentLibrary; import org.richfaces.cdk.model.ComponentModel; import org.richfaces.cdk.model.ConverterModel; import org.richfaces.cdk.model.DescriptionGroup; import org.richfaces.cdk.model.EventModel; import org.richfaces.cdk.model.FacesId; import org.richfaces.cdk.model.FacetModel; import org.richfaces.cdk.model.GeneratedFacesComponent; import org.richfaces.cdk.model.InvalidNameException; import org.richfaces.cdk.model.ModelElementBase; import org.richfaces.cdk.model.PropertyBase; import org.richfaces.cdk.model.RenderKitModel; import org.richfaces.cdk.model.RendererModel; import org.richfaces.cdk.model.TagModel; import org.richfaces.cdk.model.Taglib; import org.richfaces.cdk.model.ValidatorModel; import org.richfaces.cdk.model.validator.CallbackException; import org.richfaces.cdk.model.validator.NamingConventionsCallback; import org.richfaces.cdk.util.Strings; import com.google.common.base.Predicate; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Sets; import com.google.inject.Inject; import com.google.inject.Provider; /** * <p class="changed_added_4_0"> * </p> * * @author asmirnov@exadel.com * */ public class RichFaces5Validator implements ModelValidator { private final class ComponentTypeCallback implements NamingConventionsCallback { @Override public FacesId inferType(ClassName targetClass) { return namingConventions.inferComponentType(targetClass); } @Override public ClassName inferClass(FacesId id) { return namingConventions.inferUIComponentClass(id); } @Override public ClassName getDefaultBaseClass() throws CallbackException { return ClassName.get(UIComponentBase.class); } @Override public ClassName getDefaultClass() throws CallbackException { throw new CallbackException("Cannot determine component class name"); } @Override public FacesId inferType() throws CallbackException { throw new CallbackException("Cannot determine component type"); } } private final class BehaviorTypeCallback implements NamingConventionsCallback { private final BehaviorModel behavior; private BehaviorTypeCallback(BehaviorModel behavior) { this.behavior = behavior; } @Override public FacesId inferType(ClassName targetClass) { return namingConventions.inferBehaviorType(targetClass); } @Override public FacesId inferType() throws CallbackException { throw new CallbackException("Cannot infer type for behavior " + this.behavior); } @Override public ClassName inferClass(FacesId id) { return namingConventions.inferBehaviorClass(id); } @Override public ClassName getDefaultBaseClass() throws CallbackException { return ClassName.get(ClientBehaviorBase.class); } @Override public ClassName getDefaultClass() throws CallbackException { throw new CallbackException("Cannot infer Java class name for behavior " + this.behavior); } } private final class ConverterTypeCallback implements NamingConventionsCallback { private final ConverterModel converter; public ConverterTypeCallback(ConverterModel converter) { this.converter = converter; } @Override public FacesId inferType(ClassName targetClass) throws CallbackException { // TODO use actual methods return namingConventions.inferComponentType(targetClass); } @Override public FacesId inferType() throws CallbackException { throw new CallbackException("Cannot infer type for converter " + this.converter); } @Override public ClassName inferClass(FacesId id) throws CallbackException { throw new CallbackException("Cannot infer target Java class name for converter " + this.converter); } @Override public ClassName getDefaultBaseClass() throws CallbackException { throw new CallbackException("Cannot infer base Java class name for converter " + this.converter); } @Override public ClassName getDefaultClass() throws CallbackException { return ClassName.get(Object.class); } } private final class ValidatorTypeCallback implements NamingConventionsCallback { private final ValidatorModel validator; public ValidatorTypeCallback(ValidatorModel validator) { this.validator = validator; } @Override public FacesId inferType(ClassName targetClass) throws CallbackException { // TODO use actual methods return namingConventions.inferComponentType(targetClass); } @Override public FacesId inferType() throws CallbackException { throw new CallbackException("Cannot infer type for validator " + this.validator); } @Override public ClassName inferClass(FacesId id) throws CallbackException { throw new CallbackException("Cannot infer target Java class name for validator " + this.validator); } @Override public ClassName getDefaultBaseClass() throws CallbackException { throw new CallbackException("Cannot infer default Java class name for validator " + this.validator); } @Override public ClassName getDefaultClass() throws CallbackException { return ClassName.get(Object.class); } } private final class RendererTypeCallback implements NamingConventionsCallback { private final ComponentLibrary library; private final RendererModel renderer; private RendererTypeCallback(ComponentLibrary library, RendererModel renderer) { this.library = library; this.renderer = renderer; } @Override public FacesId inferType(ClassName targetClass) { try { return inferType(); } catch (CallbackException e) { return namingConventions.inferRendererType(targetClass); } } @Override public FacesId inferType() throws CallbackException { // For renderers with template - try to determine type by template file. if (null != this.renderer.getTemplate()) { for (ComponentModel component : this.library.getComponents()) { if (null != component.getRendererTemplate() && this.renderer.getTemplate().getTemplatePath().endsWith(component.getRendererTemplate())) { if (null != component.getRendererType()) { return component.getRendererType(); } else { FacesId rendererType = namingConventions.inferRendererType(component.getId()); component.setRendererType(rendererType); return rendererType; } } } // No component found, try to infer from template path. return namingConventions.inferRendererTypeByTemplatePath(this.renderer.getTemplate().getTemplatePath()); } // If previvious attempt fall, try to infer renderer type from family. if (null != this.renderer.getFamily()) { return namingConventions.inferRendererType(this.renderer.getFamily()); } throw new CallbackException("Cannot determine renderer type"); } @Override public ClassName inferClass(FacesId id) { return namingConventions.inferRendererClass(id); } @Override public ClassName getDefaultClass() throws CallbackException { throw new CallbackException("Cannot determine renderer class name"); } @Override public ClassName getDefaultBaseClass() throws CallbackException { return ClassName.get(Renderer.class); } } public static final ClassName DEFAULT_COMPONENT_HANDLER = new ClassName(ComponentHandler.class); public static final ClassName DEFAULT_VALIDATOR_HANDLER = new ClassName(ValidatorHandler.class); public static final ClassName DEFAULT_CONVERTER_HANDLER = new ClassName(ConverterHandler.class); public static final ClassName DEFAULT_BEHAVIOR_HANDLER = new ClassName(BehaviorHandler.class); public static final ImmutableSet<String> SPECIAL_PROPERTIES = ImmutableSet.of("eventNames", "defaultEventName", "clientBehaviors", "family"); @Inject private Logger log; private final NamingConventions namingConventions; private final Provider<SourceUtils> sourceUtilsProvider; @Inject public RichFaces5Validator(NamingConventions namingConventions, Provider<SourceUtils> sourceUtilsProvider) { this.namingConventions = namingConventions; this.sourceUtilsProvider = sourceUtilsProvider; } /* * (non-Javadoc) * * @see org.richfaces.cdk.ValidatorModel#verify(org.richfaces.cdk.model.ComponentLibrary) */ @Override public void verify(ComponentLibrary library) throws CdkException { verifyComponents(library); verifyEvents(library); verifyRenderers(library); verifyTaglib(library); verifyBehaviors(library); verifyConverters(library); verifyValidators(library); } private void verifyValidators(ComponentLibrary library) { for (ValidatorModel validator : library.getValidators()) { verifyTypes(validator, new ValidatorTypeCallback(validator)); } } protected void verifyConverters(ComponentLibrary library) { for (ConverterModel converter : library.getConverters()) { verifyTypes(converter, new ConverterTypeCallback(converter)); } } protected void verifyEvents(ComponentLibrary library) { for (EventModel event : library.getEvents()) { ClassName listenerInterface = event.getListenerInterface(); SourceUtils sourceUtils = sourceUtilsProvider.get(); if (null != listenerInterface) { event.setGenerateListener(!sourceUtils.isClassExists(listenerInterface)); } String methodName = event.getListenerMethod(); if (null == methodName) { methodName = "process"; event.setListenerMethod(methodName); } ClassName sourceInterface = event.getSourceInterface(); if (null != sourceInterface) { event.setGenerateSource(!sourceUtils.isClassExists(sourceInterface)); } // Propagate event to corresponding components. for (ComponentModel component : library.getComponents()) { for (EventModel componentEvent : component.getEvents()) { if (event.getType().equals(componentEvent.getType())) { componentEvent.merge(event); } } } } } protected void verifyTaglib(ComponentLibrary library) { Taglib taglib = library.getTaglib(); if (null == taglib) { // Oops, create taglib model taglib = new Taglib(); library.setTaglib(taglib); } // Verify URI String uri = taglib.getUri(); if (null == uri) { // infer default value. uri = namingConventions.inferTaglibUri(library); taglib.setUri(uri); // log.error("No uri defined for taglib"); } String shortName = taglib.getShortName(); if (null == shortName) { shortName = namingConventions.inferTaglibName(uri); taglib.setShortName(shortName); // log.error("No short defined for taglib"); } // Verify tags. If we have renderer-specific component, it should have a tag ? for (ComponentModel component : library.getComponents()) { if (null != component.getRendererType() && component.getTags().isEmpty()) { TagModel tag = new TagModel(); verifyTag(tag, component.getId(), DEFAULT_COMPONENT_HANDLER); component.getTags().add(tag); } } } /** * <p class="changed_added_4_0"> * Verify all behaviors in the library. * </p> * * @param library */ protected void verifyBehaviors(ComponentLibrary library) { for (final BehaviorModel behavior : library.getBehaviors()) { verifyTypes(behavior, new BehaviorTypeCallback(behavior)); for (TagModel tag : behavior.getTags()) { verifyTag(tag, behavior.getId(), DEFAULT_BEHAVIOR_HANDLER); } } } protected void verifyRenderers(ComponentLibrary library) { for (RenderKitModel renderKit : library.getRenderKits()) { // Check render kit name and class. for (RendererModel renderer : renderKit.getRenderers()) { try { vefifyRenderer(library, renderer); } catch (RuntimeException e) { throw new IllegalStateException("Caught error when verifying renderer " + renderer, e); } } } } protected void vefifyRenderer(final ComponentLibrary library, final RendererModel renderer) { String baseName = renderer.getBaseClass().getSimpleName().replaceFirst("Base$", ""); // Check renderer-type if (null == renderer.getId()) { if (null == renderer.getTemplate().getTemplatePath()) { throw new IllegalArgumentException("templatePath must not be null"); } renderer.setId(namingConventions.inferRendererTypeByTemplatePath(renderer.getTemplate().getTemplatePath())); } // Check family. if (null == renderer.getFamily()) { renderer.setFamily(namingConventions.inferRendererFamily(renderer.getId())); } // Check type. verifyTypes(renderer, new RendererTypeCallback(library, renderer)); // Check component type. for (ComponentModel component : library.getComponents()) { if (renderer.getId().equals(component.getRendererType())) { copyRendererAttributes(renderer, component); } else if (hasRendererSameBaseNameAsComponent(renderer, component)) { copyRendererAttributes(renderer, component); component.setRendererType(renderer.getId()); } } // Check template if (renderer.getTemplate() != null && renderer.getTemplate().getInterface() != null) { if (null == renderer.getTemplate().getInterface().getJavaClass()) { renderer.getTemplate().getInterface().setJavaClass(renderer.getTargetClass()); } } } private void copyRendererAttributes(final RendererModel renderer, ComponentModel component) { for (PropertyBase property : renderer.getAttributes()) { PropertyBase attribute = component.getOrCreateAttribute(property.getName()); attribute.merge(property); verifyAttribute(attribute, component); } renderer.setFamily(component.getFamily()); } private boolean hasRendererSameBaseNameAsComponent(final RendererModel renderer, ComponentModel component) { String componentBaseName = component.getTargetClass().getSimpleName(); String rendererBaseName = renderer.getTargetClass().getSimpleName(); componentBaseName = componentBaseName.replaceFirst("^UI", ""); rendererBaseName = rendererBaseName.replaceFirst("Renderer$", ""); return componentBaseName.equals(rendererBaseName); } protected void verifyComponents(ComponentLibrary library) throws CdkException { // Verify types and classes. Do it first to be sure what all all values are set before second stage. for (ComponentModel component : library.getComponents()) { try { verifyComponentType(component); } catch (RuntimeException e) { throw new CdkException("Caught error when verifying component " + component, e); } } // Verify component attributes HashSet<ComponentModel> verified = Sets.newHashSet(); for (ComponentModel component : library.getComponents()) { try { verifyComponentType(component); verifyComponentAttributes(library, component, verified); // generate component family if missing if (null == component.getFamily()) { component.setFamily(namingConventions.inferUIComponentFamily(component.getId())); } // add facelet tag if missing if (component.getTags().isEmpty()) { TagModel tag = new TagModel(); component.getTags().add(tag); tag.setName(namingConventions.inferTagName(component.getId())); tag.setGenerate(false); tag.setType(TagType.Facelets); } } catch (RuntimeException e) { throw new CdkException("Caught error when verifying component " + component, e); } } } /** * <p class="changed_added_4_0"> * </p> * * @param library * @param component * @param verified */ protected void verifyComponentAttributes(ComponentLibrary library, final ComponentModel component, Collection<ComponentModel> verified) { // There is potential StackOverflow, so we process only components which have not been // verified before. if (!verified.contains(component)) { // Propagate attributes from parent component, if any. verified.add(component); if (null != component.getBaseClass()) { try { // Step one, lookup for parent. ComponentModel parentComponent = findParent(library.getComponents(), component); component.setParent(parentComponent); if (null == component.getFamily()) { component.setFamily(parentComponent.getFamily()); } // To be sure what all properties for parent component were propagated. verifyComponentAttributes(library, parentComponent, verified); for (PropertyBase parentAttribute : parentComponent.getAttributes()) { PropertyBase attribute = component.getOrCreateAttribute(parentAttribute.getName()); attribute.merge(parentAttribute); // already exists in parent component. attribute.setGenerate(false); } } catch (NoSuchElementException e) { // No parent component in the library } } // Check attributes. for (PropertyBase attribute : component.getAttributes()) { verifyAttribute(attribute, component); } // compact(component.getAttributes()); // Check renderers. // Check Tag for (TagModel tag : component.getTags()) { verifyTag(tag, component.getId(), DEFAULT_COMPONENT_HANDLER); } verifyDescription(component); for (FacetModel facet : component.getFacets()) { verifyDescription(facet); } } } private <T extends GeneratedFacesComponent> T findParent(Iterable<T> components, final T component) throws NoSuchElementException { return Iterables.find(components, new Predicate<T>() { @Override public boolean apply(T input) { return component.getBaseClass().equals(input.getTargetClass()) && component != input; } }); } protected void verifyTag(TagModel tag, FacesId id, ClassName handler) { if (Strings.isEmpty(tag.getName())) { String defaultTagName = namingConventions.inferTagName(id); tag.setName(defaultTagName); } if (null == tag.getType()) { tag.setType(TagType.Facelets); } if (tag.isGenerate()) { if (null == tag.getBaseClass()) { tag.setBaseClass(handler); } if (null == tag.getTargetClass()) { ClassName inferredTagHandler = namingConventions.inferTagHandlerClass(id, tag.getType().toString()); tag.setTargetClass(inferredTagHandler); } } } /** * <p class="changed_added_4_0"> * </p> * * @param component * @throws InvalidNameException */ protected void verifyComponentType(ComponentModel component) throws InvalidNameException { // Check JsfComponent type. verifyTypes(component, new ComponentTypeCallback()); } /** * <p class="changed_added_4_0"> * This method virifies type/family attributes for JSF objects ( components, renderers, validators, converters, behaviors ) * </p> * * @param component object to verify. * @param callback callback to corresponding naming conventions. * @return */ protected boolean verifyTypes(GeneratedFacesComponent component, NamingConventionsCallback callback) { // Check JsfComponent type. try { if (null == component.getId()) { if (null != component.getTargetClass()) { component.setId(callback.inferType(component.getTargetClass())); } else if (null != component.getBaseClass()) { component.setId(callback.inferType(component.getBaseClass())); } else { component.setId(callback.inferType()); } } // Check classes. if (null == component.getGenerate()) { if (null == component.getTargetClass()) { component.setTargetClass(callback.inferClass(component.getId())); } component.setGenerate(!sourceUtilsProvider.get().isClassExists(component.getTargetClass())); } if (component.getGenerate()) { verifyGeneratedClasses(component, callback); } else if (null == component.getTargetClass()) { if (null == component.getBaseClass()) { component.setBaseClass(callback.getDefaultClass()); } component.setTargetClass(component.getBaseClass()); } } catch (CallbackException e) { log.error(e.getMessage()); return false; } return true; } private void verifyGeneratedClasses(GeneratedFacesComponent component, NamingConventionsCallback callback) throws CallbackException { if (null == component.getBaseClass()) { component.setBaseClass(callback.getDefaultBaseClass()); // return; } if (null == component.getTargetClass()) { component.setTargetClass(callback.inferClass(component.getId())); } } protected void verifyAttribute(PropertyBase attribute, GeneratedFacesComponent component) { // Check name. if (Strings.isEmpty(attribute.getName())) { log.error("No name for attribute " + attribute); return; } if (attribute.getName().contains(".") || Character.isDigit(attribute.getName().charAt(0)) || attribute.getName().contains(" ")) { log.error("Invalid attribute name [" + attribute.getName() + "]"); return; } // Check type BeanProperty beanProperty = findBeanProperty(attribute, component); if (null == attribute.getType()) { log.warn("Unknown type of attribute [" + attribute.getName() + "]"); attribute.setType(beanProperty.getType()); } if (attribute.getType().isPrimitive() && null == attribute.getDefaultValue()) { // Set default value for primitive attribute.setDefaultValue(attribute.getType().getDefaultValue()); } // Check binding properties. if ("javax.faces.el.MethodBinding".equals(attribute.getType().getName())) { attribute.setBinding(true); attribute.setBindingAttribute(true); } else if ("javax.el.MethodExpression".equals(attribute.getType().getName())) { attribute.setBindingAttribute(true); } // if(attribute.isBindingAttribute() && attribute.getSignature().isEmpty() && !attribute.isHidden()) { // log.error("Signature for method expression attribute "+attribute.getName()+" has not been set"); // } // Check "generate" flag. if (Boolean.TRUE.equals(component.getGenerate())) { // TODO Attribute should be only generated if it does not exist or abstract in the base class. // Step one - check base class if (SPECIAL_PROPERTIES.contains(attribute.getName())) { attribute.setGenerate(false); } else if (null == attribute.getGenerate()) { attribute.setGenerate(!beanProperty.isExists()); } } else { attribute.setGenerate(false); } verifyDescription(attribute); } private BeanProperty findBeanProperty(PropertyBase attribute, GeneratedFacesComponent component) { SourceUtils sourceUtils = sourceUtilsProvider.get(); BeanProperty beanProperty = sourceUtils.getBeanProperty(component.getBaseClass(), attribute.getName()); if (beanProperty instanceof DummyPropertyImpl && component instanceof ComponentModel) { ComponentModel model = (ComponentModel) component; if (null != model.getParent()) { beanProperty = findBeanProperty(attribute, model.getParent()); } } if (beanProperty instanceof DummyPropertyImpl && component instanceof ModelElementBase) { ModelElementBase model = (ModelElementBase) component; for (ClassName interfaceName : model.getInterfaces()) { beanProperty = sourceUtils.getBeanProperty(interfaceName, attribute.getName()); if (!(beanProperty instanceof DummyPropertyImpl)) { break; } } } return beanProperty; } protected void verifyDescription(DescriptionGroup element) { } }