All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.omnifaces.viewhandler.NoAutoGeneratedIdViewHandler Maven / Gradle / Ivy

/*
 * Copyright 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
 *
 *     https://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.viewhandler;

import static java.lang.String.format;
import static javax.faces.component.UINamingContainer.getSeparatorChar;
import static javax.faces.component.UIViewRoot.UNIQUE_ID_PREFIX;
import static org.omnifaces.util.Components.findComponentRelatively;
import static org.omnifaces.util.Components.getCurrentComponent;
import static org.omnifaces.util.Faces.getContext;
import static org.omnifaces.util.Faces.getViewRoot;
import static org.omnifaces.util.FacesLocal.isDevelopment;

import java.io.IOException;
import java.io.Writer;
import java.util.logging.Logger;

import javax.faces.application.Application;
import javax.faces.application.ProjectStage;
import javax.faces.application.ViewHandler;
import javax.faces.application.ViewHandlerWrapper;
import javax.faces.component.UIComponent;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import javax.faces.context.FacesContextWrapper;
import javax.faces.context.ResponseWriter;
import javax.faces.context.ResponseWriterWrapper;

import org.omnifaces.util.Utils;

/**
 * 

* This {@link ViewHandler} once installed will during development stage throw an {@link IllegalStateException} whenever * an automatically generated JSF component ID (j_id...) is encountered in the rendered output. * This has various advantages: *

    *
  • Keep the HTML output free of autogenerated JSF component IDs. *
  • No need to fix the IDs again and again when the client side unit tester encounters an unusable autogenerated ID. *
  • Make the developer aware which components are naming containers and/or implicitly require outputting its ID. *
*

* Note that this does not check every component for its ID directly, but instead checks the {@link ResponseWriter} for * writes to the "id" attribute. Components that write their markup in any other way won't be checked and will thus * slip through. * *

Installation

*

* Register it as <view-handler> in faces-config.xml. *

 * <application>
 *     <view-handler>org.omnifaces.viewhandler.NoAutoGeneratedIdViewHandler</view-handler>
 * </application>
 * 
*

* Note that this only runs if {@link Application#getProjectStage()} equals to {@link ProjectStage#Development}. * * @since 2.0 * @author Arjan Tijms */ public class NoAutoGeneratedIdViewHandler extends ViewHandlerWrapper { // Private constants ---------------------------------------------------------------------------------------------- private static final Logger logger = Logger.getLogger(NoAutoGeneratedIdViewHandler.class.getName()); private static final String ERROR_AUTO_GENERATED_ID_ENCOUNTERED = "Auto generated ID '%s' encountered on component type: '%s'."; // Properties ----------------------------------------------------------------------------------------------------- private ViewHandler wrapped; // Constructors --------------------------------------------------------------------------------------------------- /** * Construct a new No Auto Generated Id view handler around the given wrapped view handler. * * @param wrapped * The wrapped view handler. */ public NoAutoGeneratedIdViewHandler(ViewHandler wrapped) { this.wrapped = wrapped; } // Actions -------------------------------------------------------------------------------------------------------- @Override public void renderView(final FacesContext context, UIViewRoot viewToRender) throws IOException { super.renderView(isDevelopment(context) ? new FacesContextWrapper() { @Override public void setResponseWriter(ResponseWriter responseWriter) { super.setResponseWriter(new NoAutoGeneratedIdResponseWriter(responseWriter)); } @Override public FacesContext getWrapped() { return context; } } : context, viewToRender); } @Override public ViewHandler getWrapped() { return wrapped; } // Nested classes ------------------------------------------------------------------------------------------------- /** * This response writer throws an {@link IllegalStateException} when an attribute with name "id" is written with * a non-null value which starts with {@link UIViewRoot#UNIQUE_ID_PREFIX} or contains an intermediate. * * @since 2.0 * @author Arjan Tijms */ public static class NoAutoGeneratedIdResponseWriter extends ResponseWriterWrapper { private ResponseWriter wrapped; private final char separatorChar; private final String intermediateIdPrefix; public NoAutoGeneratedIdResponseWriter(ResponseWriter wrapped) { this.wrapped = wrapped; separatorChar = getSeparatorChar(getContext()); intermediateIdPrefix = separatorChar + UNIQUE_ID_PREFIX; } @Override public ResponseWriter cloneWithWriter(Writer writer) { return new NoAutoGeneratedIdResponseWriter(super.cloneWithWriter(writer)); } @Override public void writeAttribute(String name, Object value, String property) throws IOException { if (value != null && "id".equals(name)) { String id = value.toString(); if (id.startsWith(UNIQUE_ID_PREFIX) || id.contains(intermediateIdPrefix)) { int end = id.indexOf(separatorChar, id.indexOf(UNIQUE_ID_PREFIX)); if (end > 0) { id = id.substring(0, end); } UIComponent component = findComponentRelatively(Utils.coalesce(getCurrentComponent(), getViewRoot()), id); if (!(component instanceof UIViewRoot)) { // Skip viewstate hidden inputs. if (component == null) { logger.warning(format(ERROR_AUTO_GENERATED_ID_ENCOUNTERED, id, "")); } else { String componentType = component.getClass().getName() + ":" + component.getRendererType(); throw new IllegalStateException(format(ERROR_AUTO_GENERATED_ID_ENCOUNTERED, id, componentType)); } } } } super.writeAttribute(name, value, property); } @Override public ResponseWriter getWrapped() { return wrapped; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy