/* * JBoss, Home of Professional Open Source * Copyright 2011, Red Hat, Inc., and individual contributors * by the @authors tag. See the copyright.txt in the distribution for a * full listing of individual contributors. * * 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.jboss.seam.faces.view.config; import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import javax.enterprise.context.ApplicationScoped; import javax.inject.Inject; import org.jboss.seam.faces.security.SecurityPhaseListener; import org.jboss.solder.logging.Logger; /** * Data store for view specific data. * * @author Stuart Douglas * @author <a href="mailto:bleathem@gmail.com">Brian Leathem</a> */ @ApplicationScoped public class ViewConfigStoreImpl implements ViewConfigStore { private transient final Logger log = Logger.getLogger(SecurityPhaseListener.class); /** * cache of viewId to a given data list */ private final ConcurrentHashMap<Class<? extends Annotation>, ConcurrentHashMap<String, List<? extends Annotation>>> annotationCache = new ConcurrentHashMap<Class<? extends Annotation>, ConcurrentHashMap<String, List<? extends Annotation>>>(); private final ConcurrentHashMap<Class<? extends Annotation>, ConcurrentHashMap<String, List<? extends Annotation>>> qualifierCache = new ConcurrentHashMap<Class<? extends Annotation>, ConcurrentHashMap<String, List<? extends Annotation>>>(); private final ConcurrentHashMap<Class<? extends Annotation>, ConcurrentHashMap<String, Annotation>> viewPatternDataByAnnotation = new ConcurrentHashMap<Class<? extends Annotation>, ConcurrentHashMap<String, Annotation>>(); private final ConcurrentHashMap<Class<? extends Annotation>, ConcurrentHashMap<String, List<? extends Annotation>>> viewPatternDataByQualifier = new ConcurrentHashMap<Class<? extends Annotation>, ConcurrentHashMap<String, List<? extends Annotation>>>(); /** * setup the bean with the configuration from the extension * <p/> * It would be better if the extension could do this, but the extension cannot resolve the bean until after all lifecycle * events have been processed */ @Inject public void setup(ViewConfigExtension extension) { for (Entry<String, Set<Annotation>> e : extension.getData().entrySet()) { for (Annotation i : e.getValue()) { addAnnotationData(e.getKey(), i); } } } @Override public synchronized void addAnnotationData(String viewId, Annotation annotation) { ConcurrentHashMap<String, Annotation> annotationMap = viewPatternDataByAnnotation.get(annotation.annotationType()); if (annotationMap == null) { annotationMap = new ConcurrentHashMap<String, Annotation>(); viewPatternDataByAnnotation.put(annotation.annotationType(), annotationMap); log.debugf("Putting new annotation map for anotation type %s", annotation.annotationType().getName()); } annotationMap.put(viewId, annotation); log.debugf("Putting new annotation (type: %s) for viewId: %s", annotation.annotationType().getName(), viewId); Annotation[] annotations = annotation.annotationType().getAnnotations(); for (Annotation qualifier : annotations) { if (qualifier.annotationType().getName().startsWith("java.")) { log.debugf("Disregarding java.* package %s", qualifier.annotationType().getName()); continue; } ConcurrentHashMap<String, List<? extends Annotation>> qualifierMap = viewPatternDataByQualifier.get(qualifier .annotationType()); if (qualifierMap == null) { qualifierMap = new ConcurrentHashMap<String, List<? extends Annotation>>(); viewPatternDataByQualifier.put(qualifier.annotationType(), qualifierMap); log.debugf("Putting new qualifier map for qualifier type %s", qualifier.annotationType().getName()); } List<Annotation> qualifiedAnnotations = new ArrayList<Annotation>(); List<? extends Annotation> exisitngQualifiedAnnotations = qualifierMap.get(viewId); if (exisitngQualifiedAnnotations != null && !exisitngQualifiedAnnotations.isEmpty()) { qualifiedAnnotations.addAll(exisitngQualifiedAnnotations); } qualifiedAnnotations.add(annotation); log.debugf("Adding new annotation (type: %s) for viewId: %s and Qualifier %s", annotation.annotationType().getName(), viewId, qualifier.annotationType().getName()); qualifierMap.put(viewId, qualifiedAnnotations); } } @Override public <T extends Annotation> T getAnnotationData(String viewId, Class<T> type) { List<T> data = prepareAnnotationCache(viewId, type, annotationCache, viewPatternDataByAnnotation); if ((data != null) && (data.size() > 0)) { return data.get(0); } return null; } @Override public <T extends Annotation> List<T> getAllAnnotationData(String viewId, Class<T> type) { List<T> data = prepareAnnotationCache(viewId, type, annotationCache, viewPatternDataByAnnotation); if (data != null) { return Collections.unmodifiableList(data); } return null; } @Override public <T extends Annotation> Map<String, Annotation> getAllAnnotationViewMap(Class<T> type) { return viewPatternDataByAnnotation.get(type); } @Override public List<? extends Annotation> getAllQualifierData(String viewId, Class<? extends Annotation> qualifier) { List<? extends Annotation> data = prepareQualifierCache(viewId, qualifier, qualifierCache, viewPatternDataByQualifier); if (data != null) { return Collections.unmodifiableList(data); } return null; } private <T extends Annotation> List<T> prepareAnnotationCache(String viewId, Class<T> annotationType, ConcurrentHashMap<Class<? extends Annotation>, ConcurrentHashMap<String, List<? extends Annotation>>> cache, ConcurrentHashMap<Class<? extends Annotation>, ConcurrentHashMap<String, Annotation>> viewPatternData) { // we need to synchronize to make sure that no threads see a half // completed list due to instruction re-ordering ConcurrentHashMap<String, List<? extends Annotation>> map = cache.get(annotationType); if (map == null) { ConcurrentHashMap<String, List<? extends Annotation>> newMap = new ConcurrentHashMap<String, List<? extends Annotation>>(); map = cache.putIfAbsent(annotationType, newMap); if (map == null) { map = newMap; } } List<? extends Annotation> annotationData = map.get(viewId); if (annotationData == null) { List<Annotation> newList = new ArrayList<Annotation>(); Map<String, Annotation> viewPatterns = viewPatternData.get(annotationType); if (viewPatterns != null) { List<String> resultingViews = findViewsWithPatternsThatMatch(viewId, viewPatterns.keySet()); for (String i : resultingViews) { newList.add(viewPatterns.get(i)); } } annotationData = map.putIfAbsent(viewId, newList); if (annotationData == null) { annotationData = newList; } } return (List) annotationData; } private <T extends Annotation> List<T> prepareQualifierCache( String viewId, Class<T> qualifierType, ConcurrentHashMap<Class<? extends Annotation>, ConcurrentHashMap<String, List<? extends Annotation>>> cache, ConcurrentHashMap<Class<? extends Annotation>, ConcurrentHashMap<String, List<? extends Annotation>>> viewPatternData) { // we need to synchronize to make sure that no threads see a half // completed list due to instruction re-ordering ConcurrentHashMap<String, List<? extends Annotation>> map = cache.get(qualifierType); if (map == null) { ConcurrentHashMap<String, List<? extends Annotation>> newMap = new ConcurrentHashMap<String, List<? extends Annotation>>(); map = cache.putIfAbsent(qualifierType, newMap); if (map == null) { map = newMap; } } List<? extends Annotation> annotationData = map.get(viewId); if (annotationData == null) { List<Annotation> newList = new ArrayList<Annotation>(); Map<String, List<? extends Annotation>> viewPatterns = viewPatternData.get(qualifierType); if (viewPatterns != null) { List<String> resultingViews = findViewsWithPatternsThatMatch(viewId, viewPatterns.keySet()); for (String i : resultingViews) { newList.addAll(viewPatterns.get(i)); } } Collections.sort(newList, new AnnotationNameComparator()); annotationData = map.putIfAbsent(viewId, newList); if (annotationData == null) { annotationData = newList; } } return (List) annotationData; } private List<String> findViewsWithPatternsThatMatch(String viewId, Set<String> viewPatterns) { List<String> resultingViews = new ArrayList<String>(); for (String viewPattern : viewPatterns) { if (viewPattern.endsWith("*")) { String cutView = viewPattern.substring(0, viewPattern.length() - 1); if (viewId.startsWith(cutView)) { resultingViews.add(viewPattern); } } else { if (viewPattern.equals(viewId)) { resultingViews.add(viewPattern); } } } // sort the keys by length, longest is the most specific and so should go first Collections.sort(resultingViews, StringLengthComparator.INSTANCE); return resultingViews; } private static class StringLengthComparator implements Comparator<String> { @Override public int compare(String o1, String o2) { if (o1.length() > o2.length()) { return -1; } if (o1.length() < o2.length()) { return 1; } return 0; } public static final StringLengthComparator INSTANCE = new StringLengthComparator(); } private static class AnnotationNameComparator implements Comparator<Annotation> { @Override public int compare(Annotation o1, Annotation o2) { return o1.annotationType().getName().compareTo(o2.annotationType().getName()); } public static final StringLengthComparator INSTANCE = new StringLengthComparator(); } }