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.

HttpVariableMap.java 21KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732
  1. /* *************************************************************************
  2. IT Mill Toolkit
  3. Development of Browser User Intarfaces Made Easy
  4. Copyright (C) 2000-2006 IT Mill Ltd
  5. *************************************************************************
  6. This product is distributed under commercial license that can be found
  7. from the product package on license/license.txt. Use of this product might
  8. require purchasing a commercial license from IT Mill Ltd. For guidelines
  9. on usage, see license/licensing-guidelines.html
  10. *************************************************************************
  11. For more information, contact:
  12. IT Mill Ltd phone: +358 2 4802 7180
  13. Ruukinkatu 2-4 fax: +358 2 4802 7181
  14. 20540, Turku email: info@itmill.com
  15. Finland company www: www.itmill.com
  16. Primary source for information and releases: www.itmill.com
  17. ********************************************************************** */
  18. package com.itmill.toolkit.terminal.web;
  19. import com.itmill.toolkit.terminal.SystemError;
  20. import com.itmill.toolkit.terminal.Terminal;
  21. import com.itmill.toolkit.terminal.UploadStream;
  22. import com.itmill.toolkit.terminal.VariableOwner;
  23. import java.util.ArrayList;
  24. import java.util.Arrays;
  25. import java.util.HashMap;
  26. import java.util.HashSet;
  27. import java.util.Map;
  28. import java.util.Set;
  29. import java.util.List;
  30. import java.util.StringTokenizer;
  31. import java.util.Enumeration;
  32. import java.util.WeakHashMap;
  33. import java.io.IOException;
  34. import java.lang.ref.WeakReference;
  35. import javax.servlet.http.HttpServletRequest;
  36. import java.util.LinkedList;
  37. import java.util.Iterator;
  38. /**
  39. * Class implementing the variable mappings.
  40. *
  41. * @author IT Mill Ltd.
  42. * @version @VERSION@
  43. * @since 3.0
  44. */
  45. public class HttpVariableMap {
  46. // Id <-> (Owner,Name) mapping
  47. private Map idToNameMap = new HashMap();
  48. private Map idToTypeMap = new HashMap();
  49. private Map idToOwnerMap = new HashMap();
  50. private Map idToValueMap = new HashMap();
  51. private Map ownerToNameToIdMap = new WeakHashMap();
  52. private Object mapLock = new Object();
  53. // Id generator
  54. private long lastId = 0;
  55. /** Convert the string to a supported class */
  56. private static Object convert(Class type, String value)
  57. throws java.lang.ClassCastException {
  58. try {
  59. // Boolean typed variables
  60. if (type.equals(Boolean.class))
  61. return new Boolean(
  62. !(value.equals("") || value.equals("false")));
  63. // Integer typed variables
  64. if (type.equals(Integer.class))
  65. return new Integer(value.trim());
  66. // String typed variables
  67. if (type.equals(String.class))
  68. return value;
  69. throw new ClassCastException("Unsupported type: " + type.getName());
  70. } catch (NumberFormatException e) {
  71. return null;
  72. }
  73. }
  74. /** Register a new variable.
  75. *
  76. * @return id to assigned for this variable.
  77. */
  78. public String registerVariable(
  79. String name,
  80. Class type,
  81. Object value,
  82. VariableOwner owner) {
  83. // Check that the type of the class is supported
  84. if (!(type.equals(Boolean.class)
  85. || type.equals(Integer.class)
  86. || type.equals(String.class)
  87. || type.equals(String[].class)
  88. || type.equals(UploadStream.class)))
  89. throw new SystemError(
  90. "Unsupported variable type: " + type.getClass());
  91. synchronized (mapLock) {
  92. // Check if the variable is already mapped
  93. HashMap nameToIdMap = (HashMap) ownerToNameToIdMap.get(owner);
  94. if (nameToIdMap == null) {
  95. nameToIdMap = new HashMap();
  96. ownerToNameToIdMap.put(owner, nameToIdMap);
  97. }
  98. String id = (String) nameToIdMap.get(name);
  99. if (id == null) {
  100. // Generate new id and register it
  101. id = "v" + String.valueOf(++lastId);
  102. nameToIdMap.put(name, id);
  103. idToOwnerMap.put(id, new WeakReference(owner));
  104. idToNameMap.put(id, name);
  105. idToTypeMap.put(id, type);
  106. }
  107. idToValueMap.put(id, value);
  108. return id;
  109. }
  110. }
  111. /** Unregisters a variable. */
  112. public void unregisterVariable(String name, VariableOwner owner) {
  113. synchronized (mapLock) {
  114. // Get the id
  115. HashMap nameToIdMap = (HashMap) ownerToNameToIdMap.get(owner);
  116. if (nameToIdMap == null)
  117. return;
  118. String id = (String) nameToIdMap.get(name);
  119. if (id != null)
  120. return;
  121. // Remove all the mappings
  122. nameToIdMap.remove(name);
  123. if (nameToIdMap.isEmpty())
  124. ownerToNameToIdMap.remove(owner);
  125. idToNameMap.remove(id);
  126. idToTypeMap.remove(id);
  127. idToValueMap.remove(id);
  128. idToOwnerMap.remove(id);
  129. }
  130. }
  131. /**
  132. * @author IT Mill Ltd.
  133. * @version @VERSION@
  134. * @since 3.0
  135. */
  136. private class ParameterContainer {
  137. /** Construct the mapping: listener to set of listened parameter names */
  138. private HashMap parameters = new HashMap();
  139. /** Parameter values */
  140. private HashMap values = new HashMap();
  141. /** Multipart parser used for parsing the request */
  142. private ServletMultipartRequest parser = null;
  143. /** Name - Value mapping of parameters that are not variables */
  144. private HashMap nonVariables = new HashMap();
  145. /** Create new parameter container and parse the parameters from the request using
  146. * GET, POST and POST/MULTIPART parsing
  147. */
  148. public ParameterContainer(HttpServletRequest req) throws IOException {
  149. // Parse GET / POST parameters
  150. for (Enumeration e = req.getParameterNames();
  151. e.hasMoreElements();
  152. ) {
  153. String paramName = (String) e.nextElement();
  154. String[] paramValues = req.getParameterValues(paramName);
  155. addParam(paramName, paramValues);
  156. }
  157. // Parse multipart variables
  158. try {
  159. parser =
  160. new ServletMultipartRequest(
  161. req,
  162. MultipartRequest.MAX_READ_BYTES);
  163. } catch (IllegalArgumentException ignored) {
  164. parser = null;
  165. }
  166. if (parser != null) {
  167. for (Enumeration e = parser.getFileParameterNames();
  168. e.hasMoreElements();
  169. ) {
  170. String paramName = (String) e.nextElement();
  171. addParam(paramName, null);
  172. }
  173. for (Enumeration e = parser.getParameterNames();
  174. e.hasMoreElements();
  175. ) {
  176. String paramName = (String) e.nextElement();
  177. Enumeration val = parser.getURLParameters(paramName);
  178. // Create a linked list from enumeration to calculate elements
  179. LinkedList l = new LinkedList();
  180. while (val.hasMoreElements())
  181. l.addLast(val.nextElement());
  182. // String array event constructor
  183. String[] s = new String[l.size()];
  184. Iterator i = l.iterator();
  185. for (int j = 0; j < s.length; j++)
  186. s[j] = (String) i.next();
  187. addParam(paramName, s);
  188. }
  189. }
  190. }
  191. /** Add parameter to container */
  192. private void addParam(String name, String[] value) {
  193. // Support name="set:name=value" value="ignored" notation
  194. if (name.startsWith("set:")) {
  195. int equalsIndex = name.indexOf('=');
  196. value[0] = name.substring(equalsIndex + 1, name.length());
  197. name = name.substring(4, equalsIndex);
  198. String[] curVal = (String[]) values.get(name);
  199. if (curVal != null) {
  200. String[] newVal = new String[1 + curVal.length];
  201. newVal[curVal.length] = value[0];
  202. for (int i = 0; i < curVal.length; i++)
  203. newVal[i] = curVal[i];
  204. value = newVal;
  205. // Special case - if the set:-method is used for
  206. // declaring array of length 2, where either of the
  207. // following conditions are true:
  208. // - the both items are the same
  209. // - the both items have the same length and
  210. // - the items only differ on last character
  211. // - second last character is '.'
  212. // - last char of one string is 'x' and other is 'y'
  213. // Browser is unporposely modifying the name.
  214. if (value.length == 2
  215. && value[0].length() == value[1].length()) {
  216. boolean same = true;
  217. for (int i = 0; i < value[0].length() - 1 && same; i++)
  218. if (value[0].charAt(i) != value[1].charAt(i))
  219. same = false;
  220. if (same
  221. && ((value[0].charAt(value[0].length() - 1) == 'x'
  222. && value[1].charAt(value[1].length() - 1) == 'y')
  223. || (value[0].charAt(value[0].length() - 1) == 'y'
  224. && value[1].charAt(value[1].length() - 1)
  225. == 'x'))) {
  226. value =
  227. new String[] {
  228. value[0].substring(
  229. 0,
  230. value[1].length() - 2)};
  231. } else
  232. if (same && value[0].equals(value[1]))
  233. value = new String[] { value[0] };
  234. }
  235. // Special case - if the set:-method is used for
  236. // declaring array of length 3, where all of the
  237. // following conditions are true:
  238. // - two last items have the same length
  239. // - the first item is 2 chars shorter
  240. // - the longer items only differ on last character
  241. // - the shortest item is a prefix of the longer ones
  242. // - second last character of longer ones is '.'
  243. // - last char of one long string is 'x' and other is 'y'
  244. // Browser is unporposely modifying the name. (Mozilla, Firefox, ..)
  245. if (value.length == 3
  246. && value[1].length() == value[2].length() &&
  247. value[0].length() +2 == value[1].length()) {
  248. boolean same = true;
  249. for (int i = 0; i < value[1].length() - 1 && same; i++)
  250. if (value[2].charAt(i) != value[1].charAt(i))
  251. same = false;
  252. for (int i = 0; i < value[0].length() && same; i++)
  253. if (value[0].charAt(i) != value[1].charAt(i))
  254. same = false;
  255. if (same
  256. && (value[2].charAt(value[2].length() - 1) == 'x'
  257. && value[1].charAt(value[1].length() - 1) == 'y')
  258. || (value[2].charAt(value[2].length() - 1) == 'y'
  259. && value[1].charAt(value[1].length() - 1)
  260. == 'x')) {
  261. value =
  262. new String[] {
  263. value[0]};
  264. }
  265. }
  266. }
  267. }
  268. // Support for setting arrays in format
  269. // set-array:name=value1,value2,value3,...
  270. else if (name.startsWith("set-array:")) {
  271. int equalsIndex = name.indexOf('=');
  272. if (equalsIndex < 0)
  273. return;
  274. StringTokenizer commalist =
  275. new StringTokenizer(name.substring(equalsIndex + 1), ",");
  276. name = name.substring(10, equalsIndex);
  277. String[] curVal = (String[]) values.get(name);
  278. ArrayList elems = new ArrayList();
  279. // Add old values if present.
  280. if (curVal != null) {
  281. for (int i = 0; i < curVal.length; i++)
  282. elems.add(curVal[i]);
  283. }
  284. while (commalist.hasMoreTokens()) {
  285. String token = commalist.nextToken();
  286. if (token != null && token.length() > 0)
  287. elems.add(token);
  288. }
  289. value = new String[elems.size()];
  290. for (int i = 0; i < value.length; i++)
  291. value[i] = (String) elems.get(i);
  292. }
  293. // Support name="array:name" value="val1,val2,val3" notation
  294. // All the empty elements are ignored
  295. else if (name.startsWith("array:")) {
  296. name = name.substring(6);
  297. StringTokenizer commalist = new StringTokenizer(value[0], ",");
  298. String[] curVal = (String[]) values.get(name);
  299. ArrayList elems = new ArrayList();
  300. // Add old values if present.
  301. if (curVal != null) {
  302. for (int i = 0; i < curVal.length; i++)
  303. elems.add(curVal[i]);
  304. }
  305. while (commalist.hasMoreTokens()) {
  306. String token = commalist.nextToken();
  307. if (token != null && token.length() > 0)
  308. elems.add(token);
  309. }
  310. value = new String[elems.size()];
  311. for (int i = 0; i < value.length; i++)
  312. value[i] = (String) elems.get(i);
  313. }
  314. // Support declaring variables with name="declare:name"
  315. else if (name.startsWith("declare:")) {
  316. name = name.substring(8);
  317. value = (String[]) values.get(name);
  318. if (value == null)
  319. value = new String[0];
  320. }
  321. // Get the owner
  322. WeakReference ref = (WeakReference) idToOwnerMap.get(name);
  323. VariableOwner owner = null;
  324. if (ref != null)
  325. owner = (VariableOwner) ref.get();
  326. // Add the parameter to mapping only if they have owners
  327. if (owner != null) {
  328. Set p = (Set) parameters.get(owner);
  329. if (p == null)
  330. parameters.put(owner, p = new HashSet());
  331. p.add(name);
  332. if (value != null)
  333. values.put(name, value);
  334. }
  335. // If the owner can not be found
  336. else {
  337. // If parameter has been mapped before, remove the old owner mapping
  338. if (ref != null) {
  339. // The owner has been destroyed, so we remove the mappings
  340. idToNameMap.remove(name);
  341. idToOwnerMap.remove(name);
  342. idToTypeMap.remove(name);
  343. idToValueMap.remove(name);
  344. }
  345. // Add the parameter to set of non-variables
  346. nonVariables.put(name, value);
  347. }
  348. }
  349. /** Get the set of all parameters connected to given variable owner */
  350. public Set getParameters(VariableOwner owner) {
  351. if (owner == null)
  352. return null;
  353. return (Set) parameters.get(owner);
  354. }
  355. /** Get the set of all variable owners owning parameters in this request */
  356. public Set getOwners() {
  357. return parameters.keySet();
  358. }
  359. /** Get the value of a parameter */
  360. public String[] getValue(String parameterName) {
  361. return (String[]) values.get(parameterName);
  362. }
  363. /** Get the servlet multipart parser */
  364. public ServletMultipartRequest getParser() {
  365. return parser;
  366. }
  367. /** Get the name - value[] mapping of non variable paramteres */
  368. public Map getNonVariables() {
  369. return nonVariables;
  370. }
  371. }
  372. /** Handle all variable changes in this request.
  373. * @param req Http request to handle
  374. * @param listeners If the list is non null, only the listed listeners are
  375. * served. Otherwise all the listeners are served.
  376. * @return Name to Value[] mapping of unhandled variables
  377. */
  378. public Map handleVariables(
  379. HttpServletRequest req,
  380. Terminal.ErrorListener errorListener)
  381. throws IOException {
  382. // Get the parameters
  383. ParameterContainer parcon = new ParameterContainer(req);
  384. // Sort listeners to dependency order
  385. List listeners = getDependencySortedListenerList(parcon.getOwners());
  386. // Handle all parameters for all listeners
  387. while (!listeners.isEmpty()) {
  388. VariableOwner listener = (VariableOwner) listeners.remove(0);
  389. boolean changed = false; // Has any of this owners variabes changed
  390. // Handle all parameters for listener
  391. Set params = parcon.getParameters(listener);
  392. if (params != null) { // Name value mapping
  393. Map variables = new HashMap();
  394. for (Iterator pi = params.iterator(); pi.hasNext();) {
  395. // Get the name of the parameter
  396. String param = (String) pi.next();
  397. // Extract more information about the parameter
  398. String varName = (String) idToNameMap.get(param);
  399. Class varType = (Class) idToTypeMap.get(param);
  400. Object varOldValue = idToValueMap.get(param);
  401. if (varName == null || varType == null)
  402. Log.warn(
  403. "VariableMap: No variable found for parameter "
  404. + param
  405. + " ("
  406. + varName
  407. + ","
  408. + listener
  409. + ")");
  410. else {
  411. ServletMultipartRequest parser = parcon.getParser();
  412. // Upload events
  413. if (varType.equals(UploadStream.class)) {
  414. if (parser != null
  415. && parser.getFileParameter(
  416. param,
  417. MultipartRequest.FILENAME)
  418. != null) {
  419. String filename =
  420. (String) parser.getFileParameter(
  421. param,
  422. MultipartRequest.FILENAME);
  423. String contentType =
  424. (String) parser.getFileParameter(
  425. param,
  426. MultipartRequest.CONTENT_TYPE);
  427. UploadStream upload =
  428. new HttpUploadStream(
  429. varName,
  430. parser.getFileContents(param),
  431. filename,
  432. contentType);
  433. variables.put(varName, upload);
  434. changed = true;
  435. }
  436. }
  437. // Normal variable change events
  438. else {
  439. // First try to parse the event without multipart
  440. String[] values = parcon.getValue(param);
  441. if (values != null) {
  442. if (varType.equals(String[].class)) {
  443. variables.put(varName, values);
  444. changed
  445. |= (!Arrays
  446. .equals(
  447. values,
  448. (String[]) varOldValue));
  449. } else {
  450. try {
  451. if (values.length == 1) {
  452. Object val =
  453. convert(varType, values[0]);
  454. variables.put(varName, val);
  455. changed
  456. |= ((val == null
  457. && varOldValue != null)
  458. || (val != null
  459. && !val.equals(
  460. varOldValue)));
  461. } else if (
  462. values.length == 0
  463. && varType.equals(
  464. Boolean.class)) {
  465. Object val = new Boolean(false);
  466. variables.put(varName, val);
  467. changed
  468. |= (!val.equals(varOldValue));
  469. } else {
  470. Log.warn(
  471. "Empty variable '"
  472. + varName
  473. + "' of type "
  474. + varType.toString());
  475. }
  476. } catch (java.lang.ClassCastException e) {
  477. Log.except(
  478. "WebVariableMap conversion exception",
  479. e);
  480. errorListener.terminalError(
  481. new TerminalErrorImpl(e));
  482. }
  483. }
  484. }
  485. }
  486. }
  487. }
  488. // Do the valuechange if the listener is enabled
  489. if (listener.isEnabled() && changed) {
  490. try {
  491. listener.changeVariables(req, variables);
  492. } catch (Throwable t) {
  493. // Notify the error listener
  494. errorListener.terminalError(
  495. new VariableOwnerErrorImpl(listener, t));
  496. }
  497. }
  498. }
  499. }
  500. return parcon.getNonVariables();
  501. }
  502. /** Implementation of VariableOwner.Error interface. */
  503. public class TerminalErrorImpl implements Terminal.ErrorEvent {
  504. private Throwable throwable;
  505. private TerminalErrorImpl(Throwable throwable) {
  506. this.throwable = throwable;
  507. }
  508. /**
  509. * @see com.itmill.toolkit.terminal.Terminal.ErrorEvent#getThrowable()
  510. */
  511. public Throwable getThrowable() {
  512. return this.throwable;
  513. }
  514. }
  515. /** Implementation of VariableOwner.Error interface. */
  516. public class VariableOwnerErrorImpl
  517. extends TerminalErrorImpl
  518. implements VariableOwner.ErrorEvent {
  519. private VariableOwner owner;
  520. private VariableOwnerErrorImpl(
  521. VariableOwner owner,
  522. Throwable throwable) {
  523. super(throwable);
  524. this.owner = owner;
  525. }
  526. /**
  527. * @see com.itmill.toolkit.terminal.VariableOwner.ErrorEvent#getVariableOwner()
  528. */
  529. public VariableOwner getVariableOwner() {
  530. return this.owner;
  531. }
  532. }
  533. /** Resolve the VariableOwners needed from the request and sort
  534. * them to assure that the dependencies are met (as well as possible).
  535. * @return List of variable list changers, that are needed for handling
  536. * all the variables in the request
  537. * @param req request to be handled
  538. * @param parser multipart parser for the request
  539. */
  540. private List getDependencySortedListenerList(Set listeners) {
  541. LinkedList resultNormal = new LinkedList();
  542. LinkedList resultImmediate = new LinkedList();
  543. // Go trough the listeners and either add them to result or resolve
  544. // their dependencies
  545. HashMap deepdeps = new HashMap();
  546. LinkedList unresolved = new LinkedList();
  547. for (Iterator li = listeners.iterator(); li.hasNext();) {
  548. VariableOwner listener = (VariableOwner) li.next();
  549. if (listener != null) {
  550. Set dependencies = listener.getDirectDependencies();
  551. // The listeners with no dependencies are added to the front of the
  552. // list directly
  553. if (dependencies == null || dependencies.isEmpty()) {
  554. if (listener.isImmediate())
  555. resultImmediate.addFirst(listener);
  556. else
  557. resultNormal.addFirst(listener);
  558. }
  559. // Resolve deep dependencies for the listeners with dependencies
  560. // (the listeners will be added to the end of results in correct
  561. // dependency order later). Also the dependencies of all the
  562. // depended listeners are resolved.
  563. else if (deepdeps.get(listener) == null) {
  564. // Set the fifo for unresolved parents to contain only the
  565. // listener to be resolved
  566. unresolved.clear();
  567. unresolved.add(listener);
  568. // Resolve dependencies
  569. HashSet tmpdeepdeps = new HashSet();
  570. while (!unresolved.isEmpty()) {
  571. VariableOwner l =
  572. (VariableOwner) unresolved.removeFirst();
  573. if (!tmpdeepdeps.contains(l)) {
  574. tmpdeepdeps.add(l);
  575. if (deepdeps.containsKey(l)) {
  576. tmpdeepdeps.addAll((Set) deepdeps.get(l));
  577. } else {
  578. Set deps = l.getDirectDependencies();
  579. if (deps != null && !deps.isEmpty())
  580. for (Iterator di = deps.iterator();
  581. di.hasNext();
  582. ) {
  583. Object d = di.next();
  584. if (d != null
  585. && !tmpdeepdeps.contains(d))
  586. unresolved.addLast(d);
  587. }
  588. }
  589. }
  590. }
  591. tmpdeepdeps.remove(listener);
  592. deepdeps.put(listener, tmpdeepdeps);
  593. }
  594. }
  595. }
  596. // Add the listeners with dependencies in sane order to the result
  597. for (Iterator li = deepdeps.keySet().iterator(); li.hasNext();) {
  598. VariableOwner l = (VariableOwner) li.next();
  599. boolean immediate = l.isImmediate();
  600. // Add each listener after the last depended listener already in
  601. // the list
  602. int index = -1;
  603. for (Iterator di = ((Set) deepdeps.get(l)).iterator();
  604. di.hasNext();
  605. ) {
  606. int k;
  607. Object depended = di.next();
  608. if (immediate) {
  609. k = resultImmediate.lastIndexOf(depended);
  610. }else {
  611. k = resultNormal.lastIndexOf(depended);
  612. }
  613. if (k > index)
  614. index = k;
  615. }
  616. if (immediate) {
  617. resultImmediate.add(index + 1, l);
  618. } else {
  619. resultNormal.add(index + 1, l);
  620. }
  621. }
  622. // Append immediate listeners to normal listeners
  623. // This way the normal handlers are always called before
  624. // immediate ones
  625. resultNormal.addAll(resultImmediate);
  626. return resultNormal;
  627. }
  628. }