Created
August 26, 2014 14:43
-
-
Save fastnsilver/8624edba8415ce41a030 to your computer and use it in GitHub Desktop.
VaadinView and SpringViewProvider enhanced to discriminate version and name
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright 2014 The original authors | |
* | |
* 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.vaadin.spring.navigator; | |
import com.vaadin.navigator.View; | |
import com.vaadin.navigator.ViewProvider; | |
import com.vaadin.ui.UI; | |
import org.slf4j.Logger; | |
import org.slf4j.LoggerFactory; | |
import org.springframework.beans.factory.NoSuchBeanDefinitionException; | |
import org.springframework.beans.factory.annotation.Autowired; | |
import org.springframework.context.ApplicationContext; | |
import org.springframework.util.Assert; | |
import javax.annotation.PostConstruct; | |
import java.util.Map; | |
import java.util.Set; | |
import java.util.concurrent.ConcurrentHashMap; | |
import java.util.concurrent.ConcurrentSkipListSet; | |
/** | |
* A Vaadin {@link ViewProvider} that fetches the views from the Spring application context. The views | |
* must implement the {@link View} interface and be annotated with the {@link VaadinView} annotation. | |
* <p> | |
* Use like this: | |
* <pre> | |
* @VaadinUI | |
* public class MyUI extends UI { | |
* | |
* @Autowired SpringViewProvider viewProvider; | |
* | |
* protected void init(VaadinRequest vaadinRequest) { | |
* Navigator navigator = new Navigator(this, this); | |
* navigator.addProvider(viewProvider); | |
* setNavigator(navigator); | |
* // ... | |
* } | |
* } | |
* </pre> | |
* | |
* View-based security can be provided by creating a Spring bean that implements the {@link org.vaadin.spring.navigator.SpringViewProvider.ViewProviderAccessDelegate} interface. | |
* | |
* @author Petter Holmström ([email protected]) | |
* @see VaadinView | |
*/ | |
public class SpringViewProvider implements ViewProvider { | |
/* | |
* Note! This is a singleton bean! | |
*/ | |
// We can have multiple views with the same view name, as long as they belong to different UI subclasses | |
private final Map<String, Set<String>> viewNameToBeanNamesMap = new ConcurrentHashMap<>(); | |
private final ApplicationContext applicationContext; | |
private final Logger logger = LoggerFactory.getLogger(getClass()); | |
@Autowired | |
public SpringViewProvider(ApplicationContext applicationContext) { | |
this.applicationContext = applicationContext; | |
} | |
@PostConstruct | |
void init() { | |
logger.info("Looking up VaadinViews"); | |
int count = 0; | |
final String[] viewBeanNames = applicationContext.getBeanNamesForAnnotation(VaadinView.class); | |
for (String beanName : viewBeanNames) { | |
final Class<?> type = applicationContext.getType(beanName); | |
if (View.class.isAssignableFrom(type)) { | |
final VaadinView annotation = applicationContext.findAnnotationOnBean(beanName, VaadinView.class); | |
final String viewName = annotation.name(); | |
final String version = annotation.version(); | |
StringBuilder msg = new StringBuilder(); | |
msg.append("Found VaadinView bean [{}] with view name [{}]"); | |
if (!version.isEmpty()) { | |
msg.append(" and version [{}]"); | |
logger.debug(msg.toString(), beanName, viewName, version); | |
} else { | |
logger.debug(msg.toString(), beanName, viewName); | |
} | |
if (applicationContext.isSingleton(beanName)) { | |
throw new IllegalStateException("VaadinView bean [" + beanName + "] must not be a singleton"); | |
} | |
String viewId = null; | |
if (version.isEmpty()) { | |
viewId = viewName; | |
} else { | |
viewId = viewName + ":" + version; | |
} | |
Set<String> beanNames = viewNameToBeanNamesMap.get(viewId); | |
if (beanNames == null) { | |
beanNames = new ConcurrentSkipListSet<>(); | |
viewNameToBeanNamesMap.put(viewId, beanNames); | |
} | |
beanNames.add(beanName); | |
count++; | |
} | |
} | |
if (count == 0) { | |
logger.warn("No VaadinViews found"); | |
} else if (count == 1) { | |
logger.info("1 VaadinView found"); | |
} else { | |
logger.info("{} VaadinViews found", count); | |
} | |
} | |
@Override | |
public String getViewName(String viewAndParameters) { | |
logger.trace("Extracting view name from [{}]", viewAndParameters); | |
String viewName = null; | |
if (isViewNameValidForCurrentUI(viewAndParameters)) { | |
viewName = viewAndParameters; | |
} else { | |
int lastSlash = -1; | |
String viewPart = viewAndParameters; | |
while ((lastSlash = viewPart.lastIndexOf('/')) > -1) { | |
viewPart = viewPart.substring(0, lastSlash); | |
logger.trace("Checking if [{}] is a valid view", viewPart); | |
if (isViewNameValidForCurrentUI(viewPart)) { | |
viewName = viewPart; | |
break; | |
} | |
} | |
} | |
if (viewName == null) { | |
logger.trace("Found no view name in [{}]", viewAndParameters); | |
} else { | |
logger.trace("[{}] is a valid view", viewName); | |
} | |
return viewName; | |
} | |
private boolean isViewNameValidForCurrentUI(String viewId) { | |
final Set<String> beanNames = viewNameToBeanNamesMap.get(viewId); | |
if (beanNames != null) { | |
for (String beanName : beanNames) { | |
if (isViewBeanNameValidForCurrentUI(beanName)) { | |
return true; | |
} | |
} | |
} | |
return false; | |
} | |
private boolean isViewBeanNameValidForCurrentUI(String beanName) { | |
try { | |
final Class<?> type = applicationContext.getType(beanName); | |
Assert.isAssignable(View.class, type, "bean did not implement View interface"); | |
final UI currentUI = UI.getCurrent(); | |
final VaadinView annotation = applicationContext.findAnnotationOnBean(beanName, VaadinView.class); | |
Assert.notNull(annotation, "class did not have a VaadinView annotation"); | |
final Map<String, ViewProviderAccessDelegate> accessDelegates = applicationContext.getBeansOfType(ViewProviderAccessDelegate.class); | |
for (ViewProviderAccessDelegate accessDelegate : accessDelegates.values()) { | |
if (!accessDelegate.isAccessGranted(beanName, currentUI)) { | |
logger.debug("Access delegate [{}] denied access to view class [{}]", accessDelegate, type.getCanonicalName()); | |
return false; | |
} | |
} | |
if (annotation.ui().length == 0) { | |
StringBuilder msg = new StringBuilder(); | |
msg.append("View class [{}] with view name [{}]"); | |
if (!annotation.version().isEmpty()) { | |
msg.append(" and version [{}] is available for all UI subclasses"); | |
logger.trace(msg.toString(), type.getCanonicalName(), annotation.name(), annotation.version()); | |
} else { | |
msg.append(" is available for all UI subclasses"); | |
logger.trace(msg.toString(), type.getCanonicalName(), annotation.name()); | |
} | |
return true; | |
} else { | |
for (Class<? extends UI> validUI : annotation.ui()) { | |
if (validUI == currentUI.getClass()) { | |
StringBuilder msg = new StringBuilder(); | |
msg.append("View class [%s] with view name [{}]"); | |
if (!annotation.version().isEmpty()) { | |
msg.append(" and version [{}] available for UI subclass [{}]"); | |
logger.trace(msg.toString(), type.getCanonicalName(), annotation.name(), annotation.version(), validUI.getCanonicalName()); | |
} else { | |
msg.append(" is available for UI subclass [{}]"); | |
logger.trace(msg.toString(), type.getCanonicalName(), annotation.name(), validUI.getCanonicalName()); | |
} | |
return true; | |
} | |
} | |
} | |
return false; | |
} catch (NoSuchBeanDefinitionException ex) { | |
return false; | |
} | |
} | |
@Override | |
// id in this case is either name or if non-empty version, the concatenation of name and version | |
public View getView(String viewId) { | |
final Set<String> beanNames = viewNameToBeanNamesMap.get(viewId); | |
if (beanNames != null) { | |
for (String beanName : beanNames) { | |
if (isViewBeanNameValidForCurrentUI(beanName)) { | |
return (View) applicationContext.getBean(beanName); | |
} | |
} | |
} | |
logger.warn("Found no view with id [{}]", viewId); | |
return null; | |
} | |
/** | |
* Interface to be implemented by Spring beans that will be consulted before the Spring View provider | |
* provides a view. If any of the view providers deny access, the view provider will act like no such | |
* view ever existed. | |
*/ | |
public interface ViewProviderAccessDelegate { | |
/** | |
* Checks if the current user has access to the specified view and UI. | |
* | |
* @param beanName the bean name of the view, never {@code null}. | |
* @param ui the UI, never {@code null}. | |
* @return true if access is granted, false if access is denied. | |
*/ | |
boolean isAccessGranted(String beanName, UI ui); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright 2014 The original authors | |
* | |
* 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.vaadin.spring.navigator; | |
import com.vaadin.ui.UI; | |
import org.vaadin.spring.VaadinComponent; | |
import java.lang.annotation.Documented; | |
import java.lang.annotation.Retention; | |
import java.lang.annotation.Target; | |
/** | |
* Annotation to be placed on {@link com.vaadin.navigator.View}-classes that should be | |
* handled by the {@link SpringViewProvider}. | |
* <p> | |
* This annotation is also a stereotype annotation, so Spring will automatically detect the annotated classes. | |
* <b>However, the scope must be explicitly specified as the default singleton scope will not work!</b> You can use | |
* the {@code prototype} scope or the {@link org.vaadin.spring.UIScope ui} scope. | |
* <p> | |
* This is an example of a view that is mapped to an empty view name and is available for all UI subclasses in the application: | |
* <pre> | |
* @VaadinView(name = "") | |
* @UIScope | |
* public class MyDefaultView extends CustomComponent implements View { | |
* // ... | |
* } | |
* </pre> | |
* This is an example of a view that is only available to a specified UI subclass: | |
* <pre> | |
* @VaadinView(name = "myView", ui = MyUI.class) | |
* @UIScope | |
* public class MyView extends CustomComponent implements View { | |
* // ... | |
* } | |
* </pre> | |
* | |
* @author Petter Holmström ([email protected]) | |
*/ | |
@Target({java.lang.annotation.ElementType.TYPE}) | |
@Retention(java.lang.annotation.RetentionPolicy.RUNTIME) | |
@Documented | |
@VaadinComponent | |
public @interface VaadinView { | |
/** | |
* The name of the view. This is the name that is to be passed to the | |
* {@link com.vaadin.navigator.Navigator} when navigating to the view. There can be multiple views | |
* with the same name as long as they belong to separate UI subclasses. | |
* | |
* @see #ui() | |
*/ | |
String name(); | |
/** | |
* Optional version, used to discriminate view. If declared, the annotated | |
* {@link com.vaadin.navigator.View} id is the concatenation of name and version. | |
*/ | |
String version() default ""; | |
/** | |
* By default, the view will be available for all UI subclasses in the application. This attribute can be used | |
* to explicitly specify which subclass (or subclasses) that the view belongs to. | |
*/ | |
Class<? extends UI>[] ui() default {}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment