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.

GlobalResourceHandler.java 8.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  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 javax.servlet.http.HttpServletResponse;
  18. import java.io.IOException;
  19. import java.util.HashMap;
  20. import java.util.HashSet;
  21. import java.util.Map;
  22. import java.util.Set;
  23. import java.util.logging.Level;
  24. import java.util.logging.Logger;
  25. import java.util.regex.Matcher;
  26. import java.util.regex.Pattern;
  27. import com.vaadin.shared.ApplicationConstants;
  28. import com.vaadin.ui.LegacyComponent;
  29. import com.vaadin.ui.UI;
  30. import com.vaadin.util.CurrentInstance;
  31. /**
  32. * A {@link RequestHandler} that takes care of {@link ConnectorResource}s that
  33. * should not be served by the connector.
  34. *
  35. * @author Vaadin Ltd
  36. * @since 7.0.0
  37. */
  38. public class GlobalResourceHandler implements RequestHandler {
  39. private static final String LEGACY_TYPE = "legacy";
  40. private static final String RESOURCE_REQUEST_PATH = "global/";
  41. /**
  42. * Used to detect when a resource is no longer used by any connector.
  43. */
  44. private final Map<Resource, Set<ClientConnector>> resourceUsers = new HashMap<>();
  45. /**
  46. * Used to find the resources that might not be needed any more when a
  47. * connector is unregistered.
  48. */
  49. private final Map<ClientConnector, Set<Resource>> usedResources = new HashMap<>();
  50. private final Map<ConnectorResource, String> legacyResourceKeys = new HashMap<>();
  51. private final Map<String, ConnectorResource> legacyResources = new HashMap<>();
  52. private int nextLegacyId = 0;
  53. // APP/global/[uiid]/[type]/[id]
  54. private static final Pattern PATTERN = Pattern
  55. .compile("^/?" + ApplicationConstants.APP_PATH + '/'
  56. + RESOURCE_REQUEST_PATH + "(\\d+)/(([^/]+)(/.*))");
  57. @Override
  58. public boolean handleRequest(VaadinSession session, VaadinRequest request,
  59. VaadinResponse response) throws IOException {
  60. String pathInfo = request.getPathInfo();
  61. if (pathInfo == null) {
  62. return false;
  63. }
  64. Matcher matcher = PATTERN.matcher(pathInfo);
  65. if (!matcher.matches()) {
  66. return false;
  67. }
  68. String uiid = matcher.group(1);
  69. String type = matcher.group(3);
  70. String key = matcher.group(2);
  71. if (key == null) {
  72. return error(request, response,
  73. pathInfo + " is not a valid global resource path");
  74. }
  75. session.lock();
  76. Map<Class<?>, CurrentInstance> oldInstances = null;
  77. DownloadStream stream = null;
  78. try {
  79. UI ui = session.getUIById(Integer.parseInt(uiid));
  80. if (ui == null) {
  81. return error(request, response, "No UI found for id " + uiid);
  82. }
  83. oldInstances = CurrentInstance.setCurrent(ui);
  84. ConnectorResource resource;
  85. if (LEGACY_TYPE.equals(type)) {
  86. resource = legacyResources.get(urlEncodedKey(key));
  87. } else {
  88. return error(request, response, "Unknown global resource type "
  89. + type + " in requested path " + pathInfo);
  90. }
  91. if (resource == null) {
  92. return error(request, response,
  93. "Global resource " + key + " not found");
  94. }
  95. stream = resource.getStream();
  96. if (stream == null) {
  97. return error(request, response,
  98. "Resource " + resource + " didn't produce any stream.");
  99. }
  100. } finally {
  101. session.unlock();
  102. if (oldInstances != null) {
  103. CurrentInstance.restoreInstances(oldInstances);
  104. }
  105. }
  106. stream.writeResponse(request, response);
  107. return true;
  108. }
  109. private String urlEncodedKey(String key) {
  110. // getPathInfo return path decoded but without decoding plus as spaces
  111. return ResourceReference.encodeFileName(key.replace("+", " "));
  112. }
  113. /**
  114. * Registers a resource to be served with a global URL.
  115. * <p>
  116. * A {@link ConnectorResource} registered for a {@link LegacyComponent} will
  117. * be set to be served with a global URL. Other resource types will be
  118. * ignored and thus not served by this handler.
  119. *
  120. * @param resource
  121. * the resource to register
  122. * @param ownerConnector
  123. * the connector to which the resource belongs
  124. */
  125. public void register(Resource resource, ClientConnector ownerConnector) {
  126. if (resource instanceof ConnectorResource) {
  127. if (!(ownerConnector instanceof LegacyComponent)) {
  128. throw new IllegalArgumentException(
  129. "A normal ConnectorResource can only be registered for legacy components.");
  130. }
  131. ConnectorResource connectorResource = (ConnectorResource) resource;
  132. if (!legacyResourceKeys.containsKey(resource)) {
  133. String uri = LEGACY_TYPE + '/'
  134. + Integer.toString(nextLegacyId++);
  135. String filename = connectorResource.getFilename();
  136. if (filename != null && !filename.isEmpty()) {
  137. uri += '/' + ResourceReference.encodeFileName(filename);
  138. }
  139. legacyResourceKeys.put(connectorResource, uri);
  140. legacyResources.put(uri, connectorResource);
  141. registerResourceUsage(connectorResource, ownerConnector);
  142. }
  143. }
  144. }
  145. private void unregisterResource(Resource resource) {
  146. String oldUri = legacyResourceKeys.remove(resource);
  147. if (oldUri != null) {
  148. legacyResources.remove(oldUri);
  149. }
  150. }
  151. private void registerResourceUsage(Resource resource,
  152. ClientConnector connector) {
  153. ensureInSet(resourceUsers, resource, connector);
  154. ensureInSet(usedResources, connector, resource);
  155. }
  156. private <K, V> void ensureInSet(Map<K, Set<V>> map, K key, V value) {
  157. Set<V> set = map.get(key);
  158. if (set == null) {
  159. set = new HashSet<>();
  160. map.put(key, set);
  161. }
  162. set.add(value);
  163. }
  164. /**
  165. * Gets a global URI for a resource if it's registered with this handler.
  166. *
  167. * @param connector
  168. * the connector for which the uri should be generated.
  169. * @param resource
  170. * the resource for which the uri should be generated.
  171. * @return an URI string, or <code>null</code> if the resource is not
  172. * registered.
  173. */
  174. public String getUri(ClientConnector connector,
  175. ConnectorResource resource) {
  176. // app://APP/global/[ui]/[type]/[id]
  177. String uri = legacyResourceKeys.get(resource);
  178. if (uri != null && !uri.isEmpty()) {
  179. return ApplicationConstants.APP_PROTOCOL_PREFIX
  180. + ApplicationConstants.APP_PATH + '/'
  181. + RESOURCE_REQUEST_PATH + connector.getUI().getUIId() + '/'
  182. + uri;
  183. } else {
  184. return null;
  185. }
  186. }
  187. /**
  188. * Notifies this handler that resources registered for the given connector
  189. * can be released.
  190. *
  191. * @param connector
  192. * the connector for which any registered resources can be
  193. * released.
  194. */
  195. public void unregisterConnector(ClientConnector connector) {
  196. Set<Resource> set = usedResources.remove(connector);
  197. if (set == null) {
  198. return;
  199. }
  200. for (Resource resource : set) {
  201. Set<ClientConnector> users = resourceUsers.get(resource);
  202. users.remove(connector);
  203. if (users.isEmpty()) {
  204. resourceUsers.remove(resource);
  205. unregisterResource(resource);
  206. }
  207. }
  208. }
  209. private static Logger getLogger() {
  210. return Logger.getLogger(GlobalResourceHandler.class.getName());
  211. }
  212. private static boolean error(VaadinRequest request, VaadinResponse response,
  213. String logMessage) throws IOException {
  214. getLogger().log(Level.WARNING, logMessage);
  215. response.sendError(HttpServletResponse.SC_NOT_FOUND,
  216. request.getPathInfo() + " can not be found");
  217. // Request handled (though not in a nice way)
  218. return true;
  219. }
  220. }