You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

UIProvider.java 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. /*
  2. * Copyright 2000-2018 Vaadin Ltd.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.vaadin.server;
  17. import java.io.InputStream;
  18. import java.io.Serializable;
  19. import java.lang.annotation.Annotation;
  20. import java.lang.annotation.Inherited;
  21. import java.util.logging.Level;
  22. import java.util.logging.Logger;
  23. import com.vaadin.annotations.PreserveOnRefresh;
  24. import com.vaadin.annotations.Push;
  25. import com.vaadin.annotations.Theme;
  26. import com.vaadin.annotations.Title;
  27. import com.vaadin.annotations.Widgetset;
  28. import com.vaadin.shared.communication.PushMode;
  29. import com.vaadin.shared.ui.ui.Transport;
  30. import com.vaadin.ui.UI;
  31. import com.vaadin.util.ReflectTools;
  32. public abstract class UIProvider implements Serializable {
  33. /* Default widgetset name to look for */
  34. private static final String APP_WIDGETSET_NAME = "AppWidgetset";
  35. public abstract Class<? extends UI> getUIClass(UIClassSelectionEvent event);
  36. public UI createInstance(UICreateEvent event) {
  37. return ReflectTools.createInstance(event.getUIClass());
  38. }
  39. /**
  40. * Helper to get an annotation for a class. If the annotation is not present
  41. * on the target class, its super classes and directly implemented
  42. * interfaces are also searched for the annotation. Interfaces implemented
  43. * by superclasses are not taken into account.
  44. * <p>
  45. * Note that searching implemented interfaces for {@code @Inherited}
  46. * annotations and searching for superclasses for non-inherited annotations
  47. * do not follow the standard semantics and are supported for backwards
  48. * compatibility. Future versions of the framework might only support the
  49. * standard semantics of {@code @Inherited}.
  50. *
  51. * @param clazz
  52. * the class from which the annotation should be found
  53. * @param annotationType
  54. * the annotation type to look for
  55. * @return an annotation of the given type, or <code>null</code> if the
  56. * annotation is not present on the class
  57. */
  58. protected static <T extends Annotation> T getAnnotationFor(Class<?> clazz,
  59. Class<T> annotationType) {
  60. // Don't discover hierarchy if annotation is inherited
  61. if (annotationType.getAnnotation(Inherited.class) != null) {
  62. T annotation = clazz.getAnnotation(annotationType);
  63. if (annotation != null) {
  64. return annotation;
  65. }
  66. } else {
  67. // Find from the class hierarchy
  68. Class<?> currentType = clazz;
  69. while (currentType != Object.class) {
  70. T annotation = currentType.getAnnotation(annotationType);
  71. if (annotation != null) {
  72. return annotation;
  73. } else {
  74. currentType = currentType.getSuperclass();
  75. }
  76. }
  77. }
  78. // Find from a directly implemented interface
  79. for (Class<?> iface : clazz.getInterfaces()) {
  80. T annotation = iface.getAnnotation(annotationType);
  81. if (annotation != null) {
  82. return annotation;
  83. }
  84. }
  85. return null;
  86. }
  87. /**
  88. * Finds the theme to use for a specific UI. If no specific theme is
  89. * required, <code>null</code> is returned.
  90. * <p>
  91. * The default implementation checks for a @{@link Theme} annotation on the
  92. * UI class.
  93. *
  94. * @param event
  95. * the UI create event with information about the UI and the
  96. * current request.
  97. * @return the name of the theme, or <code>null</code> if the default theme
  98. * should be used
  99. *
  100. */
  101. public String getTheme(UICreateEvent event) {
  102. Theme uiTheme = getAnnotationFor(event.getUIClass(), Theme.class);
  103. if (uiTheme != null) {
  104. return uiTheme.value();
  105. } else {
  106. return null;
  107. }
  108. }
  109. /**
  110. * Finds the widgetset to use for a specific UI. If no specific widgetset is
  111. * required, <code>null</code> is returned.
  112. * <p>
  113. * This method uses the Widgetset definition priority order from
  114. * {@link #getWidgetsetInfo(UICreateEvent)}.
  115. * <p>
  116. * <strong>Note:</strong> This method exists only for backwards
  117. * compatibility and overriding it won't have the effect it used to.
  118. *
  119. * @param event
  120. * the UI create event with information about the UI and the
  121. * current request.
  122. * @return the name of the widgetset, or <code>null</code> if the default
  123. * widgetset should be used
  124. * @deprecated This method has been replaced by
  125. * {@link #getWidgetsetInfo(UICreateEvent)} in 7.7
  126. */
  127. @Deprecated
  128. public String getWidgetset(UICreateEvent event) {
  129. WidgetsetInfo widgetsetInfo = getWidgetsetInfo(event);
  130. return widgetsetInfo != null ? widgetsetInfo.getWidgetsetName() : null;
  131. }
  132. /**
  133. * Finds the widgetset to use for a specific UI. If no specific widgetset is
  134. * required, <code>null</code> is returned.
  135. * <p>
  136. * The default implementation uses the following order of priority for
  137. * finding the widgetset information:
  138. * <ul>
  139. * <li>@{@link Widgetset} annotation if it is defined for the UI class</li>
  140. * <li>The class AppWidgetset if one exists in the default package</li>
  141. * <li>A widgetset called AppWidgetset if there is an AppWidgetset.gwt.xml
  142. * file in the default package</li>
  143. * <li>null to use the default widgetset otherwise</li>
  144. * </ul>
  145. *
  146. * @since 7.7
  147. *
  148. * @param event
  149. * the UI create event with information about the UI and the
  150. * current request.
  151. * @return the widgetset info, or <code>null</code> if the default widgetset
  152. * should be used
  153. */
  154. public WidgetsetInfo getWidgetsetInfo(UICreateEvent event) {
  155. Widgetset uiWidgetset = getAnnotationFor(event.getUIClass(),
  156. Widgetset.class);
  157. // First case: We have an @Widgetset annotation, use that
  158. if (uiWidgetset != null) {
  159. return new WidgetsetInfoImpl(uiWidgetset.value());
  160. }
  161. // Second case: We might have an init parameter, use that
  162. String initParameterWidgetSet = event.getService()
  163. .getDeploymentConfiguration().getWidgetset(null);
  164. if (initParameterWidgetSet != null) {
  165. return new WidgetsetInfoImpl(initParameterWidgetSet);
  166. }
  167. // Find the class AppWidgetset in the default package if one exists
  168. WidgetsetInfo info = getWidgetsetClassInfo();
  169. // Third case: we have a generated class called APP_WIDGETSET_NAME
  170. if (info != null) {
  171. return info;
  172. } else {
  173. // Fourth case: we have an AppWidgetset.gwt.xml file
  174. InputStream resource = event.getUIClass()
  175. .getResourceAsStream("/" + APP_WIDGETSET_NAME + ".gwt.xml");
  176. if (resource != null) {
  177. return new WidgetsetInfoImpl(false, null, APP_WIDGETSET_NAME);
  178. }
  179. }
  180. // fifth case: we are using the default widgetset
  181. return null;
  182. }
  183. private Class<WidgetsetInfo> findWidgetsetClass() {
  184. try {
  185. // We cannot naively use Class.forname without getting the correct
  186. // classloader
  187. // FIXME This might still fail with osgi
  188. ClassLoader tccl = VaadinService.getCurrent().getClassLoader();
  189. Class<?> c = Class.forName(APP_WIDGETSET_NAME, true, tccl);
  190. // if not implementing the interface, possibly a @WebListener class
  191. // from an earlier version - ignore it
  192. if (WidgetsetInfo.class.isAssignableFrom(c)) {
  193. return (Class<WidgetsetInfo>) c;
  194. }
  195. } catch (ClassNotFoundException e) {
  196. // ClassNotFound is a normal case
  197. }
  198. return null;
  199. }
  200. private WidgetsetInfo getWidgetsetClassInfo() {
  201. Class<WidgetsetInfo> cls = findWidgetsetClass();
  202. if (cls != null) {
  203. try {
  204. return cls.newInstance();
  205. } catch (InstantiationException e) {
  206. getLogger().log(Level.INFO,
  207. "Unexpected trying to instantiate class "
  208. + cls.getName(),
  209. e);
  210. } catch (IllegalAccessException e) {
  211. getLogger().log(Level.INFO,
  212. "Unexpected trying to access class " + cls.getName(),
  213. e);
  214. }
  215. }
  216. return null;
  217. }
  218. /**
  219. * Checks whether the same UI state should be reused if the framework can
  220. * detect that the application is opened in a browser window where it has
  221. * previously been open. The framework attempts to discover this by checking
  222. * the value of window.name in the browser.
  223. * <p>
  224. * Whenever a preserved UI is reused, its
  225. * {@link UI#refresh(com.vaadin.server.VaadinRequest) refresh} method is
  226. * invoked by the framework first.
  227. *
  228. *
  229. * @param event
  230. * the UI create event with information about the UI and the
  231. * current request.
  232. *
  233. * @return <code>true</code>if the same UI instance should be reused e.g.
  234. * when the browser window is refreshed.
  235. */
  236. public boolean isPreservedOnRefresh(UICreateEvent event) {
  237. PreserveOnRefresh preserveOnRefresh = getAnnotationFor(
  238. event.getUIClass(), PreserveOnRefresh.class);
  239. return preserveOnRefresh != null;
  240. }
  241. public String getPageTitle(UICreateEvent event) {
  242. Title titleAnnotation = getAnnotationFor(event.getUIClass(),
  243. Title.class);
  244. if (titleAnnotation == null) {
  245. return null;
  246. } else {
  247. return titleAnnotation.value();
  248. }
  249. }
  250. /**
  251. * Finds the {@link PushMode} to use for a specific UI. If no specific push
  252. * mode is required, <code>null</code> is returned.
  253. * <p>
  254. * The default implementation uses the @{@link Push} annotation if it's
  255. * defined for the UI class.
  256. *
  257. * @param event
  258. * the UI create event with information about the UI and the
  259. * current request.
  260. * @return the push mode to use, or <code>null</code> if the default push
  261. * mode should be used
  262. *
  263. */
  264. public PushMode getPushMode(UICreateEvent event) {
  265. Push push = getAnnotationFor(event.getUIClass(), Push.class);
  266. if (push == null) {
  267. return null;
  268. } else {
  269. return push.value();
  270. }
  271. }
  272. /**
  273. * Finds the {@link Transport} to use for a specific UI. If no transport is
  274. * defined, <code>null</code> is returned.
  275. * <p>
  276. * The default implementation uses the @{@link Push} annotation if it's
  277. * defined for the UI class.
  278. *
  279. * @param event
  280. * the UI create event with information about the UI and the
  281. * current request.
  282. * @return the transport type to use, or <code>null</code> if the default
  283. * transport type should be used
  284. */
  285. public Transport getPushTransport(UICreateEvent event) {
  286. Push push = getAnnotationFor(event.getUIClass(), Push.class);
  287. if (push == null) {
  288. return null;
  289. } else {
  290. return push.transport();
  291. }
  292. }
  293. private static final Logger getLogger() {
  294. return Logger.getLogger(UIProvider.class.getName());
  295. }
  296. }