diff --git a/.travis.yml b/.travis.yml index 7c671b9..d52cf8a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,15 +4,15 @@ jdk: - openjdk7 before_script: - rm -rf target -script: ./travis-build.sh +script: "./travis-build.sh" env: global: - GIT_NAME="Graeme Rocher" - GIT_EMAIL="graeme.rocher@gmail.com" - - secure: tskmGxcLqxs8LpgeF73bcKNw2hCQvd+i3t63xpLlGddg1LB6L2LyytbM3jF5Ynif39LUSwsACSly33EywkXDAEEtkGUEhSOL/piZQcd/YidCq5Xdf1SXLZJYjLsfohu8mi3PVue33TWLucYzqvvR1jO9xEoKjw0BPfbiJt5mniswl2O/pRJjiDmqfYJZVClZu9fe18/eAvhaBYzNX6CEU3wvrymBiQVpTGVbV1UWaQKFHv2lGKhnpF2UM+F182/GU59gYsm/gn+5+6xUapAfEHGGv6kr+ZnB5E3oHigQKqT6B/zOZf7RHtuHxtiOZx1WoOYedpl++oMae9mdX+s5hXYvxH2DHe18k/RDAEl04Ae0qt9nK98eYMuTDS1HEYxAG5yuFbjlK46Nx7ANhjdeVzJdFjTwpmXNpbraVqlF4/3/JYzqcdumGJippHa+5ejlPXP20csGejIhJYufoRvRgvYz+nloYg8s/6cyKcUZx1Nm3gTdNZVPhYZJN0w9wtWo5OyTTSAwKsejex+95LvcTDF6AvDAGm97CByTWNV0QsQ7fbmMIBHEfVIsGEXkzvzSoq11ran1+ZO8uiELIYZccwQ4GKy+Q0a8DxwRJ2yP5fIY+srsNIY4rZViy1xe6SDdUmqaNyQepY+gzNjVmBsVq0C4GdcXsGxLxY63vOzjoBo= - - secure: 0bTHsSCK5YFDd2bUIVH0W88F/ZWLCpVf4KOuC6Qgj/fv8RjELlnGc998INq2KGd0jaS2Il8OWf8YC171Frkt3senwiYE/MZANJeKRIuloJHnhgUdKBQgerpU/YSpGRUk+kTban7MZgg8f9qbHhkbrtTymLJTCvWz8YribPlyVIaz1n/Dv/4ddBUWHMW8cn8LChiF3WQN4KoL9Jd23XKm0mfOUFGeEqJC5c18Fj7NPsFpacWfpOna9+D9ncBcvHM1ktJ9+FyUA7dCrarRBQaPKcPluzvCJ7BBWlQYN5NUpV54FGn+MphtqBfxvlsZb2LTjK0O6j/4HiazGskZdYul0ige4O6+TCDtce8DUrvcRLCIfrd4dU8pJmSFLKDiervk0UhqWhuD6UB2ttrs+HSOjSvXLz4FlAHvF/choiFYVIP/iNkRaQP1EvyA65RocYgGfEOxZrFlqFipcSBLiFOL3zxh5lAEjR8JzmLM6EiU3dORwQVgB+Zpqj4FaeSjojRYI90/0AMQ/y5/FvxXhhg7tNfpn1q8Cs/A31kdvz3P/iuNTZXCxD68dWKouTdnukQ67a8aWSBVT6u/LgBpsd+dBIOhH1/g2e9Gtg26InHJluCMrdTJU9Q14aFuaPBijGV3NxhbPXqdT9VyZ038rEjoaMrG+ig6vsEa+HVDtukdIpI= - - secure: r0nnAQtdcU7snnp9467KgbrbrA/LQor+cS8IaLRVr8zXdW+6P4WeaE25ofST04YukX1+TlBHMPw6W1R0NlNUF2Jif1tgNWpN9QvAUBDl72yrZIq5LaRUhJlGjFwJRq9ZbV9SjX99yvsBopfv6pB7CgkXLNOjIshp9QJiYTZ371/qE9NH0+83tkA/rqQpm9cEU8axwtKhtC9WyNBBtWE9f8OdcSXAlSf1eRohs+Rbe/qSMT9MMAVYEuw7tPB/dcFVgo36yOjSF1sr42Kydk2FXEZ06z7ceDld9QkO/BT3qKY9UMiJHzMgAMzXp0mRAW/WYl98l2d+b+6nbmXjIJMQln0ZYOVmJr/hPYOdHxzv6Ik/uE/zeFowtfnpbT7WqN9VmyLb8kfunC9JduzIQB4vdRNlrgyi+DD4yKXGb0fzmAmsb8E9cqrrJ0ek5ofA8OCfjP/NQofPpIHjJchMB/oKO1l/wEYwVKfh37vm48DTZ8PwQKkXE4szW1g0RHVnYfZtgSBTBCQVukpw3PjobyLDXzZtBE/zGW/yvWEXOHtReYC29QTQTPxXXGlTBJl3dVnXrXzE2eRNdrM54/VutVrjMVLKbI/hEdoFbAUL8Zpsax3JkCi7kJ8j9iAaf34XOULjmPpQIwscJyXpH31OrcN43/eJyc87dnnAjfvqJ0gsZk8= - - secure: k3yM6Eeu1bl83wfNsthZqaKBdJa8RtBBGzvaUv52W0m0Iq/aOtsxMBzKRhceyHECXgOci7QdPoSjlxO34qkPl8MOTFGUc6/5duRfhXY2ZZTzCobAIIong7tSJLb16cagbkfWnwMoK+9/Yg8aOB7Dxxpq54nGssjt5oqzA481GMj+zPU9ebILRHJXRESbtZGYTAkb5GbxLoWIJ+8pxZv4JI5WkrFNF84GQAtIjOdOCDXtWXkwMqLGyu9Vnfii+VEB4XJZJdZ/eir3cIB7oZnsd+5E1iEGJ5sHGKh/sF0/RIaC7CR9SQjP8ihuNlku5XmBEmwR1KmRJonnPiiT8iGJOgtCUsQ7u1ofYxIVAoAsDFjD7vMJBXp1jJUwtARp8on1hHzTQ1c8C/c+/rN+aFSKuG/lBtY0yNxy2XcYSsUdxBDgFhWAwoF0dsEjBlIAeH1JdLpJYZ3DOqECKIbiFUXWV8lE3sB1fqtV6pR4Mff3GcZT9btsz6/K4gSiv5DsCSSrb7CoeU4FprfXOfqHtHXyRTyVQwosOr+fNc299h8QQ0BEL2yU40dqnpsCvhQzKfP1iITcGfYRp4cXjBbzU8ZZ5JKoFsh83DOBTcjEzaNHLgvBJ/l7jEd3zDrw+L+F6pE0CRJSLgb5ecUpXMqxj+MqiXqy+xSB2yXvkCAssdTmfuI= - - secure: hunUZmaPhrD1IWzmChMrTjWl2K8XcCu2l0a1XY0vgvmzrLvaaLExTN5Xr3ek9lART1s6qoGQw61GUmGN8FBclX7fxrzLsBVYQuSoVepVmKDyzMVDuRBYoB8f5s23hrAA8QXkQz6iT6n4B8tH0Tc6/wKMuBSYU6kjnNQJYb1heY9ppKTkjM6kwAlWIuUT2GeFG3keL5ZaIWZR/WcAUKUJ74ljweP7PxHvQgrCwOOCkFWMO+bGdS5+gqgjeXQ0WQ+pVh4PqmezVkiff/Nr0uWo2aeB4xcgCa2loOqc8tgYfbPcveyJO1tR64APdwL51ZeyIajxW8iz2VK5wUiIsRexihZYpVG3ykdQpRyLgUWmc8nhPAm/pAdmV+ZrdKPn8bA1D2xefQRb6aB3Ku6f/xXLVsrwNm2MXwyPEA3EDS3x+D7Bybs6Rg/8MbeHs5cTHLN5AKRl/FiF8TR//kMVtsLKj5Aj+n6kY9LMkgEAfObIb28iZdqdc7A6gh53ofiXzmics93Elm/wkGBDEDOf1obw2nMxkgmXlTCJxpwWNnkrNSQ2f1YSj5RRYsQY5BOCRmemv2avgfCSX4GJSsBeQPy9ztH+8dNqjt2k/2VceWh8vveg/TtTQ0TOBdgrtyfpSLHvApMkjzQ0fSwv+Nb/51i8pEBNMzqZhCkbBaOHbS1hHtw= - - secure: EACEddZXExFtO2w2DVnlBwHILVnPDDO1C6ohank5cEtzWusez0uCQTIrNPvbp5aXYHH4EltgQsnJ2jtCCxlxX1Y7Iu+Y5yOaQATVQBJMPrqmgjwCajrEuJTUUyvBwJ53MRx05ikOeMKm6Nc9vZ0/HCn7r4bIbFmVSrJN6QF2wmNtAg2hiER59ln+V2KkmJKjoh5w05uwDJ4ElZtUYZgfDLPZ+nTDjRfZGis0Qc90D5Gee5v8yR27OFexWssWIUdiopdcOmOKm7VCLqXxR9vbvBxEKpYEXWCkbaZFRHnbIE2v8058Y9jOI7pTq5DtzQlrrIXrwXREBTT+XR1Mk9bG4+bWsQGFMSlUuJylXFSBTiT9Ck9DMT1Rr/Dkn7iJ/PTxIEFjd6h2rBfCB0RtELosDbwLPVt0FmoP3oOnXUxS9ytbKkh9ok1uKQSjt6IrIuU3SF+jeD5cnUQ20wWOaMm/8sxgBRDFWKmM3yACFMr78z+5Z8I5WIydgFM6gh8XIZtBVk21S5sSm+YarvciN2AJ/FL0DKAeYARuLbjwFNnMnOaqn8bZ0IyRTd2qjWXq4vbbxyE6zttxfwId2+93m0bxkTPgzpFMNpg+2hSZa/jcjvZ2piuGOfLiBlukrp4ntAAVxgFgORyhYV4h4tmiXA32zFsEAjQATqxUUurF/zaDmCE= - - secure: O3w9lswdKxL2kya9avjaGE4N2oKWaFsGwoM1Ub/pte5DN2Lg0HSDWIw8i3Qalg6xg08UlkSV401FjDwz4W1ykuxHmkl5TvtJ2IhANSFf0rK1LgBXwmF+w2Qqj5bSagJlWQjwyOxWT0KAQqwDaQp01KhOceQnTnRU4R9NYoRJmT8/EL8X0Fz8UzJh/tXhAqF+p07mLUNZOPGzgSQ4neuHg8KF3XNOGEXSOEPEjz/4mz65zm3LI8nQLOAgPocW5IjUPUJaGjuKWdz5GG0wirhMhr8GSdoCzzy32vmOYIG4kksN2VI6ZndmLjH3TdQ5XFzGiQzA46IsIfapXJgzDjuRUaupJhS/4kiZ78P7lJPE8F84XHm+DbobSIm7c4hRJDY+/LneSIRqO8/AO0FpF6HP7Ygx5eXyaWU2grLgTFh5tyFYRESxxBbKGhn6oeaawsjFn9B+7hxyYX1UO6+2EmwqRkY86hw8inH8p6o9XrrXWAMVIAqRNc0EyHaLAKU2utNulQ1VDN+FwyF1tGprebdZ1t0z5bRyLyE1wolzwkc6oNpTyNKjlTPXa0VzYAGYuPJJ6oua1NuHWK0mSTPHo/XhA9StClPjp016WfcSnyGnAkgUeZr6HKWiDS5qwet7G3VD+MO1CKfdgKp/httq3W7ycz9bvlTLR80jdRLZnUdOv5w= + - secure: ipAPhJPN2FxdEigCMEhnxC6VnKwHsPxiG8CIlD9tLBmSBuCuIRe4C7r9lrUbLwDEYLXYUy7K0l9V/t4XTpK6uOFKrSfeBltndjcVXUB7ISsM/cZ/sLRUxwy4fdICZXV2haBTOLViQ+I82si5pr8ycs6b4kXuZjrAbN3BeF9FN8CZbNrJZz4JT3sUEXlDNo7Sa8Ld8uxraBWu/gFeJcHF5ZZcAQJtde59j3/BMtLRlv4jjHVdg1kDxrH5hXk521w1SKvPhDsLhHmCaayEuYW6wxt0sjRep9W4WRDLnZnfMdxn3uwYtd0o1FZiJ6uL+U/p106KaMd0x+J1Ua4pyqHqGO8SgGwdPICsprxE7+bBCqMEiFpSvR/vwv9MjE6pdEtCLnt7kmX4UHQ8A7GunqAu33d1Sh9LSzYXQeReToa3Pel27BMKRzLjO0j8rPQoEBLAZGdrMWV8/j0cbNEoxZYue1kbhn1z96j1Ze0T8quNNpIfi1lhCCWws7zrewbiTnECiwC6gUlcLqz6jJevwMNKnMMMLdbIdKtHQy8dn++LEsUS9FmdxFnorIAssNVcVLaorM+3HRGS555fGyxNyN+OkizclxmgDHeJvJYRwpVVMnE2DZcd3mfVsFEjH+SX5BEOjD/6eRzo64r9nyXluekM4mArPoRRS3tswCAwObSUUyc= + - secure: bqleUgtd2ZyOqzGgnkl2mYSuMcEhbYGsfIVW1r8XduNh2bRQRms9n1GXAyUVkml5PbIWjBtZvdNoZaO5glA55YiJdAjLN4wPpR8aF8EDS8ArkuFBmNalWSIEHjEhAJeboTARn7JnbrpgPe6jVNKIAhtBxBHFD5bDVwlaCbQ3Cs5mS1eLDicSHBOU5JwCL7a7CMlN+RQVvNt88CefYCBdxar+0HXRytYlaqwAZCuuFVaidhrqVmhk5OzyNHhNVk88fRTv7rpz/Jg62jm18i9jtAJJJE5Xp5jV6lws9RuBuWXb+Dw3aeDrm1wwPdwNOEKqfxA/VVcTwtfjrwY3ZjCtK1xalkbLX3wywesNr0eKNSzpZjd8b3OWMLCzMcI5HeAzxhwJEm7p6lkbyptmLd0UX3BmmP214U57nCCBYVohIyJyJgeshZbQlWtRJQN3SGVk7fj+8G1+70fsLLAl5gOQXaEJOyxG8uGbZEzNFVIz2oRjaO2A0Spp0SCY7nVHRbFiinhHZfEtrRontsbSe2SS72myL4lCsl1Hg0Pv8XzKBmEfwot2Xn+4GMsJTLie6ML3F6QAekuSJZmH8KlEUnivHSIiavJHFRohYMhyNtqENquwn+yBWkyHp3bzfpsyJQmzY3UvG+auiINBeUFzNvl+xKB9wBXRtxks/dBtjeEVOHI= + - secure: T6a0YVzI8eo2WdC7xLNpej0E8QlnHPSpU+ClZK1adYlQ8lVOexa3fwsXT0tN6mRVsUSkdbAuLuOGoikt3tJN/FXnabt0YL5tR/VPEc4wAcc/rYrGEoZIzXO2GV1YKSRXildtin2h9bYvzx+cfsDKYp5BhnHoD+Rk7X9xG7pOmRH4bBKZb9KFvFY27yoEBOzZjJOGkfWkSZj2Ef9GkX7q7p5/GkDU3JAuj7nqet79Jt6difxQMCoEX/vxbmiS4MCazsuo8Z0eKHlCcImu29D1aaBQ8mv5pCotFUqYi32qY3Bnx76knRRlotEV5HUFGHx/zP5d9LtEdbv8WAunAYMqPWeh5V8uqUjcUi7AxVIJMOcDzprEA37ahL/YrlPTbOH+RqAKC8ueQAom/yRY3TBPHvFkUAHqHqNIzziOk4Xa6maXMMW79pdOYNIaKn4DgXz2qz8tQJhVUczz+fmD1wkai/sdfnZO9L/0aQw5mAKuz5/5e3/eABh8Gcbkqd8bpmZz/a02hVMzKNU2t6I/DUrZGl6ZfR7pxStf+ylSjMv9E+d8DAoWt2GlR0+rYHTKtERMzNDSaVB72X/oIxr2Un2dfx+jR1/SN2FEyByWOXVap64Vs8XtrMl+80hwvVbrpiuTS57m1GgTuqTnEvCQd00tRxbyohNF0unBZ/5jeWgvvOQ= + - secure: Kk/wqyVDvosbYLSk9FoP8V4xNPKRDjDCk8T8vr7g/IhOKLqvONiYl7j3cUaXAeSTWX8CE0bp+EN2hVC2dgurFA6RJqr8jVaNSJDhdZUQ4U3I8dtqgt8+CXkAVfrwNlnaaeYq+ZskPDFGcB5U6MiJZtrXQUx8Lm1sDSs9/03DjvXvsFLSqhXm4eTSChLUW/AEOxw8IibIDebDyhRi6e5s292WRkVNEe483ZBAot2pWU8qa/8nlLlMvZICL0avaNbm1ufzMFuz+wuQGbDJXJGFn2AIHOhUV9D5ZBo8umJNyextK0qo1NeUqZufj+GiVgT3UNYHpyLTRjcemQhcegtbnJ7tkokWZfA8qSb3A7ws68ppqGw/CUZ5juQsXvw+qCWzM5qaXQz1HQmrPSdHYtJTMhl6N72XuEAzDMYh3ZHFpTitAIooc+6a0ENtToj2npOtB/ypHbQWt6O171cPjd/URTnxjvnCrw0aQynQd7V8XVHsyQnz/+pKDWMkpeLUn9nG+qEjGb2MEg5lVmxpUmP9iI3QC4gd+px83W5y3ymlRavdGVdgB95jDHarSxlz6uIjHRBGsEGgmkyNcB12YLl0UgcmgRIIp/i7I37ecdaek4gKpY2+IupTR0lbcF4HTrMDR3UmGGSM/3pq5faAfIX1BqsgSlWO6Iz6qkilDVYWFUY= + - secure: r8GPUh8GzRjPQzlOGLntqX45Fm6uMxcvE9bCK9XlIrvA3akL5jRpspg9ud6ALvNQiXy7o+stRQLnHMAwi2s+So1IO3IRT5B7ytN39fr/5ChMBg0MPD6NM9XeSSAgkQ5POtpytdbV/8X/Z/R/3NwnhqYPXyX+h/exLV5Vvp+wm5s7ts1VDiuWXyzogxP3lAQL0GzsrP2leI7ptv/VPYxkV0aTZUM7qKWXD9y8tutSWq6ZnWAqMwMvxjvM54lvrfre80KD+0nm6V/80i3W9+YNE1VJARvhKCcNUUBav2gl7I6lt2z8tBNbl3x3Y4fUiKsBYv8cwj1bIzI2O+s6455CECBvohlm2VzZIc4J5Sw/Pox00/6aRfZvFu7sv44/l/qXlDuCdHbWpN0G34ALeVEHJA4B+QBJU5n6fW07aA26zgi4vWGquhPhhXOnQx++LfDMEaOf7YbcGPLe4yQ34n3G90LPY35AgB6AxQZjA4nf366CC6fOE7QlopJtfziXoh1KNgiaqkWS2Sy32SwP8sue/8qL+52K2obVXRlapFGZlZVAKO9RVSBMXXjzASehfaJn1Gc9Ns3aTnK5qZNU3Pe/CMgkOpmi2oswackivnl4/kR7RbmRtWp5AWy6ihWMl9xrDCsk/k/47VKhbOPTMXnyZwxAL863n23MHGvQ4z6h1zg= + - secure: AOEjis+8Xp11iTnUh1+9qJ5M8P+bkBHjFLn6TTGIiT7rA1+w08ktgRUy7dNYmy6/Mck8jngq69utTlR2xMd95a1Gbx2mCoZQZj1veq/J6Y9qnQxLqumH+KzjlMNRRGJ5wYl2BCKtAVC27ZC6zYDqGcLLbzr1PgeNLRTJRjQI6Hv0AzLLKi6vqvnSGDL9u2SLz7MnbtQrVvHH5Z+gP/xlXr7aZCepT0+r+gL34vzNyojUnwqfKLKgm3Vm7r41YDoQCvkGv3YKNJijPXkz84TQIAY0ghktH5LGBW6RobS8Vm+wnG2iIckcuTqI6jz7eTrOJn5IuQ87c92gia7lehncVDWvFb7dwjjSFH0Z3WP2dA+EDreTjMBxTwPGXpVMZWR3h3cXJ6dFLfcRxfqkgkwINGM4+3bL6RFEIthJnWG/xUnJ2dKylix9ouz8ehh9Mv9WlwOKEzL0YJX4RN/t5YheZ67b05Mi/Ydq/loLWgv3NIebFAbHfCDTcaGSOHJ8cNdagUWaWZvphDP0mf3xgvei+CuRu37hMuRe4fRZtHdvKRa2DL9GzupbPK6I21pwH3cQak991IuRKgGZA1kiHovIx2kDGACPfj4SKZCRr6q8wnkvMLTVEcwMKggwE6daQ3yn5G77dQjgaK8y024yQCOqz0su500IgG+B6ITPnq0viEc= + - secure: vL0Zf+rvVDg/3jlbUv9GRPgEIugmDKDmYFDZHrsDCY7SMwVDC9PNaWhUKZTmGb+cl9XPXeMjm4ex2kUW5fXeo48SOtqfEOsudCMGCTwQuJYmBDSVZx9tlNtHMOxfQR/9bbW3UwxWinlMMmXnMLRWASwOjyJaYwjFZBdppm06BpuTCMNocLvyT4KVt7afM24WnsY2iajVFwmAhHBb+ZbkltU4tR/+xpq7zEuQzdQJjmOQtvvY6nnhxMi7/CMIcL9qzg/YOBZHhrtQrYRfIMDSH88ZzQRc859Km32i/NKg2ZxuaGAh/OZjdN0YVU+aZopSL/zTUMeOCN7+a7sE8B1SA1XJhYdd7OYEwRQZbHvP6JP0oEIzZb3AdURgXNR9pBHlYf0iOnLVq0QJv24wRV665fiokwirnCU7RXLTFw3NpYT9qZaIuRPdJ81k8/tVP4ypFJ8uD1IlZwGscolbpExUf1G1KTuc9v8pIk1MFHFSC7WUhL8rzMF23eLiYVpwHTlLaMyMqoMoPdyRg6boSnRjai+TdD3hOjFXq8NZrTPewjXUXk4WeuZhR8OVk4dVVERZF2dG/IZE09Q5P/nFh4qzRriE8/skqoXYukEAx58K+6D4bh6+R2JdOOuwK1XkqC7B/PBcJkxsxYZF+u3IXO2xF7wa/VUgLFBcvYhlJ/n2P2E= diff --git a/build.gradle b/build.gradle index 14ca1b5..eb10e95 100644 --- a/build.gradle +++ b/build.gradle @@ -17,7 +17,7 @@ plugins { -version "1.0.2.BUILD-SNAPSHOT" +version "1.1.2.BUILD-SNAPSHOT" group "org.grails.plugins" apply plugin:"eclipse" @@ -50,7 +50,7 @@ dependencies { compile "org.springframework.boot:spring-boot-starter-tomcat" compile "org.grails:grails-dependencies" compile "org.grails:grails-web-boot" - compile 'io.reactivex:rxjava:1.2.6' + compile "io.reactivex:rxjava:$rxJavaVersion" console "org.grails:grails-console" diff --git a/examples/server-sent-event-example/gradle.properties b/examples/server-sent-event-example/gradle.properties index 62d5d8e..2733d6b 100644 --- a/examples/server-sent-event-example/gradle.properties +++ b/examples/server-sent-event-example/gradle.properties @@ -1,2 +1,2 @@ -grailsVersion=3.2.5 +grailsVersion=3.2.6 gradleWrapperVersion=2.13 diff --git a/examples/server-sent-event-example/grails-app/controllers/rxjava/demo/TickTockController.groovy b/examples/server-sent-event-example/grails-app/controllers/rxjava/demo/TickTockController.groovy index d34bde9..3104d14 100644 --- a/examples/server-sent-event-example/grails-app/controllers/rxjava/demo/TickTockController.groovy +++ b/examples/server-sent-event-example/grails-app/controllers/rxjava/demo/TickTockController.groovy @@ -2,14 +2,12 @@ package rxjava.demo import grails.converters.JSON import grails.rx.web.* -import io.reactivex.Emitter -import io.reactivex.Observable -import io.reactivex.schedulers.Schedulers -import io.reactivex.subjects.PublishSubject -import io.reactivex.subjects.Subject import reactor.spring.context.annotation.Consumer import reactor.spring.context.annotation.Selector - +import rx.Observable +import rx.Subscriber +import rx.subjects.* +import rx.schedulers.Schedulers import java.util.concurrent.TimeUnit /** @@ -19,22 +17,21 @@ import java.util.concurrent.TimeUnit class TickTockController implements RxController { def index() { - rx.stream { Emitter emitter -> - for(i in (0..5)) { - if(i % 2 == 0) { + rx.stream { Subscriber emitter -> + for (i in (0..5)) { + if (i % 2 == 0) { emitter.onNext( - rx.render("Tick") + rx.render("Tick") ) - } - else { + } else { emitter.onNext( - rx.render("Tock") + rx.render("Tock") ) } sleep 1000 } - emitter.onComplete() + emitter.onCompleted() } } @@ -42,15 +39,14 @@ class TickTockController implements RxController { def lastId = request.getHeader('Last-Event-ID') as Integer def startId = lastId ? lastId + 1 : 0 log.info("Last Event ID: $lastId") - rx.stream { Emitter emitter -> + rx.stream { Subscriber emitter -> log.info("SSE Thread ${Thread.currentThread().name}") - for(i in (startId..(startId+9))) { - if(i % 2 == 0) { + for (i in (startId..(startId + 9))) { + if (i % 2 == 0) { emitter.onNext( rx.event("Tick\n$i", id: i, event: 'tick', comment: 'tick') ) - } - else { + } else { emitter.onNext( rx.event("Tock\n$i", id: i, event: 'tock', comment: 'tock') ) @@ -58,7 +54,7 @@ class TickTockController implements RxController { } sleep 1000 } - emitter.onComplete() + emitter.onCompleted() } } @@ -66,15 +62,23 @@ class TickTockController implements RxController { def lastId = request.getHeader('Last-Event-ID') as Integer def startId = lastId ? lastId + 1 : 0 rx.stream( - Observable - .interval(1, TimeUnit.SECONDS) - .doOnSubscribe { log.info("Observable Subscribe Thread ${Thread.currentThread().name}") } - .doOnNext { log.info("Observable Thread ${Thread.currentThread().name}") } - .map { - def id = it + startId - rx.event([type: 'observable', num: id] as JSON, id: id, comment: 'hello') - } - .take(10), + Observable + .interval(1, TimeUnit.SECONDS) + .doOnSubscribe { log.info("Observable Subscribe Thread ${Thread.currentThread().name}") } + .doOnNext { log.info("Observable Thread ${Thread.currentThread().name}") } + .map { + def id = it + startId + def json = [type: 'observable', num: id] as JSON + + rx.event(new Writable() { + @Override + Writer writeTo(Writer writer) throws IOException { + json.render(writer) + return writer + } + }, id: id, comment: 'hello') + } + .take(10) ) } @@ -89,13 +93,21 @@ class TickTockController implements RxController { def quartz() { rx.stream( - publishedObservable - .doOnSubscribe { log.info("Quartz Subscribe Thread ${Thread.currentThread().name}") } - .doOnError { log.info("Quartz thread error") } - .map { - log.info("Quartz Thread ${Thread.currentThread().name}") - rx.event([type: 'quartz', num: it as int] as JSON, comment: 'hello') - } + publishedObservable + .doOnSubscribe { log.info("Quartz Subscribe Thread ${Thread.currentThread().name}") } + .doOnError { log.info("Quartz thread error") } + .map { + log.info("Quartz Thread ${Thread.currentThread().name}") + + def json = [type: 'quartz', num: it as int] as JSON + rx.event(new Writable() { + @Override + Writer writeTo(Writer writer) throws IOException { + json.render(writer) + return writer + } + }, comment: 'hello') + } ) } } diff --git a/gradle.properties b/gradle.properties index cf7ef73..a2abf34 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,3 @@ grailsVersion=3.1.13 gradleWrapperVersion=2.13 +rxJavaVersion=1.3.0 diff --git a/src/docs/asciidoc/gettingStarted.adoc b/src/docs/asciidoc/gettingStarted.adoc index da03503..1d5747e 100644 --- a/src/docs/asciidoc/gettingStarted.adoc +++ b/src/docs/asciidoc/gettingStarted.adoc @@ -16,7 +16,7 @@ You then may want to include an implementation of RxGORM, for the examples in th ---- dependencies { ... - compile 'org.grails.plugins:rx-mongodb:6.0.0.M2' + compile 'org.grails.plugins:rx-mongodb:6.0.7' } ---- diff --git a/src/docs/asciidoc/serverSentEvents.adoc b/src/docs/asciidoc/serverSentEvents.adoc index e45a926..6f33272 100644 --- a/src/docs/asciidoc/serverSentEvents.adoc +++ b/src/docs/asciidoc/serverSentEvents.adoc @@ -8,15 +8,15 @@ For example: ---- def index() { - rx.stream { Subscriber subscriber -> <1> + rx.stream { rx.Observer observer -> <1> for(i in (0..5)) { if(i % 2 == 0) { - subscriber.onNext( + observer.onNext( rx.render("Tick") <2> ) } else { - subscriber.onNext( + observer.onNext( rx.render("Tock") ) @@ -28,7 +28,7 @@ def index() { } ---- -<1> Call the `stream` method passing a closure that accepts an `rx.Subscriber` to start sending events +<1> Call the `stream` method passing a closure that accepts an `rx.Observer` to start sending events <2> Emit a one or many items using `onNext` <3> Call `sleep` to simulate a slow request <4> Call `onCompleted` to complete the request @@ -60,7 +60,7 @@ If you wish to send a particular named event you can use the name argument of th [source,groovy] ---- -stream "ticktock", { Subscriber subscriber -> +stream "ticktock", { rx.Observer observer -> ---- And then attach an event listener for only that event on the client: @@ -74,4 +74,42 @@ function tickTock() { document.getElementById('message').innerHTML = event.data; }, false); } +---- + +In addition for more complex event types you can use the `rx.event` method: + +[source,groovy] +---- + rx.stream { rx.Observer observer -> + for(i in (0..5)) { + if(i % 2 == 0) { + observer.onNext( + rx.event("Tick", event:"Event $i", id:"$id", comment:"Tick Event") <1> + ) + } + else { + ... + } + } + ... + } +---- + +<1> The `event` method can be used to customize the Server Sent event properties and rendering multi-line data. + +The `event` method also accepts `Writable` instances, which can be useful in combination with builders: + +[source,groovy] +---- +Writable writable = new Writable() { + @Override + Writer writeTo(Writer writer) throws IOException { + new StreamingJsonBuilder(writer).call { + tick "Tock" + } + return writer + } +} +rx.event(writable, event:"Event $i", id:"$id", comment:"Tick Event") + ---- \ No newline at end of file diff --git a/src/main/groovy/grails/rx/web/Rx.groovy b/src/main/groovy/grails/rx/web/Rx.groovy index 93cb49a..9da7a13 100644 --- a/src/main/groovy/grails/rx/web/Rx.groovy +++ b/src/main/groovy/grails/rx/web/Rx.groovy @@ -1,6 +1,5 @@ package grails.rx.web -import grails.async.Promises import grails.web.databinding.DataBindingUtils import grails.web.mapping.mvc.exceptions.CannotRedirectException import groovy.transform.CompileDynamic @@ -17,10 +16,11 @@ import org.grails.web.servlet.mvc.GrailsWebRequest import org.springframework.web.context.request.RequestContextHolder import org.springframework.web.context.request.async.WebAsyncManager import org.springframework.web.context.request.async.WebAsyncUtils +import rx.Emitter import rx.Observable import rx.Subscriber +import rx.functions.Action1 -import javax.servlet.ServletInputStream import javax.servlet.http.HttpServletRequest import java.util.concurrent.TimeUnit @@ -223,36 +223,36 @@ class Rx { */ @CompileDynamic static Observable bindData(Object object, Object bindingSource, Map arguments = Collections.emptyMap(), String filter = null) { - Observable.create( { Subscriber subscriber -> - subscriber.onStart() - Promises.task { - if(bindingSource instanceof HttpServletRequest) { - WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(bindingSource) - // horrible hack, find better solution - GrailsWebRequest webRequest = asyncManager != null ? asyncManager.@asyncWebRequest : null - if(webRequest != null) { - RequestContextHolder.setRequestAttributes(webRequest) - try { - List includeList = convertToListIfCharSequence(arguments?.include) - List excludeList = convertToListIfCharSequence(arguments?.exclude) - - DataBindingUtils.bindObjectToInstance(object, bindingSource, includeList, excludeList, filter) - } finally { - RequestContextHolder.setRequestAttributes(null) - } - } - else { - object.properties = bindingSource + Observable.create( { Emitter emitter -> + try { + if(bindingSource instanceof HttpServletRequest) { + WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(bindingSource) + // horrible hack, find better solution + GrailsWebRequest webRequest = asyncManager != null ? asyncManager.@asyncWebRequest : null + if(webRequest != null) { + RequestContextHolder.setRequestAttributes(webRequest) + try { + List includeList = convertToListIfCharSequence(arguments?.include) + List excludeList = convertToListIfCharSequence(arguments?.exclude) + + DataBindingUtils.bindObjectToInstance(object, bindingSource, includeList, excludeList, filter) + } finally { + RequestContextHolder.setRequestAttributes(null) } } else { object.properties = bindingSource } - subscriber.onNext(object) - subscriber.onCompleted() - } - } as Observable.OnSubscribe) + else { + object.properties = bindingSource + } + emitter.onNext(object) + emitter.onCompleted() + } catch (Throwable e) { + emitter.onError(e) + } + } as Action1>, Emitter.BackpressureMode.NONE) } @@ -264,24 +264,28 @@ class Rx { * @return An observable */ static Observable fromBody(HttpServletRequest request) { - Observable.create( { Subscriber subscriber -> - subscriber.onStart() - Promises.task { - InputStream inputStream = null + Observable.create( { Emitter subscriber -> + WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request) + if(!asyncManager.isConcurrentHandlingStarted()) { + throw new IllegalStateException("Servlet async processing must have been started to use fromBody(..)") + } + InputStream inputStream = null + try { try { - try { - inputStream = request.getInputStream() - subscriber.onNext(inputStream) - } catch (Throwable e) { - subscriber.onError(e) - } - } finally { + inputStream = request.getInputStream() + subscriber.onNext(inputStream) subscriber.onCompleted() + } catch (Throwable e) { + subscriber.onError(e) + } + } finally { + try { inputStream?.close() + } catch (Throwable e) { + // ignore } - } - } as Observable.OnSubscribe) + } as Action1>, Emitter.BackpressureMode.NONE) } /** diff --git a/src/main/groovy/org/grails/plugins/rx/web/NewObservableResult.groovy b/src/main/groovy/org/grails/plugins/rx/web/NewObservableResult.groovy index 6d4b6a3..97e580e 100644 --- a/src/main/groovy/org/grails/plugins/rx/web/NewObservableResult.groovy +++ b/src/main/groovy/org/grails/plugins/rx/web/NewObservableResult.groovy @@ -1,8 +1,7 @@ package org.grails.plugins.rx.web import groovy.transform.CompileStatic -import rx.Observable -import rx.Subscriber +import rx.Observer import java.util.concurrent.TimeUnit @@ -21,9 +20,9 @@ class NewObservableResult extends TimeoutResult { super(timeout, unit) this.callable = callable def parameterTypes = this.callable.parameterTypes - boolean isSubscriber = parameterTypes.length == 1 && Subscriber.isAssignableFrom(parameterTypes[0]) + boolean isSubscriber = parameterTypes.length == 1 && Observer.isAssignableFrom(parameterTypes[0]) if(!isSubscriber) { - throw new IllegalArgumentException("Passed closure must accept argument of type rx.Subscriber") + throw new IllegalArgumentException("Passed closure must accept argument of type rx.Observer") } } diff --git a/src/main/groovy/org/grails/plugins/rx/web/RxResultTransformer.groovy b/src/main/groovy/org/grails/plugins/rx/web/RxResultTransformer.groovy index 8a4c8ed..8309c62 100644 --- a/src/main/groovy/org/grails/plugins/rx/web/RxResultTransformer.groovy +++ b/src/main/groovy/org/grails/plugins/rx/web/RxResultTransformer.groovy @@ -15,8 +15,11 @@ import org.springframework.beans.factory.annotation.Autowired import org.springframework.web.context.request.async.AsyncWebRequest import org.springframework.web.context.request.async.WebAsyncManager import org.springframework.web.context.request.async.WebAsyncUtils +import rx.Emitter import rx.Observable import rx.Subscriber +import rx.functions.Action1 +import rx.schedulers.Schedulers import javax.servlet.AsyncContext import javax.servlet.ServletResponse @@ -117,18 +120,22 @@ class RxResultTransformer implements ActionResultTransformer { // in a separate thread register the observable subscriber asyncContext.start { - observable.subscribe(subscriber) + observable.observeOn(Schedulers.immediate()) // run on the async servlet thread + .subscribe(subscriber) } } else { asyncContext.start { NewObservableResult newObservableResult = (NewObservableResult)actionResult - Observable newObservable = Observable.create( { Subscriber newSub -> + + Observable newObservable = Observable.create( { Emitter newSub -> Closure callable = newObservableResult.callable callable.setDelegate(newSub) callable.call(newSub) - } as Observable.OnSubscribe) - newObservable.subscribe(subscriber) + } as Action1, Emitter.BackpressureMode.NONE) + newObservable + .observeOn(Schedulers.immediate()) // run on the async servlet thread + .subscribe(subscriber) } } // return null indicating that the request thread should be returned to the thread pool diff --git a/src/test/groovy/org/grails/plugins/rx/EventSpec.groovy b/src/test/groovy/org/grails/plugins/rx/EventSpec.groovy index 0019634..0bea2ef 100644 --- a/src/test/groovy/org/grails/plugins/rx/EventSpec.groovy +++ b/src/test/groovy/org/grails/plugins/rx/EventSpec.groovy @@ -13,6 +13,8 @@ import org.grails.web.servlet.mvc.GrailsWebRequest import org.grails.web.util.GrailsApplicationAttributes import org.springframework.mock.web.MockHttpServletRequest import org.springframework.web.context.request.RequestContextHolder +import rx.Emitter +import rx.Observer import rx.Subscriber import spock.lang.Specification @@ -126,23 +128,23 @@ class EventController implements Controller, RestResponder{ RxHelper rx = new RxHelper() def stream() { - rx.stream { Subscriber subscriber -> + rx.stream { Observer observer -> for(i in 0..3) { - subscriber.onNext( + observer.onNext( rx.event("Foo $i\nFoo\nBar\nBaz", event: "Event $i", comment: 'potato', id: "$i") ) } - subscriber.onCompleted() + observer.onCompleted() } } def streamJson() { - rx.stream { Subscriber subscriber -> + rx.stream { Emitter emitter -> for(i in 0..3) { def foo = """bar $i bar $i""" def json = [foo: foo, 'baz': 3] as JSON - subscriber.onNext( + emitter.onNext( rx.event(new Writable() { @Override Writer writeTo(Writer writer) throws IOException { @@ -151,7 +153,7 @@ bar $i""" }, id: "$i", retry: 1000) ) } - subscriber.onCompleted() + emitter.onCompleted() } } } \ No newline at end of file diff --git a/src/test/groovy/org/grails/plugins/rx/NewObservableResultSpec.groovy b/src/test/groovy/org/grails/plugins/rx/NewObservableResultSpec.groovy index 614aa19..08f9ef0 100644 --- a/src/test/groovy/org/grails/plugins/rx/NewObservableResultSpec.groovy +++ b/src/test/groovy/org/grails/plugins/rx/NewObservableResultSpec.groovy @@ -14,6 +14,7 @@ import org.grails.web.servlet.mvc.GrailsWebRequest import org.grails.web.util.GrailsApplicationAttributes import org.springframework.mock.web.MockHttpServletRequest import org.springframework.web.context.request.RequestContextHolder +import rx.Emitter import rx.Subscriber import spock.lang.Specification import static grails.rx.web.Rx.* @@ -119,35 +120,35 @@ data: {"foo":"bar 3"} } class NewObservableController implements Controller, RestResponder{ def index() { - create { Subscriber subscriber -> - subscriber.onNext( + create { Emitter emitter -> + emitter.onNext( render("Foo") ) - subscriber.onCompleted() + emitter.onCompleted() } } def stream() { - stream { Subscriber subscriber -> + stream { Emitter emittter -> for(i in 0..3) { - subscriber.onNext( + emittter.onNext( render("Foo $i") ) } - subscriber.onCompleted() + emittter.onCompleted() } } def streamJson() { - stream { Subscriber subscriber -> + stream { Emitter emitter -> for(i in 0..3) { - subscriber.onNext( + emitter.onNext( render(contentType:"application/json") { foo "bar $i" } ) } - subscriber.onCompleted() + emitter.onCompleted() } } } diff --git a/src/test/groovy/org/grails/plugins/rx/RenderSpec.groovy b/src/test/groovy/org/grails/plugins/rx/RenderSpec.groovy index 8ee5e7e..9cc6e15 100644 --- a/src/test/groovy/org/grails/plugins/rx/RenderSpec.groovy +++ b/src/test/groovy/org/grails/plugins/rx/RenderSpec.groovy @@ -131,10 +131,44 @@ class RenderSpec extends Specification { RequestContextHolder.setRequestAttributes(null) } + + + void "Test render a from body async"() { + setup: + GrailsWebRequest webRequest = GrailsWebMockUtil.bindMockWebRequest() + MockHttpServletRequest request = webRequest.getCurrentRequest() + request.setAsyncSupported(true) + RenderController controller = new RenderController() + request.setAttribute(GrailsApplicationAttributes.CONTROLLER, controller) + request.setContent("Foo".bytes) + when:"A controller uses the render method and a string" + Observable observable = controller.renderFromBody() + + then: + observable != null + + when:"The observable is transformed" + RxResultTransformer transformer = new RxResultTransformer() + def result = transformer.transformActionResult(webRequest, "renderText", observable) + then:"null is returned" + result == null + webRequest.response.contentAsString == "Foo" + + cleanup: + ConvertersConfigurationHolder.clear() + RequestContextHolder.setRequestAttributes(null) + + } } class RenderController implements Controller { + def renderFromBody() { + fromBody(request) + .map { InputStream input -> + render(input.text) + } + } def renderView() { Observable.just("Foo") .map { String result -> pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy