Skip to content

Instantly share code, notes, and snippets.

@fastnsilver
Created August 26, 2014 14:43
Show Gist options
  • Save fastnsilver/8624edba8415ce41a030 to your computer and use it in GitHub Desktop.
Save fastnsilver/8624edba8415ce41a030 to your computer and use it in GitHub Desktop.
VaadinView and SpringViewProvider enhanced to discriminate version and name
/*
* 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>
* &#64;VaadinUI
* public class MyUI extends UI {
*
* &#64;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 (petter@vaadin.com)
* @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);
}
}
/*
* 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>
* &#64;VaadinView(name = "")
* &#64;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>
* &#64;VaadinView(name = "myView", ui = MyUI.class)
* &#64;UIScope
* public class MyView extends CustomComponent implements View {
* // ...
* }
* </pre>
*
* @author Petter Holmström (petter@vaadin.com)
*/
@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