/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2016 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package com.sun.faces.component.search;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.faces.FacesException;
import javax.faces.component.ContextCallback;
import javax.faces.component.UIComponent;
import javax.faces.component.search.ComponentNotFoundException;
import javax.faces.component.search.SearchExpressionContext;
import javax.faces.component.search.SearchExpressionHandler;
import javax.faces.component.search.SearchExpressionHint;
import javax.faces.component.search.SearchKeywordContext;
import javax.faces.component.search.SearchKeywordResolver;
import javax.faces.context.FacesContext;
public class SearchExpressionHandlerImpl extends SearchExpressionHandler {
protected void addHint(SearchExpressionContext searchExpressionContext, SearchExpressionHint hint) {
// It is a Set already
if (!searchExpressionContext.getExpressionHints().contains(hint)) {
searchExpressionContext.getExpressionHints().add(hint);
}
}
@Override
public String resolveClientId(SearchExpressionContext searchExpressionContext, String expression) {
if (expression == null) {
expression = "";
} else {
expression = expression.trim();
}
addHint(searchExpressionContext, SearchExpressionHint.RESOLVE_SINGLE_COMPONENT);
FacesContext facesContext = searchExpressionContext.getFacesContext();
SearchExpressionHandler handler = facesContext.getApplication().getSearchExpressionHandler();
if (!expression.isEmpty() && handler.isPassthroughExpression(searchExpressionContext, expression)) {
return expression;
}
ResolveClientIdCallback internalCallback = new ResolveClientIdCallback();
if (!expression.isEmpty()) {
handler.invokeOnComponent(searchExpressionContext, expression, internalCallback);
}
String clientId = internalCallback.getClientId();
if (clientId == null && !isHintSet(searchExpressionContext, SearchExpressionHint.IGNORE_NO_RESULT)) {
throw new ComponentNotFoundException("Cannot find component for expression \""
+ expression + "\" referenced from \""
+ searchExpressionContext.getSource().getClientId(facesContext) + "\".");
}
return clientId;
}
private static class ResolveClientIdCallback implements ContextCallback {
private String clientId = null;
@Override
public void invokeContextCallback(FacesContext context, UIComponent target) {
if (clientId == null) {
clientId = target.getClientId(context);
}
}
public String getClientId() {
return clientId;
}
}
@Override
public List<String> resolveClientIds(SearchExpressionContext searchExpressionContext, String expressions) {
if (expressions == null) {
expressions = "";
} else {
expressions = expressions.trim();
}
FacesContext facesContext = searchExpressionContext.getFacesContext();
SearchExpressionHandler handler = facesContext.getApplication().getSearchExpressionHandler();
ResolveClientIdsCallback internalCallback = new ResolveClientIdsCallback();
if (!expressions.isEmpty()) {
for (String expression : handler.splitExpressions(facesContext, expressions)) {
if (handler.isPassthroughExpression(searchExpressionContext, expression)) {
internalCallback.addClientId(expression);
} else {
handler.invokeOnComponent(searchExpressionContext, expression, internalCallback);
}
}
}
if (internalCallback.getClientIds() == null && !isHintSet(searchExpressionContext, SearchExpressionHint.IGNORE_NO_RESULT)) {
throw new ComponentNotFoundException("Cannot find component for expressions \""
+ expressions + "\" referenced from \""
+ searchExpressionContext.getSource().getClientId(facesContext) + "\".");
}
List<String> clientIds = internalCallback.getClientIds();
if (clientIds == null) {
clientIds = Collections.emptyList();
}
return clientIds;
}
private static class ResolveClientIdsCallback implements ContextCallback {
private List<String> clientIds = null;
@Override
public void invokeContextCallback(FacesContext context, UIComponent target) {
addClientId(target.getClientId(context));
}
public List<String> getClientIds() {
return clientIds;
}
public void addClientId(String clientId) {
if (clientIds == null) {
clientIds = new ArrayList<>();
}
clientIds.add(clientId);
}
}
@Override
public void resolveComponent(SearchExpressionContext searchExpressionContext, String expression, ContextCallback callback) {
if (expression != null) {
expression = expression.trim();
}
addHint(searchExpressionContext, SearchExpressionHint.RESOLVE_SINGLE_COMPONENT);
FacesContext facesContext = searchExpressionContext.getFacesContext();
SearchExpressionHandler handler = facesContext.getApplication().getSearchExpressionHandler();
ResolveComponentCallback internalCallback = new ResolveComponentCallback(callback);
handler.invokeOnComponent(searchExpressionContext, expression, internalCallback);
if (!internalCallback.isInvoked() && !isHintSet(searchExpressionContext, SearchExpressionHint.IGNORE_NO_RESULT)) {
throw new ComponentNotFoundException("Cannot find component for expression \""
+ expression + "\" referenced from \""
+ searchExpressionContext.getSource().getClientId(facesContext) + "\".");
}
}
private static class ResolveComponentCallback implements ContextCallback {
private final ContextCallback callback;
private boolean invoked;
public ResolveComponentCallback(ContextCallback callback) {
this.callback = callback;
this.invoked = false;
}
@Override
public void invokeContextCallback(FacesContext context, UIComponent target) {
if (!isInvoked()) {
invoked = true;
callback.invokeContextCallback(context, target);
}
}
public boolean isInvoked() {
return invoked;
}
}
@Override
public void resolveComponents(SearchExpressionContext searchExpressionContext, String expressions, ContextCallback callback) {
if (expressions != null) {
expressions = expressions.trim();
}
FacesContext facesContext = searchExpressionContext.getFacesContext();
SearchExpressionHandler handler = facesContext.getApplication().getSearchExpressionHandler();
ResolveComponentsCallback internalCallback = new ResolveComponentsCallback(callback);
if (expressions != null) {
for (String expression : handler.splitExpressions(facesContext, expressions)) {
handler.invokeOnComponent(searchExpressionContext, expression, internalCallback);
}
}
if (!internalCallback.isInvoked() && !isHintSet(searchExpressionContext, SearchExpressionHint.IGNORE_NO_RESULT)) {
throw new ComponentNotFoundException("Cannot find component for expressions \""
+ expressions + "\" referenced from \""
+ searchExpressionContext.getSource().getClientId(facesContext) + "\".");
}
}
private static class ResolveComponentsCallback implements ContextCallback {
private final ContextCallback callback;
private boolean invoked;
public ResolveComponentsCallback(ContextCallback callback) {
this.callback = callback;
this.invoked = false;
}
@Override
public void invokeContextCallback(FacesContext context, UIComponent target) {
invoked = true;
callback.invokeContextCallback(context, target);
}
public boolean isInvoked() {
return invoked;
}
}
@Override
public void invokeOnComponent(SearchExpressionContext searchExpressionContext, UIComponent previous, String expression, final ContextCallback callback) {
if (expression == null || previous == null) {
return;
}
expression = expression.trim();
FacesContext facesContext = searchExpressionContext.getFacesContext();
SearchExpressionHandler handler = facesContext.getApplication().getSearchExpressionHandler();
// contains keyword? If not, just try findComponent and don't apply our algorithm
if (expression.contains(KEYWORD_PREFIX)) {
// absolute expression and keyword as first command -> try again from ViewRoot
char separatorChar = facesContext.getNamingContainerSeparatorChar();
if (expression.charAt(0) == separatorChar && expression.charAt(1) == KEYWORD_PREFIX.charAt(0)) {
handler.invokeOnComponent(searchExpressionContext, facesContext.getViewRoot(), expression.substring(1), callback);
return;
}
String command = extractFirstCommand(facesContext, expression);
// check if there are remaining keywords/id's after the first command
String remainingExpression = null;
if (command.length() < expression.length()) {
remainingExpression = expression.substring(command.length() + 1);
}
if (command.startsWith(KEYWORD_PREFIX)) {
String keyword = command.substring(KEYWORD_PREFIX.length());
if (remainingExpression == null) {
invokeKeywordResolvers(searchExpressionContext, previous, keyword, null, callback);
} else {
if (facesContext.getApplication().getSearchKeywordResolver().isLeaf(searchExpressionContext, keyword)) {
throw new FacesException("It's not valid to place a keyword or id after a leaf keyword: "
+ KEYWORD_PREFIX + keyword + ". Expression: " + expression);
}
final String finalRemainingExpression = remainingExpression;
invokeKeywordResolvers(searchExpressionContext, previous, keyword, remainingExpression, new ContextCallback() {
@Override
public void invokeContextCallback(FacesContext facesContext, UIComponent target) {
handler.invokeOnComponent(searchExpressionContext, target, finalRemainingExpression, callback);
}
});
}
} else {
String id = command;
UIComponent target = previous.findComponent(id);
if (target != null) {
if (remainingExpression == null) {
callback.invokeContextCallback(facesContext, target);
} else {
handler.invokeOnComponent(searchExpressionContext, target, remainingExpression, callback);
}
}
}
}
else {
UIComponent target = previous.findComponent(expression);
if (target != null) {
callback.invokeContextCallback(facesContext, target);
} else if (!isHintSet(searchExpressionContext, SearchExpressionHint.SKIP_VIRTUAL_COMPONENTS)) {
// fallback
// invokeOnComponent doesnt work with the leading ':'
char separatorChar = facesContext.getNamingContainerSeparatorChar();
if (expression.charAt(0) == separatorChar) {
expression = expression.substring(1);
}
facesContext.getViewRoot().invokeOnComponent(facesContext, expression, callback);
}
}
}
protected void invokeKeywordResolvers(SearchExpressionContext searchExpressionContext, UIComponent previous,
String keyword, String remainingExpression, ContextCallback callback)
{
// take the keyword and resolve it using the chain of responsibility pattern.
SearchKeywordContext searchContext = new SearchKeywordContext(searchExpressionContext, callback, remainingExpression);
searchExpressionContext.getFacesContext().getApplication()
.getSearchKeywordResolver().resolve(searchContext, previous, keyword);
}
@Override
public String[] splitExpressions(FacesContext context, String expressions) {
// we can't use a split(",") or split(" ") as keyword parameters might contain spaces or commas
List<String> tokens = new ArrayList<>();
StringBuilder buffer = new StringBuilder();
char[] separators = getExpressionSeperatorChars(context);
int parenthesesCounter = 0;
char[] charArray = expressions.toCharArray();
for (char c : charArray) {
if (c == '(') {
parenthesesCounter++;
}
if (c == ')') {
parenthesesCounter--;
}
if (parenthesesCounter == 0) {
boolean isSeparator = false;
for (char separator : separators) {
if (c == separator) {
isSeparator = true;
}
}
if (isSeparator) {
// lets add token inside buffer to our tokens
String bufferAsString = buffer.toString().trim();
if (bufferAsString.length() > 0) {
tokens.add(bufferAsString);
}
// now we need to clear buffer
buffer.delete(0, buffer.length());
} else {
buffer.append(c);
}
} else {
buffer.append(c);
}
}
// lets not forget about part after the separator
tokens.add(buffer.toString());
return tokens.toArray(new String[tokens.size()]);
}
@Override
public boolean isPassthroughExpression(SearchExpressionContext searchExpressionContext, String expression) {
if (expression != null) {
expression = expression.trim();
}
if (expression != null && expression.contains(KEYWORD_PREFIX)) {
FacesContext facesContext = searchExpressionContext.getFacesContext();
String command = extractFirstCommand(facesContext, expression);
// check if there are remaining commands/id's after the first command
String remainingExpression = null;
if (command.length() < expression.length()) {
remainingExpression = expression.substring(command.length() + 1);
}
if (command.startsWith(KEYWORD_PREFIX) && remainingExpression == null) {
String keyword = command.substring(KEYWORD_PREFIX.length());
SearchKeywordResolver keywordResolver = facesContext.getApplication().getSearchKeywordResolver();
return keywordResolver.isPassthrough(searchExpressionContext, keyword);
}
// check again the remainingExpression
SearchExpressionHandler handler = facesContext.getApplication().getSearchExpressionHandler();
return handler.isPassthroughExpression(searchExpressionContext, remainingExpression);
}
return false;
}
@Override
public boolean isValidExpression(SearchExpressionContext searchExpressionContext, String expression) {
if (expression != null) {
expression = expression.trim();
}
if (expression == null || expression.isEmpty()) {
return true;
}
if (expression.contains(KEYWORD_PREFIX)) {
FacesContext facesContext = searchExpressionContext.getFacesContext();
SearchExpressionHandler handler = facesContext.getApplication().getSearchExpressionHandler();
// absolute expression and keyword as first command -> try again from ViewRoot
char separatorChar = facesContext.getNamingContainerSeparatorChar();
if (expression.charAt(0) == separatorChar) {
expression = expression.substring(1);
}
String command = extractFirstCommand(facesContext, expression);
// check if there are remaining commands/id's after the first command
String remainingExpression = null;
if (command.length() < expression.length()) {
remainingExpression = expression.substring(command.length() + 1);
}
if (command.startsWith(KEYWORD_PREFIX)) {
String keyword = command.substring(KEYWORD_PREFIX.length());
// resolver for keyword available?
SearchKeywordResolver keywordResolver = facesContext.getApplication().getSearchKeywordResolver();
if (!keywordResolver.isResolverForKeyword(searchExpressionContext, keyword)) {
return false;
}
if (remainingExpression != null && !remainingExpression.trim().isEmpty()) {
// there is remaingExpression avialable but the current keyword is leaf -> invalid
if (keywordResolver.isLeaf(searchExpressionContext, keyword)) {
return false;
}
return handler.isValidExpression(searchExpressionContext, remainingExpression);
}
}
else {
if (remainingExpression != null) {
return handler.isValidExpression(searchExpressionContext, remainingExpression);
}
}
}
return true;
}
protected boolean isHintSet(SearchExpressionContext searchExpressionContext, SearchExpressionHint hint) {
return searchExpressionContext.getExpressionHints().contains(hint);
}
/**
* Extract the first command from the expression.
* @child(1):myId => @child(1)
* myId:@parent => myId
*
* @param facesContext
* @param expression
* @return
*/
protected String extractFirstCommand(FacesContext facesContext, String expression) {
// we can't use a split(":") or split(" ") as keyword parameters might contain spaces or commas
int parenthesesCounter = -1;
int count = -1;
for (int i = 0; i < expression.length(); i++) {
char c = expression.charAt(i);
if (c == '(') {
if (parenthesesCounter == -1) {
parenthesesCounter = 0;
}
parenthesesCounter++;
}
if (c == ')') {
parenthesesCounter--;
}
if (parenthesesCounter == 0) {
//Close first parentheses
count = i+1;
break;
}
if (parenthesesCounter == -1) {
if (i > 0 && c == facesContext.getNamingContainerSeparatorChar()) {
count = i;
break;
}
}
}
if (count == -1) {
return expression;
} else {
return expression.substring(0, count);
}
}
}