PageRenderTime 27ms CodeModel.GetById 28ms RepoModel.GetById 1ms app.codeStats 0ms

/core/src/main/php/webservices/rest/RestMarshalling.class.php

https://github.com/ghiata/xp-framework
PHP | 267 lines | 180 code | 15 blank | 72 comment | 77 complexity | 08c75a5e9209f4811f3cfcad4e647f4c MD5 | raw file
  1. <?php
  2. /* This class is part of the XP framework
  3. *
  4. * $Id$
  5. */
  6. uses('webservices.rest.TypeMarshaller', 'util.collections.HashTable');
  7. /**
  8. * Marshalling takes care of converting the data to a simple output
  9. * format consisting solely of primitives, arrays and maps; and vice
  10. * versa.
  11. *
  12. * @test xp://net.xp_framework.unittest.webservices.rest.RestMarshallingTest
  13. */
  14. class RestMarshalling extends Object {
  15. protected $marshallers;
  16. /**
  17. * Constructor
  18. */
  19. public function __construct() {
  20. $this->marshallers= create('new HashTable<Type, TypeMarshaller>');
  21. }
  22. /**
  23. * Adds a type marshaller
  24. *
  25. * @param var type either a full qualified type name or a type instance
  26. * @param webservices.rest.TypeMarshaller m
  27. * @return webservices.rest.TypeMarshaller The added marshaller
  28. */
  29. public function addMarshaller($type, TypeMarshaller $m) {
  30. $keys= $this->marshallers->keys();
  31. // Add marshaller
  32. $t= $type instanceof Type ? $type : Type::forName($type);
  33. $this->marshallers[$t]= $m;
  34. // Iterate over map keys before having altered the map, checking for
  35. // any marshallers less specific than the added marshaller, and move
  36. // them to the end. E.g. if a marshaller for Dates is added, it needs
  37. // to be in the map *before* the one for for Objects!
  38. foreach ($keys as $type) {
  39. if ($type->isAssignableFrom($t)) {
  40. $this->marshallers->put($type, $this->marshallers->remove($type));
  41. }
  42. }
  43. return $m;
  44. }
  45. /**
  46. * Adds a type marshaller
  47. *
  48. * @param var type either a full qualified type name or a type instance
  49. * @return webservices.rest.TypeMarshaller The added marshaller
  50. */
  51. public function getMarshaller($type) {
  52. return $this->marshallers[$type instanceof Type ? $type : Type::forName($type)];
  53. }
  54. /**
  55. * Calculate variants of a given name
  56. *
  57. * @param string name
  58. * @return string[] names
  59. */
  60. protected function variantsOf($name) {
  61. $variants= array($name);
  62. $chunks= explode('_', $name);
  63. if (sizeof($chunks) > 1) { // product_id => productId
  64. $variants[]= array_shift($chunks).implode(array_map('ucfirst', $chunks));
  65. }
  66. return $variants;
  67. }
  68. /**
  69. * Convert data
  70. *
  71. * @param var data
  72. * @return var
  73. */
  74. public function marshal($data) {
  75. if ($data instanceof Date) {
  76. return $data->toString('c'); // ISO 8601, e.g. "2004-02-12T15:19:21+00:00"
  77. } else if ($data instanceof String || $data instanceof Character) {
  78. return $data->toString();
  79. } else if ($data instanceof Integer || $data instanceof Long || $data instanceof Short || $data instanceof Byte) {
  80. return $data->intValue();
  81. } else if ($data instanceof Float || $data instanceof Double) {
  82. return $data->doubleValue();
  83. } else if ($data instanceof Boolean) {
  84. return (bool)$data->value;
  85. } else if ($data instanceof ArrayList) {
  86. return (array)$data->values;
  87. } else if ($data instanceof Generic) {
  88. foreach ($this->marshallers->keys() as $t) { // Specific class marshalling
  89. if ($t->isInstance($data)) return $this->marshallers[$t]->marshal($data, $this);
  90. }
  91. $class= $data->getClass();
  92. $r= array();
  93. foreach ($class->getFields() as $field) {
  94. $m= $field->getModifiers();
  95. if ($m & MODIFIER_STATIC) {
  96. continue;
  97. } else if ($field->getModifiers() & MODIFIER_PUBLIC) {
  98. $r[$field->getName()]= $this->marshal($field->get($data));
  99. } else {
  100. foreach ($this->variantsOf($field->getName()) as $name) {
  101. if ($class->hasMethod($m= 'get'.$name)) {
  102. $r[$field->getName()]= $this->marshal($class->getMethod($m)->invoke($data));
  103. continue 2;
  104. }
  105. }
  106. }
  107. }
  108. return $r;
  109. } else if (is_array($data)) {
  110. $r= array();
  111. foreach ($data as $key => $val) {
  112. $r[$key]= $this->marshal($val);
  113. }
  114. return $r;
  115. }
  116. return $data;
  117. }
  118. /**
  119. * Returns the first element of a given traversable data structure
  120. * or the data structure itself, or NULL if the structure has more
  121. * than one element.
  122. *
  123. * @param var struct
  124. * @param var[]
  125. */
  126. protected function keyOf($struct) {
  127. if (is_array($struct) || $struct instanceof Traversable) {
  128. $return= NULL;
  129. foreach ($struct as $element) {
  130. if (NULL === $return) {
  131. $return= array($element);
  132. continue;
  133. }
  134. return NULL; // Found a second element, return NULL
  135. }
  136. return $return; // Will be NULL if we have no elements
  137. }
  138. return array($struct);
  139. }
  140. /**
  141. * Returns the first element of a given traversable data structure
  142. * or the data structure itself
  143. *
  144. * @param var struct
  145. * @param var
  146. */
  147. protected function valueOf($struct) {
  148. if (is_array($struct) || $struct instanceof Traversable) {
  149. foreach ($struct as $element) {
  150. return $element;
  151. }
  152. }
  153. return $struct;
  154. }
  155. /**
  156. * Convert data based on type
  157. *
  158. * @param lang.Type type
  159. * @param [:var] data
  160. * @return var
  161. */
  162. public function unmarshal($type, $data) {
  163. if (NULL === $type || $type->equals(Type::$VAR)) { // No conversion
  164. return $data;
  165. } else if (NULL === $data) { // Valid for any type
  166. return NULL;
  167. } else if ($type->equals(XPClass::forName('lang.types.String'))) {
  168. return new String($this->valueOf($data));
  169. } else if ($type->equals(XPClass::forName('util.Date'))) {
  170. return $type->newInstance($data);
  171. } else if ($type instanceof XPClass) {
  172. foreach ($this->marshallers->keys() as $t) {
  173. if ($t->isAssignableFrom($type)) return $this->marshallers[$t]->unmarshal($type, $data, $this);
  174. }
  175. // Check if a public static one-arg valueOf() method exists
  176. // E.g.: Assuming the target type has a valueOf(string $id) and the
  177. // given payload data is either a map or an array with one element, or
  178. // a primitive, then pass that as value. Examples: { "id" : "4711" },
  179. // [ "4711" ] or "4711" - in all cases pass just "4711".
  180. if ($type->hasMethod('valueOf')) {
  181. $m= $type->getMethod('valueOf');
  182. if (Modifiers::isStatic($m->getModifiers()) && Modifiers::isPublic($m->getModifiers()) && 1 === $m->numParameters()) {
  183. if (NULL !== ($arg= $this->keyOf($data))) {
  184. return $m->invoke(NULL, array($this->unmarshal($m->getParameter(0)->getType(), $arg[0])));
  185. }
  186. }
  187. }
  188. // Generic approach
  189. $return= $type->newInstance();
  190. if (NULL === $data) {
  191. $iter= array();
  192. } else if (is_array($data) || $data instanceof Traversable) {
  193. $iter= $data;
  194. } else {
  195. $iter= array($data);
  196. }
  197. foreach ($iter as $name => $value) {
  198. foreach ($this->variantsOf($name) as $variant) {
  199. if ($type->hasField($variant)) {
  200. $field= $type->getField($variant);
  201. $m= $field->getModifiers();
  202. if ($m & MODIFIER_STATIC) {
  203. continue;
  204. } else if ($m & MODIFIER_PUBLIC) {
  205. if (NULL !== ($fType= $field->getType())) {
  206. $field->set($return, $this->unmarshal($fType, $value));
  207. } else {
  208. $field->set($return, $value);
  209. }
  210. continue 2;
  211. }
  212. }
  213. if ($type->hasMethod('set'.$variant)) {
  214. $method= $type->getMethod('set'.$variant);
  215. if ($method->getModifiers() & MODIFIER_PUBLIC) {
  216. if (NULL !== ($param= $method->getParameter(0))) {
  217. $method->invoke($return, array($this->unmarshal($param->getType(), $value)));
  218. } else {
  219. $method->invoke($return, array($value));
  220. }
  221. continue 2;
  222. }
  223. }
  224. }
  225. }
  226. return $return;
  227. } else if ($type instanceof ArrayType) {
  228. $return= array();
  229. foreach ($data as $element) {
  230. $return[]= $this->unmarshal($type->componentType(), $element);
  231. }
  232. return $return;
  233. } else if ($type instanceof MapType) {
  234. $return= array();
  235. foreach ($data as $key => $element) {
  236. $return[$key]= $this->unmarshal($type->componentType(), $element);
  237. }
  238. return $return;
  239. } else if ($type->equals(Primitive::$STRING)) {
  240. return (string)$this->valueOf($data);
  241. } else if ($type->equals(Primitive::$INT)) {
  242. return (int)$this->valueOf($data);
  243. } else if ($type->equals(Primitive::$DOUBLE)) {
  244. return (double)$this->valueOf($data);
  245. } else if ($type->equals(Primitive::$BOOL)) {
  246. return (bool)$this->valueOf($data);
  247. } else {
  248. throw new FormatException('Cannot convert to '.xp::stringOf($type));
  249. }
  250. }
  251. }
  252. ?>