blob: fa1fafb161f890627ec1197c9e73724bc19020c9 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.myfaces.view.facelets.impl;
import java.io.IOException;
import java.net.URL;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import jakarta.faces.view.facelets.FaceletContext;
import jakarta.faces.view.facelets.FaceletException;
import org.apache.myfaces.resource.ResourceLoaderUtils;
import org.apache.myfaces.util.lang.Assert;
import org.apache.myfaces.view.facelets.AbstractFaceletCache;
import org.apache.myfaces.view.facelets.AbstractFaceletContext;
/**
* Extended MyFaces specific FaceletCache implementation that recompile
* facelet instance when a template context param is found.
*
* @author Leonardo Uribe
* @since 2.1.0
*
*/
class CacheELFaceletCacheImpl extends AbstractFaceletCache<DefaultFacelet>
{
private static final long INFINITE_DELAY = -1;
private static final long NO_CACHE_DELAY = 0;
/**
* FaceletNode is necessary only here, because view metadata and
* composite component metadata are special and does not allow use nested
* template tags. View metadata facelet trims everything outside f:metadata
* and composite component metadata only takes into account composite:xxx tags,
* ignoring ui:xxx tags.
*/
private Map<String, FaceletNode> _facelets;
private Map<String, DefaultFacelet> _viewMetadataFacelets;
private Map<String, DefaultFacelet> _compositeComponentMetadataFacelets;
private long _refreshPeriod;
CacheELFaceletCacheImpl(long refreshPeriod)
{
_refreshPeriod = refreshPeriod < 0 ? INFINITE_DELAY : refreshPeriod * 1000;
_facelets = new ConcurrentHashMap<>();
_viewMetadataFacelets = new ConcurrentHashMap<>();
_compositeComponentMetadataFacelets = new ConcurrentHashMap<>();
}
@Override
public DefaultFacelet getFacelet(URL url) throws IOException
{
Assert.notNull(url, "url");
String key = url.toString();
FaceletNode node = _facelets.get(key);
DefaultFacelet f = node != null ? node.getFacelet() : null;
if (f == null || this.needsToBeRefreshed(f))
{
Set<String> paramsSet = null;
if (node != null)
{
paramsSet = node.getParams();
}
f = getMemberFactory().newInstance(url);
if (_refreshPeriod != NO_CACHE_DELAY)
{
_facelets.put(key, (paramsSet != null && !paramsSet.isEmpty()) ?
new FaceletNode(f, paramsSet) : new FaceletNode(f) );
}
}
return f;
}
@Override
public DefaultFacelet getFacelet(FaceletContext ctx, URL url) throws IOException
{
String key = url.toString();
//1. Check that the current parameters on the template are known
// for the template.
//2. If all current parameters are known return the template
//2. If some current parameter is not known, add the param(s) to the
// template, register the known params in the template context and
// recompile the facelet, to clean up al EL expressions at once.
FaceletNode node = _facelets.get(key);
DefaultFacelet f = (node != null) ? node.getFacelet() : null;
Set<String> paramsSet = Collections.emptySet();
paramsSet = (node != null) ? node.getParams() : paramsSet;
AbstractFaceletContext actx = (AbstractFaceletContext) ctx;
Set<String> knownParameters = actx.getTemplateContext().isKnownParametersEmpty() ?
(Set) Collections.emptySet() : actx.getTemplateContext().getKnownParameters();
boolean create = false;
for (String paramKey : knownParameters)
{
if (!paramsSet.contains(paramKey))
{
create = true;
break;
}
}
if (f == null || this.needsToBeRefreshed(f) || create)
{
f = getMemberFactory().newInstance(url);
if (_refreshPeriod != NO_CACHE_DELAY)
{
if (!paramsSet.isEmpty()|| !knownParameters.isEmpty() )
{
paramsSet = new HashSet(paramsSet);
paramsSet.addAll(knownParameters);
_facelets.put(key, new FaceletNode(f, paramsSet));
}
else
{
_facelets.put(key, new FaceletNode(f));
}
}
}
if (!paramsSet.isEmpty())
{
for (String param : paramsSet)
{
if (!actx.getTemplateContext().containsKnownParameter(param))
{
actx.getTemplateContext().addKnownParameters(param);
}
}
}
return f;
}
@Override
public boolean isFaceletCached(URL url)
{
return _facelets.containsKey(url.toString());
}
@Override
public DefaultFacelet getViewMetadataFacelet(URL url) throws IOException
{
Assert.notNull(url, "url");
String key = url.toString();
DefaultFacelet f = _viewMetadataFacelets.get(key);
if (f == null || this.needsToBeRefreshed(f))
{
f = getMetadataMemberFactory().newInstance(url);
if (_refreshPeriod != NO_CACHE_DELAY)
{
_viewMetadataFacelets.put(key, f);
}
}
return f;
}
@Override
public boolean isViewMetadataFaceletCached(URL url)
{
return _viewMetadataFacelets.containsKey(url.toString());
}
/**
* Template method for determining if the Facelet needs to be refreshed.
*
* @param facelet
* Facelet that could have expired
* @return true if it needs to be refreshed
*/
protected boolean needsToBeRefreshed(DefaultFacelet facelet)
{
// if set to 0, constantly reload-- nocache
if (_refreshPeriod == NO_CACHE_DELAY)
{
return true;
}
// if set to -1, never reload
if (_refreshPeriod == INFINITE_DELAY)
{
return false;
}
long target = facelet.getCreateTime() + _refreshPeriod;
if (System.currentTimeMillis() > target)
{
// Should check for file modification
try
{
long lastModified = ResourceLoaderUtils.getResourceLastModified(facelet.getSource());
return lastModified == 0 || lastModified > target;
}
catch (IOException e)
{
throw new FaceletException("Error Checking Last Modified for " + facelet.getAlias(), e);
}
}
return false;
}
@Override
public DefaultFacelet getCompositeComponentMetadataFacelet(URL url) throws IOException
{
Assert.notNull(url, "url");
String key = url.toString();
DefaultFacelet f = _compositeComponentMetadataFacelets.get(key);
if (f == null || this.needsToBeRefreshed(f))
{
f = getCompositeComponentMetadataMemberFactory().newInstance(url);
if (_refreshPeriod != NO_CACHE_DELAY)
{
_compositeComponentMetadataFacelets.put(key, f);
}
}
return f;
}
@Override
public boolean isCompositeComponentMetadataFaceletCached(URL url)
{
return _compositeComponentMetadataFacelets.containsKey(url.toString());
}
private static class FaceletNode
{
private DefaultFacelet facelet;
private Set<String> params;
public FaceletNode(DefaultFacelet facelet)
{
this.facelet = facelet;
this.params = Collections.emptySet();
}
public FaceletNode(DefaultFacelet facelet, Set<String> params)
{
this.facelet = facelet;
this.params = params;
}
public DefaultFacelet getFacelet()
{
return facelet;
}
public void setFacelet(DefaultFacelet facelet)
{
this.facelet = facelet;
}
public Set<String> getParams()
{
return params;
}
public void setParams(Set<String> params)
{
this.params = params;
}
}
}