You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@olingo.apache.org by ch...@apache.org on 2014/11/18 16:12:02 UTC

[01/22] olingo-odata4 git commit: [OLINGO-472] BatchParser first draft

Repository: olingo-odata4
Updated Branches:
  refs/heads/master c2c8bf196 -> 51acf8aeb


http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/32247295/lib/server-core/src/test/resources/batchWithContent.batch
----------------------------------------------------------------------
diff --git a/lib/server-core/src/test/resources/batchWithContent.batch b/lib/server-core/src/test/resources/batchWithContent.batch
new file mode 100644
index 0000000..834857d
--- /dev/null
+++ b/lib/server-core/src/test/resources/batchWithContent.batch
@@ -0,0 +1 @@
+/9j/4AAQSkZJRgABAQEBLAEsAAD/4RM0RXhpZgAATU0AKgAAAAgABwESAAMAAAABAAEAAAEaAAUAAAABAAAAYgEbAAUAAAABAAAAagEoAAMAAAABAAIAAAExAAIAAAAUAAAAcgEyAAIAAAAUAAAAhodpAAQAAAABAAAAnAAAAMgAAAEsAAAAAQAAASwAAAABQWRvYmUgUGhvdG9zaG9wIDcuMAAyMDA1OjExOjE1IDE1OjMyOjQwAAAAAAOgAQADAAAAAf//AACgAgAEAAAAAQAAAV6gAwAEAAAAAQAAAZMAAAAAAAAABgEDAAMAAAABAAYAAAEaAAUAAAABAAABFgEbAAUAAAABAAABHgEoAAMAAAABAAIAAAIBAAQAAAABAAABJgICAAQAAAABAAASBgAAAAAAAABIAAAAAQAAAEgAAAAB/9j/4AAQSkZJRgABAgEASABIAAD/7QAMQWRvYmVfQ00AAv/uAA5BZG9iZQBkgAAAAAH/2wCEAAwICAgJCAwJCQwRCwoLERUPDAwPFRgTExUTExgRDAwMDAwMEQwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwBDQsLDQ4NEA4OEBQODg4UFA4ODg4UEQwMDAwMEREMDAwMDAwRDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDP/AABEIAIAAbwMBIgACEQEDEQH/3QAEAAf/xAE/AAABBQEBAQEBAQAAAAAAAAADAAECBAUGBwgJCgsBAAEFAQEBAQEBAAAAAAAAAAEAAgMEBQYHCAkKCxAAAQQBAwIEAgUHBggFAwwzAQACEQMEIRIxBUFRYRMicYEyBhSRobFCIyQVUsFiMzRygtFDByWSU/Dh8WNzNRaisoMmRJNUZEXCo3Q2F9JV4mXys4TD03Xj80YnlKSFtJXE1OT0pbXF1eX1VmZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3EQACAgECBAQDBAUGBwcGBTUBAAIRA
 yExEgRBUWFxIhMFMoGRFKGxQiPBUtHwMyRi4XKCkkNTFWNzNPElBhaisoMHJjXC0kSTVKMXZEVVNnRl4vKzhMPTdePzRpSkhbSVxNTk9KW1xdXl9VZmdoaWprbG1ub2JzdHV2d3h5ent8f/2gAMAwEAAhEDEQA/APVUkkklKSSXBfXb/GBVgVHD6WW3ZtjnsDnDdVW2tzqLb7G/4az7RXdj4+O79FvotyLvUr9D1Ep6rqX1h6Z06gX5F9ddbvoW2vbVW6Bu/Rvf7r/b/wBxa8hc3mf40ugVtLqMr1CJ9tWLdbx/wtzsBq8myszKzcl2VmXWZOS/6V1ri939WXfRZ/IZ7EzGF5k6Dj5o0i3vOtf4yX/rNeEMp14ZFGS21ldW9zA5tv2P07nfo3u/mfVs/wCEtQ7f8aX1iwcmzFLcTOrodsbe6t9b7GgD9I/0bXUte78706vTXLYXTX34VuTWf5gv3jxAG9US0GsEcjukp9S6L/jQyOouLLOkTs+mcfJpc/8ArDGyzhv2/wDXF2eH1PFzIbWXV2kbvRta6t8d3Blgbva2fp17618712WVWNtqcWWN+i9pghdF0T6+9b6Y9rL9vUcUO3ejadjwfGi+v+bf/YSpVvuKSzug9bweudNqz8Gw2VWSDuAa9rm6WU31j+bur/O/M/wlX6NaKCVJJJJKf//Q9VSSSSU5n1gy8nG6ca8IhudmPZi4hOobZadnrxDtzcSn1cx/8jHXh/1mxcDC+sGdh9OJOJj2CquSXQWNa25u930tt3qf217Zlb8n6x4tIBNXT8ezKcewuvP2LEd/7Djqa8Cs9Qlz3Bx3PcS9wMEkud9I/nP+kiEFiNTp3Vpg2gAdlWYYcDzqtrovQM/rb7W4L6AcfabfWs2EB30XCtrX2Pb/AGUlPQfUPFrv6dmm0Sz7Q1jh4hzDv/6K5G/HOLk34hMnHtspn+o91f8ABek/VXpR6X0m3Gssbbccu43OaCG7qz6Aazd7tu
 yvd/bWFm/VinqGNl9U9c4dlGVlsuea32se1t7yywsxw69r2Ns2b62WexK0vEEQY8EyLktY17tljbWAlotZO10HR7N4Y/a7+WxBmdRqEUPZ/wCLDrg6d144FztuP1QBjfAZDPdQf5Pq1+tR/X9Bexr5y25uGMXNYx1RcfWw7iPY91Lg6arB7XOptbtuZ/OVf4RfQnTc2vqHT8XPqEV5dTLmDmBY0Wbf7O5NKWykkkkp/9H1VJJJJTwvXfrg7pXUetV4WM/Mz2GqvcWzRTU2lt7bL3g/pHerkZX6pX+kf6f+DXm/Xer53Vsht+bbbZXUSymt7g4hzv0lz7C2Km5F7v0ljamfoK/Rxa9lFFVa6T/GB1bHx8rI6Z06WG+19+S/dJLnkGx5/lXOqqZV/osTFp9P+fXI44osxTW8HfQ912v0XMLWM2fyXeq2tv8AbRCCyw8HLyAH1troqILm2XE+4Ax+jYwPut937tfpf8ItvE699Y+kVCnHyMP0Rr6Hotr3fGz0muc7/jXrHOXl1scTYQ7IaHOcIBIH6OsN/wBHXt+hs/wf6OtVSdZmSdSSkp6Rv176tNrKqaqXl7rrIZ6p3ODQ/bvura1rnM3/AEbE1f1y+sJxBXj5VOBXue82VsD7rHWPfbbY1jd/pe5371CzcYtFddd2Q2pjXF7K3EiA6N1gimxv02/v2ZH+ipVTJcXZFrnHe5z3OL+Zkn3Ttr+l/wAXX/xaSkl9udfc+85dltjvdZa6sNJ83mv1FTe+5rpt9x8SAZHjuaj0WPZYNjizcQ0xwQdPc385IDc1zPzS1zgD4t1cG/5u5JTr/VvL6e19vROsNLuk9Sc0uO6DjZA9lPVMV7/bW5jfZk/8B9P1qK7KV6/9VumZXSOhY3TMp7brMPfU21ugfWHv+zv2+7Z+g9Pez8x68BL3ENM6gD7wI/gvWv8AFr9ZrOo4LOn5LybsJoq1jVnOPZ/J2+/G/wC2EikPdJJJIKf/0vVVR61
 1KvpfTMjOeQBSwkT4/mq8vPv8a3VhXhNwGO91ha2ATEuPqW7v5VVNbG/+hiSny3KybMrJsybTusueXunxd/5H6Kkz213tJDHFgAB0kh7HbW/9UgztIPKs0mvextwLqSYc9v0thIEtn89kbUUMb7jbYXgbW7WNa3mA0beyiGk6ak9h3PwH0l6C/wCoX1fHT33YdmTfbbWX41r7Ghslu6r9DVWxv+evP6X3Mey6txquqc17HcFtjTuaR/KrsCSm19nys9xtc0N2MDWsYxx2t1LNzK2vc3d/wn6R6bMraGY9jJLH1SbACWyCWvHqRs/N/rrovqv9Yvq/gY+XX1+i67JyHvyacj0zYLHPH09Cx+/1Ge1/83/wiB03635vT6ep14dAbh9Rfbc2h3tfU+0Nroyq7o/SMY6r9LT/AOCpKebYSHNfMgEHTyMold0+nU/aBT6hLxJkPH0f+kuk+pn1fxOq0ZWT1Cs3NFjaa3OLgZj1brA5pa71PcxX/rL0LoPTsvAwOntNNt7jdkPtIc2uhgPq27tjbHWbWv8ATr3/AE9iSnhXtcwbHNLXNAmedRuWr9U+rWdJ65jZLdzmFwZZW36Tmn81n/Cf6L/hVS6raLs23IZIZc421bvpFrj7bXf1/wDBf8GqoJHBg9ikp+kqLq76a76nB9drQ9jxwWuG5rtf3moi5L/Fx1k9Q6N9msIL8QNNcT/NWb9rXz+dTk05eP8A8TTSutQS/wD/0/U7LGVsdZY4MYwFz3OMAAauc4rw767dR+3dRr1O7Ycq4HkWZRbdXV/1np7MCj/ra9a+tNzD05vTi4Nd1SxuIZjSpwNue9278yvAqyXLw3qmd+0OqZmfEDJvssYD2YXH0m/2atjUQotMrS6VT9pJxHw0ZD9mLY7RoySN1dFjvzKsxv6Df/g7vRt/0qzy1ruDHkVaqLX02Y7yxzLADtd+jO4Dbua7+a37fb9KtJDvU/WvLw/qx+zanObnG51dVh/nK6tX
 XSz6TMhln6H+R+l/0awun4dnUM/HwmPDbMuwVMc/WXPO3+1v/lLqsCzMv6dm9RYcY5NlYd1DFt0tynY8j18V9e7Zdl0vZXlVf9q7vVtp/pKF0XC6Vh2dJ6i+237dj5H2vOqbhZWjYaKcHF20ei1uP793/CP/ANHWkpHR0zGZ0QevWLbGfWSrDfa/3PNTWbLKfUb/AIJzvzGrP6v0puNn9asoDmYvTsx1DWGXNDLXOFVTrCXPr3R+je5dC1uGOnvxTfeXv6wOrBwwMuNv+gP6L+d/8DT59eFl0/WCttmWz9u5FN9R/Z+UfS9F/qllvs9+/wD4NJTzn1e61mdE6g2ysPyMO0j7TjN13sP0cilv5l9cf+69i0/rFkYuX1TM6pdNuFjxjUtiG3Wjc6rFa76XpPY1uTnvZ768H9D/ADmfSonHwMbrIxOn5D8DCymix+Tl49lb8Y1j1L6q67mtfk4+Xt/Q17vp/wDblmPmWVWvLbbnFlLy2sBpcXNM2XZe+x/89l5Dt9tT6v8Arv6CpIpc7Jutvtffc/1LrXbrHmBJP8lvtb/JY36CEFYvfX6LWDdvnWeDrO7b+bt+ggDlJD1v+L/q7endWpe5+ykWCm4GI9HKLKNznH6P2fqFeB/1q/IXtC+dOnX0U5X6y1z8a5llF7WQHFljHV7qy/2epVZsur3/AOEqXs+D9Yr8/wColnWsdwOfTg2us0GmVRW8Wbq//DFe9rf9EkUh/9SX+NjrYf1KjpVDo+y1F17hzuv2ubT/AGaqmWv/AONZ/LXnpXf/AOM/6rdQZ1K36wY7HX4V7WnKLRLqXVsZTvewe77O+qtn6X/BfpPVXAESA4cHg9iigsHFWbYbteOHBVStLpeN9tz8DCcdMm1lLo1O17mMf/msSUhoy8ihxNDzWTEwAdQd7HbXAt3MeN7HLq6frD1bqr3W4mZlHMc0HK6WzIe2S1sPzOlNbvfZRsb6uV0yr9Yx/wDtN6lH83g/WLpGL
 0jq1mNiZVeZiuHqUPY9rntafo05Ppy1trP3v8Mz9Is5rnsey2t7q7ayH12MJa5rhq19b2+5j2/vNSU9hjZd19lVnr2Wi9ss22EFw49j63ZnUH/2qk1tub64qY+xrWtL8kWXGplVYljr8u977upV0t/l012Xv/QUV+osTG6/ktvttucG23g+u9tcttfB23349dmNU/L/AO7H+E/7UU3KlmdVysig4jQ2jEe/1bKqwGm14+jZlPaG+ts/wVfsx6P8DSkpn1XPZmXn0S91DdGPuc59j4Jh7nXustrq/wBBj+p+ir/fsQtw9Frnak/jqqomIRmhoEkyfDsElI7jwTzH5dVAHupW9vKPw0UAipnyfius+pfWMirA670gkux8rp2VkNH7t1VXpl4/46l+2z/iKlzfT8DO6hmNw+n0Oysp3FVYBIH71jnQypn8u1zGL1L6vfUDI6T0DqbrXNu631HEuoaAYrqFjDsx2WfnOst9N2Rf/U2fzX6QFQf/1fVVzHXP8Xf1a6w51xpODku1N2IRXuPO62na6i137z/S9X/hF06SSnyHqX+KHr9D3Hp+Tj51Q+iHl1Fp/sRdR/4Mxcruzuh5mQ0tZXm0NtxLIcH7H2NNORssrca/VqqsfV/wb19Dunadph0aE6iV8+/WbonUuhZ7cDqTm2ZDm/aDewlzbPUJ32Nc9rH/AM96jX72IhDkB20Q0ADwCcXPAjhRU6qbr3iumt1tjuGMaXOPyaipTXbj7jz4KVpY1o28k8ob2uqsNVgNdjTDmPBa4H+Ux8OT01W5FgqxmOvtPDKml7v81m5JTKpxe8NcYnwVn7PLTtcS6NAeFGzpvUMPIYzKofQ76QDxoW/vNIRg6CkpsfVnoDvrF1T9mNyGYdxYbGutk7tpHq111jbvtax3q7P3PUXpHTf8UXQMfa7Pvvz3NGrJFNZ/sUfp/wD2ZXLfU/6odX6tmY3XMS1uHTg5FZFz5m303frDaGt+l6bN9Lt/6O
 3+Y/wVi9jQKWtg9OwOnUDHwMerFpGvp0sDAT+87YPc7+UrKSSCn//W9VSSSSUpcv8A4wfqq/6x9HH2VoPUcJxtxQSBvBEXYu93tZ67Wt2/8NXT+YuoSSU/NFlVtVjqrmOqtrJbZXY0te1w+kyxjvcx7VPFy8rCvbkYlz6Lm/RsYYP9U/mvZ/IevdvrB9Sfq99YHG7NoNeXG0ZdB9O2NPpu91d23b7ftFdq5Sz/ABNY4cTV1OxzezH1gH/t1jv/AESjaKeSb9fOuWDbn04PVG/mtzcVjw0RG1npGn26In/jh9daw1Y1GBh0BpAox8bYzcfo2R6jvcz/ALb/AODXQu/xPZROmc2PGYP/AJ4coj/E5lzr1BgH3/8AopiWitXz/LzMvNyHZOXc6692hsd4DhjQ2GsY39xi1vqv9XupfWPNbiYoLamEHKzCPZUzv7vovyX/AOAo/P8A+J9Sxd1hf4nemse12dm2XNBBdXUPTkD/AAbrHPs9jvzvTrqt/wCEXc9N6ZgdLxGYfT6GY2PX9GusQJ/ecfpPe786x/vStVMsDBxenYdODh1irGx2CuqsdmjzPuc7997vpqwkkglSSSSSn//Z/9sAQwAGBAUGBQQGBgUGBwcGCAoQCgoJCQoUDg8MEBcUGBgXFBYWGh0lHxobIxwWFiAsICMmJykqKRkfLTAtKDAlKCko/9sAQwEHBwcKCAoTCgoTKBoWGigoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgo/8AAEQgBkwFeAwEiAAIRAQMRAf/EAB0AAQAABwEBAAAAAAAAAAAAAAABAgMEBQYHCAn/xABNEAABAwMBBQQHBQQHBQYHAAABAAIDBAURBgcSITFBE1FhcRQiMoGRobEIFSNCYlJywdEkM0OCkqLhFiU08PFTY3OywtIXJkRUdIOT/8QAFgEBAQEAAAAAAAAAAAAAAAAAAAEC/8QAGxE
 BAQEBAAMBAAAAAAAAAAAAAAERMQISIUH/2gAMAwEAAhEDEQA/APVKIiAiIgIiICIiAiIgIiICIiAipzzxU8ZknkZHGBkue7AHvWuT62tHauhtxqbrUA4MdvgdNg+Lh6o95QbOi1J921VWD+gWCnomHk+4VQLv8DM/VWNTR6sqP+K1LSUTeraSjyR73FMNb2td1Tq62abdGyu9JlnlBcyClgdK8jvw0cB5rUqjTD6hx+8NZX6TPPs5mwj/AChW94v9i2WULaauudZUS1zu0gdKTNI1gAzl3dvEn3+CuJqtU7ZbVBnNj1CQOpoXBW0+3Ox00ZkqbRfYWD80lLuj4krXJttennDLa6Z3h2Lv5Lme1jaj/tPRNttsklbQkh0pcCDIegx3K4a9WaP1PbtV2WG52qQmCTI3XjDmkcwQs7kd68OavtR0/Y9M3K1VczGXCk3ntZI4YeDx4g9crCUWrr9TseYr3conNHq7tS/H1Uw17+ReGrdtb1rQY7K+1Mg7psP+oW22r7RGqKUhtbBRVjRz3oy0/EKK9cIvP1j+0lbZi1t3s88Pe+nkDwPccLo2ntrGjL4Wspr1BBM7lFVfgn/Nw+BQb2ikilZMwPie17TyLTkFToCIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIVi7lcXslFLQRCorSM7pOGxj9p56Dw5lBeV1bT0MBmq5mQxDm55wsE64Xi7n/AHRTtoKM/wD1lY313DvZFzx4ux5LXdZao09ohgrtTVnp93xvRQNALgf0M5NHiePivPGvdtuotSumgo5fuu3ngIoHeu4fqf192FcTXfNUag0RpcGTVF2N1r28eykd2zs+EY9VvvXNb99o2SNpg0xZYKaEcGunOT/hbgBeeJp3yvLpHuc48SSckqRrgQcgk9O5XDrpV32060uTiPvQ07T+WBoatYrda6lrD/SLzXv7/wAYha44boyeAPcpct6ojJT3i5T/ANdX1L/35nH+
 K2PXl4fdaXSjHSb5prRHG4uOeO+/n7gFpe8OjVMHulcxr3Eho3R4D/koKrd5w4SNB7uATde7gHAlVewiaeGXeYVVrGtA3G4Q1tOrrlHW6C0nEx536ds0TxjkQ4LRyfFXEUkj8QucTHGXFre7PNSzsLW5AyO7uQUd7h/qoFw/6qXI6hQOPFBtWmJNOzvZFdaaRkpOA/tDun+S6LS6Q0rVsH4M0WR7TJlxAHxWyab1PUWwthnLpKb5t8lV13jTWla+zOa/Surq2jxxEFQ0SRHwIyui2zWN+tDGt1damz0vL7yteZWDxfH7TfMArhdn15QQuafS2DzOF0/TGtKeqDewnjk/ceCphrsNquVJdKNlVQVEVRA/k+N2Qrxc+oGUklUau0y/dte/i4tH4U3hIzkfMYK262XF07uwrIvR6xoyWZy14/aYeo+alismiIoCIiAiIgIiICIiAiIgIiICIiAiIgIiICIsdqG70tis1Xc6+QMpqaMvcT9PMoKV4uToZ6egpN11fU5LBz3GD2nnwHDzJAXI9re1Wl0PDLZdOFlTe5Bmeocd7snHq7vd4dFh7htFms2hbhq2ocPv7UEzoaCN39hAzgMDoBknxJC80VtVNV1Ms9TI6SWRxc57jkknmVYlqtd7pWXaulq7hUyVFTKd575HZJKsicNyeJPJSlS571UTZHVTB/q4LRnoVTxxwpmtJOMIJnOe/GTy5I1u8RgZKMHrDqr6GLcGeRQUWU7zzw3zUtGzFQQeJCvRjpknxVvTDFU/zQbBWW1zaVksbeOASArKrpnQxQueMbwXRbdRia307w0YcwLEa0t5jt8crW+wcFBz6JoFS/PVViAfJUTkVPFV0FjKzDiD7lSV3UNy3I5hWpGEEEREEchV6SsnpZBJTTPieDkFhwrZMoOpaN2u3WzysiurRXUo4F3KRvv5H3r0roTV9n1jbmmjqA57OOM4kid5dF4aaQDx5LLaevdfp66w11qqXwVERyC08
 D4EdR4Ia+hFJM/Jinx2reORyeO8K6XPdlOuaXXunI6mPdhulLhs8OfZPeP0lb/DIJGBwBHeO4rLSdERAREQEREBERAREQEREBERAREQEREBebvtNaqnr6+n0lanOe2Iek1YZ1IGQD4AZPwXfdT3eCw6fr7pVODYaWF0hz1I5D3nAXD9kmm5LxZdQavvbO0rbuZI4S/8sf5iPM8PIIPOepb5PdnUTJCW09HA2ngj6NaOZ8ySSsKTwWW1Vbza75VUnSN/DyWJK0yKRRGQhOUEWcHA9OqqBxY4lvNUhjiqjBvEdyC4pGcS9w8ldj1jxVONu60DCqNOEFQAK0gGKqVVySeiowf8VKg7npGn9I03RPx+TCn1VaTVWGrYG+sGFw9yyWziEyaOon46FbC+kbI17HDIcCCstPKj8iq48xzVYHKvNUUJtupKqmcMbkhx5KyBwStMpXjLSFZOWQdyVjKN15QU0yiIIEJhEQRBU+d5uO5U1MOYCDbtmmr6rRuqKW40zndiDuTx54SRk8QR9F7pttdBXUtLX0bw+lrI2yMcORyMgr51N4HhzXrD7L2pvvbSNVYKiXNRb3b0OTx7Nxz8nZ+KlWV3ZFTgeZImkjDuRHceqqKKIiICIiAiIgIiICIiAiIgIiICIhQcV+0Xcp66OyaQt5JqrrUtMjW89wHAz7+PuW+3CGm0zpKnoYd1lNRwBueXADifqucaTH+2G3273d/r0VjYYYc8Rv8Asj/1FVftJ6k+7NJvpYn4nrn9g3HMNxlx+Ax70SvL2sLn98ahra3PqySHc/d6fJYNVXHiqRGCtIcgoJlEEQMq6pmZ4nkFat5rIQDEYQVkxhE4oAdxOeCpQf8AFS+QVXqqcY/pjx3gIPTmzGDd0NbierSVnWt/FPgqGiqb0bR9siIwRA0keavMYJyFjWnn7bfRim1ZBKwACeIOOO8EhaGup7b6Y1N/tMbfbfGWgd/rLlr2lj3McMOacEFblShOVbVI4g
 4VwpJWb7D3hEWSKJGOB5qHJBAoolQ6IB5KI44KgFM1BMDghbvsh1U7SGt6Guc4ilc7sagDkWO4H4c1pLW8FMDxyg+jcEjHHfjcHRyND2kHgVcLlf2f9TnUGzukbO/fq7a70WQk8S0D1T8Poup9VlpFERAREQEREBERAREQEREBERAWK1XdG2XTV0uUnAUtNJLx6kNOB8cLKrl32iLg+DQQt0BPb3OpjpWtHUE5I+Q+KC2+z7a32/QMt0qB/SrpM+oc48yM4H8Vyf7UJmN2tLpHfgmN5a3xyMlemLZbWWjTFFbYQA2mgZFw8BxPxXmz7Uo/pViHXcl+oRK4K5UnH1lUfkcVSK0gihzUcccIJm81kISHMBHLCx2CFc00m64NOcFBdg4UWqLRlwb1PBdJ01slul4pY6qSqp4IHgEEHeOEJHNuqvdOUEly1JS0sTS4yuAwOPDPFdtoNitsgw6trp5yOYb6oUbZYbdp7ahbae3wBkUlFIePElwPNTVx0yliEFJFC0YbGwMHuCoPGc8FfOxu8FaSdSsYrj+1PDdZaZe4cO1Gf8YWF2i6Dq4q+W4WmIy08h3nRt5tPXgsztgbu33Tkg6TY/zNXV6eEPA3uq0PI88EsDy2aN7HDo4YVPivUWp4dKxkQ3xtLHI4ZG+MFce2g2vStPTifT9aHT5wYWnIIWozXNqkAEHHNW6v5WBzcEKxc0gkFBKiJ0wggCqjVSIIPBVIwXkAH1jwwUFZSNPrlVqmlqaKXsqyCSF5AcA9pGQeo7wqA4SclV47r9lW+ei6rrbPM7EVfCS0frbx+hK9WwEmMZ58ivAmzi8mwa2s9yDsNhqWb/7hOHfIle+4iMuxxB9YHzWFVEREBERAREQEREBERAREQEREBci2if762v6Lsp9aKlc6vlb09XiPm0Lrq4/pR33vt+1LWe1HbqVlKw9xOM/xQdcmZvRkdFxba/s8m1vVUBhqm0opS8Oc5uQWnB+WF2Wvq6eho5a
 qtmjgpoml0kkjsNaO8lea9c7SK3Xl9/2b0jMaGzu3jVVvsudE323E/lYB8UStaj2TW2qt9VdfviKj05QucyatlO9JO9vAhjRwAzwHUrj1zFKK2YW8yGlDiI3Se05vQlbjtF1fHc4qWw2Iug03bRuQRjh27+sr+8k5x3LROJdw4k9FpEOSgM9OauBSyHBeAwfqOPkqzGwRe29pPkgtoonPxgH3q9ghDemXdFKauBvDLz5cP4K6ob42jeHRRbxHEb2D/BBk7Tp26XaRrKKjleT1xwXoDZRpy76dtczLtUlzZCCyHOdxcatm1i60DQ2FrGtHQMb/ACWwUe2qskc30iOJ3m3H0RY76/lvZK0O/tMe0XTlR+V8c0JPjgFYm17V6OrAFRDgnhmN4PyOCqt71Haqqe017KuNopasF4f6rmtcCCSDx7llXRXygKk6UEHitKuO0jTlKSBXCc90TS75rA1W1mztP4NPUyeYAQVNp9L6VfNLxtGS+tDfm1diihaAPVC4GzXFBftTWCeeCWlhpaoylzhvAjdOOXXIC2nUO2Cjpd5ltja8jh2jzw+H+qDdtW6Vtuo6N0VfC3faPVlHBzfeuC6t2cV1mc+WlmiqaYfqAcPcqV82sXWtLmtqntaekfqj5cfmtNrdVXGpeSJXA9/M/E5PzWp8TUzaOpkcWsgkcRz3WkqEllrX+sIHNP6uH1WLmuVdPntaiZ3m8lWzppD7Tz8URk5LRXM/sHHyIKtpaOpiyX08rR3lhVqJH/tEKpHUTx8WSvafAkIIgA8+ahjcOVXFfI/AnDZfF4yfjzU+IJxkZiz38R/NB1nZDd7TqqnbovWkbZqeXP3fVuOJaZ/7LXdx7uWVrm0/ZrddCXPFQ01Ftkd+BVsHqu8HdzlpQZUUU0U7CW7p3o5YzwyO4jqvZmy/UVu2obO/Qr0yKpqY2ej1sL+ZPR47s8896lV4wZ6rshe99ml1N60LYq9zt58lK1rz+powfovJG1vZ
 1WaEvjmtD5rTO4mmqCOn7Lv1D5r0B9mG4Gr2dGmJy6kqXN9xwf5pVdgCioKKgIiICIiAiIgIiICIiAiIgEgDJ5LzRsv2g2TT9x1neb3UubNWVhfDExu8+QZccAfzXoHVtaLdpe7VhOOwpJZAfENOF4o0JpGt1ZdJGRMcKOnb2tTNyDW92ehPyGT0QZ3ajtLvWumPYGOpLJHKBHTsPtu6bx/MeuFobb2+jsdRbaEFkta4CqlHtOYDkRj9OeJ78BdUqLXboLLNdTCTR4dR2WmaMOqZDwfPg9O4nkBlcon9HtW8ync2Wq/POOIae5n/ALlYlWQoOyAfWu7MnlEPbPn3e9UpKpsOWU8Yj+vxV5Ym+m3hna5LGh0js9cDKxVQd6Z5xzcSqiR8r3Z3nHipACeQVaGB88jWRtL3uO60NGST3LdaSgt2mAPvGFlxvRAIpScxU5/7wj2nfpHAde5BgbJpK83pu/RUUnYD2ppMRxt83OwFmm6GgiGKu/24SDmyn35yPewY+azrn1txayW6zumx7EI9WKMdwaOChKRGziQxo9wQYJ2kKAD1Lu4n9VI8BWFRpYtP4NfRyfvPMR/zAfVZapu1NHkNcHnwCxNXcmT8AwBUkYurtNfQnMkT2tPJwOWnyI4H3KjHX1bGhhlcWtOQHHICycNbJBnsXFoPMDkfMciradktxqWxwU+9O84a2JnFx8AEKqwXufID4aaY/qgac/AKtNf6gYEVNR05720zQfiQVBtluNtqQa2nfSub0lY4H4AKrUxur3NY4iR/INY1zSfiFBiqqvrKh2ZZnPPQk5x5dyu7Zp+4XQGVsZEDfammcGRt83u4BTVFuq7RURyVNJJC08WCZvB381Ur7rPXuaat8kobwYwnDWjwaOAVMX8VpsFFj065vqpBzZRREt/xuwPgCoyVOn42ltNZ53/qmqOPyCwrpmdI/moCQdW/NF4u5pLfIfVoTGD+zKT9Vay0tJJns3vjPQPGR8Qqk
 PZvkDXODM9Ss390xmly0hxxkEdVEapUUUsIDi3LDycOIPvVthZlr307y3gW8nMdxBVOqpY3M9Jpf6vOHsPExk/w8UGJIV1SguilaBkt9ZKqmMW69v8AVP5H6hT2sgVsbXey/wBQ+9BXhqHUwBGHRv4OY7i0+a27Z9qSq0ffor3ZXOkp2+rV0pOSYzz8x49Oq1BzPUfE7mwkKShqZaOpEkTy17DkEfRCV71lhsO0fRsbnhlXbK6MOafzMP8ABwK1HY1oy4aHut/ttSHSUEhZJTVHR44jHgR3Ll2wvXcVguIgmf2dlrZA2eInhSTHgHj9DuXgV6nBB5clloHLKipW8lMEBERAREQEREBERAREQEREGs7SLfW3bRF2t1rYH1lVF2TATgcSAcnyyuX3u0UWj9LU2jKKct7dnpN6rIx65hzgsb13nnDGjxXbq+rhoaKerqniOCBjpJHHo0DJXm3VupHW6lrtQ1oH3nWSl1JC/juSAYa4juiaeH6ye5BpO0/UL4qh1EGMhqWxiJ0TDltHEPZgb44wXHqeHRcllkdI455K5r6mSpqHvkeXve4uc5xyST1Vq1vV3Bq0yz2jIHT11S1vtejuA8zwWMrKN0Nwkp35aWnDvAK603cTbro2ZvBuN057lnbnDT3K7RPpyM1jQ95/7NjfaPvwgsLbUiz03pcTQK2YFtPn+ybyL/M9PeVYQTFs4keS4728c9Spayb0mskkAwzOGDuaOAHwVSmYQ0yFu8eTW95QbG7UZpqACRjH1L/YjH5R3n+S1uvq6mokJqZCXHjug8B7lSpnkzSTyneLBvceruioFxcSXHiUE7iQ0KTJU7j6oUmFV1Mwne4lX1vrKi2VcVdRSOjngO+xwPIrHtHrBZCKLfjcMdES1n7hV6nrnMrLpJUNhlaJGgDmDyIChSS3J72Mom1Bm5AlmMnzyoM09d5LXQXCvq2Op5I2mGMzjeDOnA8lkI7fWV0TaaARQSHg17p2jJUGG1
 dX3qqrYaK/hzZqRm6Gubunjxye9YHdWc1RQ3OgvlRT3x0jq1gGTI/eJGBjj5LCvcFVimCCo8FDHEHgodUVN0V5QXCekdhh34+rCrJTMeWvDhjIKC7q3NlPaxnLXfEeat4pnU8oe0Bw5OaeTh1BVzcAymrT2YxFI0O3enEK2maG+OeIUZXL42lrqUnMMw34XHoenwPD3+Cw7N6KTjwc0/ArLwDt7bPH/a0347D+nk4fMH3FYutcHTdo384yfPqguK6TFa5w5Pw74hWjz+Jw6qpOe27Nw4uAAIVJ7XAg4KDJ2W4uoqoPwHsILXxu5SNPNpXrvZDrP740pPbpKpzq2ig34Jn8XSQ/lLh1LfZd5eK8Yg4IIXQNmGqKmyXylmpzvSxu32MJ4SA+3Ef3m5x+oBM1Y9oabu4u1M9zozHNGQ2RvMb3XB7lmAtE0hW08V3YaR+/bbnCJ6V/z3fMcR7lvayqKIiAiIgIiICIiAiIgIig4gAknAHMoNB2rXPdo4LVHI2MzntZnu5NjbxyfDIz47uOq8h7QNSm93SR0O82ji/CpmE8WxjkT4nmT3ldb23ap7SOufE/Elc7sY+8QN/nw+JXnad5e89ysSpS4E56qTPHmjeDhnkp42BxPHiFUGc/FbJp/MNmvNc72mxMpoyeheePyaVrmMOWep5dzRs7OslezP8AdjP/ALkGMafWAHUrYayJlBahIR+JuYb5lYS1M7aviaeI3lf6sqcyRQZ9kbxCDEsw2jd3uf8ARUlMTmmYP1FUsoK+fUCkyVAP9XChkdEFRhy4Ad62Kgpy5jjjk3KwNviMtZEwcyVuEtVQ22ne2eVpkLCNyM5dn+CDUYqwtez0l8k4YN1rN/AAHRX895jniEXowiHRzZCT81giPXOM46ZVR4OAgzVVI+qeZ3Oc7eGPWOSAMcFjHnDsFZegie+YMDch8bSOIHhlY+vhEb3Y5tOCOoKpKoAgoSqO+pt8I0mPBRyN3A5qnvBC4Im
 sleT69Nnn2LUkp3ijjLhg8m+PVU707+lsZ1ZEwfJZyiDarTeTxdF17iESsXYns+86drhgSkxO8nDBHzWGqWFkjo3c2OLVf0jwy4QubyEoI+Kp3poZd6xvQTO+qgsugHcobx55+KqxMLhnHDv71JIzoOJ8EEg4EhXNHM6KVrmOLXNIcCOhVrJ7ZA78I1xBGOiD03sm1BJdLI6gjditp819CB+00/jRD/zAeK9CWitZcbbT1cRy2Vgd5FeHtml7qLddYTSPxUxPFTTeMjQcs8nt3m+8L1/oivgnibLRuzbrgz0ul/ST7bPMEqVY28IoBRUUREQEREBERAREQFrWv7kaCwyRxn8eqPYsGe/n8vqtlXENseod2a4Sxv8AwqCM08ZHWVwG8fdkDzaUHnbaTd/vHUFR2bt6CD8GI94HN3vOT71pR71e3GUuncQeOeKsSFplDOCp8HJI5hUwMnA4lX0TN5rmkcUFEHIyfes3HA5+jxIwEhteQcDPOIfyKwgZ7Q7llKO7mnsclvDSHGpbUb3Tg0tx80FnbKwUteyRwO4Dg+HipLrUGpuE0jTvMJw0+Col2ST3qcSfhFnDic5QQw4wtHUEqUNPUgKYBzuQJPgpmtefy/FADBzLvgFEbgPBpPmVHs93jI4DyU8b2B49TLe9BL644j1fLgqfUrKUdtuF2EpoKKeobE0veIYy7db3nCmpaBlLuT3RjhEWh7Ic4dJnl5Dr/wBUFnQW6qrX7tJTzTO7o2F2PgspUabuzI9822rOPaxEThZihpbjd6YZkNLQDg2KL1WfAc/MqpLpeaMGS31L+2bxADt0n3hBbaV7CSmnhrYiJGubHnd4sacjPxwsRqCKaCvkiqWETt4OcPzjoVkaK5yGu7K7OImGWduR67fB37Q+ayN90rqKurWyxUU1ax4BjdTtL94EZyMKcMaQWB3JuT4cCqZYO8g+KzlXY7hROeLlbq2mc1pI3oSDnoOKw7pSDuycf3grKWKX
 Z4/N8QotjO+0FzcZ4qriNwyN4eXEKXDBx7QYQT3NzqitnljGYyeBHd0UYK+eG2y0rODZHZPHioRTCJkgh4ve0tyegPNWwDgcYQXdtjfJUMORhpB5+Kq39ub7W/8Aiu+qpUTjA8vBy4jG6BklXtDRzXOuL3HL3nGd3kep938UGMkeWepnjyPh4Km48QPFVamLsKqWIne3HEZ71Rfhkg3jjHHggpOOXuPTKgoAKYIL+1VL6Wqjlidh8bg9pHQgr1PsRv7J31Fkc8DtG/elt495xLGPI54dxK8nRktcCOi6Toe9T0NHBX0bj6dZJ21jB+1ASGyt+bT8UxY9tQSCWJr28nDKqhYuw18Fxt1PWUbg6mq4xPER3EZIWTCyqKIiAiIgIiICIiDGakubbPZKyvdxMMZLW/tO5NHvOF5T2s1rqW1UVDI/emlDqmV37RyRk+bu0d5OC7vtYr3VFfZ7DA7D5nmrm8GMOG583kfBeVNqd4F11XXPi/4aNwghH6GANH0z71YlaTMcuJ71TxhTvUh8VUT0hb2ha78wwD3FVC50cmc+sDx8QrUZB4K9IE8AePbA4+KC5jhEzHyx8QW8R3FQpKeOWKQuxvNaSPEq2o53wOJZycMFveru31IjkLQMgneCC2EJcxzw1rWt4HJUgj967ps20rprUMEj62l3pQQ4MDt0bpHhzOQV0636L03bi00topQ4fmc3ePzQeTKe11kre0ipKhzW8d4MOFSqYXQyOD/VP7PcvYlfBE2NzIY2NAHANAAXnrbLao6S6w1cEYY2oB3t0YG8EHOAB04qUnCiRwUCM8Ag2vZxqlmk79LcZWPkBpJYWsbw3nObgZ8MrF1lS65XGEg+02KIeGGtasMWuc7DQsjb3xUdwpXzEmOKVr3448iCUHoWz6OkqRDR0sfqtaGgeSy9fs0rKOEz8G7vE4K2DZPqG3X2q9Ioahkm8DlnJzfAjmt61pOYbLIRy657kHkDapZPu+opatrd3
 tSY3kdSOR+C7VseuNLPojThnqhHVQ1PHDwHOaCQAfDiuPbXdUUd07K10jd+SnlMj5QcjljdHetPaainZQXC2SuMbSCYi88JGEEt59eBCnkseudekU8VRK8b0bQ5xB4jgvI2pr668Vkkno1NCzJ3RHGAceJXSrxtljv+k6yir6J9NcnxFjXRneY4nh5j5rjBPFTxi2q9HTvqqqKCBm9LI4NaBwyVuMuzy8tY0xtppnYzuh3H5gKTZPbhXaqjcW7zYWGTyPIL0BDRDI4LTLzlUaQusJPpNLNTgdTCSPiFPS6cooiHVtVNjqGxEL1zaoWsiG8AeHHIUbw23QUM09TS05bGwuJdGOgSrjx3Pa4mV4bbpZRFu7xMw3N0d58FtGmfRYbNcZ4mZggcGmUjjK/nw8FjNcVz5ax1PExrZal4lcxo9lp9hnDwOfMrc2WSKzaMp46oB263tJGnq9x/5CyY5BUMLXSVEww57iWtP1WKd67zjOM9Vlr/AFLqqtk3Rxzg45DuaPALGvb2bSOq2iVxGA0cgjcDmpG9VMoKgWwaOuTaC8U0k/GncTFM39pjhuuHwK11qrUzt2QIPYH2eLy5lDc9KVkm9VWibegJPtwOPAjy/iF2RePtJalNlveltWRuPZOJtdxA8MbpPm0g/wBxevoZGyxMkjcHMcA5pHUFZaVEUAooCIiAiIgIUWp7Ur87T2jK6qhP9LlAp6cd8j+A+uUHJtS3z0iv1VqYu/DhaaWkPTcZljT75HPP9xeZrjMZalzicnmSu2bTpBZNndttTHfi1cgkeepYzgPi4vd/eXCpCXPJKsSqZPEqV3NRdzUpVRKVWgkMeHDoeKpYypm+w4eRQX09PgMljP4Tzz/Zcjm7rRM0YcHbsjf2Xd/kVcWCeJ0xpKvHYTerk/lPer650UlBUSCdhJYAyZv7cZ9l4/55jxQbdsuv7rXcY3Od+HvAPH6HHifccH4r0hBI2RrXDByM5XkChqvuypYx+H
 wuGQ4D22H/AEXonZjfTcrWaOofvVVJhhdni9mPVd7wg3GraPVIXJ9slu9J01JOxuX0zw73Z4rq9W7Eec8lx7aVrmgpoqy200Yqah43Hj8rCg4aeLsdVXh3Izl4yR9VS5HPUqAaXEAcygrxOwXTuA4HDR3u/wBFNTW+qrw50DPUHNx4BUZHBzmtzuxsGM/Uq9kvkrKcQUEYhhYMZ5uPigsKC4Vtsqu2oamammaeD4nlp+SzV11xqa50no1dfLhNDjBY+YkFbtcdl1dS6Gkv2aZ0forajG5g7paDwIPPj1XO9O2mrut4p6OkpxUTvJ3YiD62ASRwx0BVLFhRQzVU7WQjekOTxPPqsnbZjFJJR1JLIpiA4n+zePZePI/IlXN/o7jp2sgZU2wW2oaThwDh2g/vEj4LFVdc6rl35WNbJ1LRgFCVe19NiZ7pmbsoyJWt6/qascBxxnI6FZJz3VlE2UOPpFMMO4+0zofMcj4YVhKeb2D1fzAflKmYtdh2C24NFfWuA9YiJvu4ldmhaOHeF5x2e69k089lLVs7Wgc7juj1mZ6jvXoiz1cFfRw1dJK2WCVoc17eIOURsMT92MHwWi7Wr4KKwGma/wBepzvAdI28XH6D3rcXP/CAzjguDbY7qX+muDstkeKKL91vrSEeZwFKsaXovduurvTK4gxxZndvch3LKa71Q6saIqckRE4ib+1+s/w957lrlvZHR0RqpCRCfVLM47d3d+6OGe/ks/YqCKitUmrNQNDt5xbQ07hjtXj82P2QorVLhbxaaOM1Q/ps7d7cPNjTyz4lYFxJ3ieayF3rprjWy1NQ8ukkdklY54w1aZShTBSgYUwQRBwVODxBVPCnCDetC/70oLxYncTVwdvAO6aLJb8QXD3r1H9n3VB1HoCniqH5rra70WYHmQB6p94+hXjzS1xfa7zSVkZw6GQO93cu2bLbzHpDbHPQh+7ab21r4u7DxvRn3ElqlWPUaioKIUUREQEREAr
 im1qufe9oNk0/AS6K3sNbOB1kdhsYPln5rs9RK2GF8shwxjS5x7gF500tcfTLtqbV9T7D5JJ2E9I48sjA83E//wA0HN9uV2ZV6nfSwuBhomtpmY/SOPzJXL5fWc4jkOCyF+rH11xlnkJLnuLyT3k5/irKR47ERM5A5J7ytMrclSqdykQFMzm791U3KaH+sA78j5IJ2HDsjmumWKP/AGs0w+ONoderawlrTznh6t8f+i55aqY1dbHCObis5ZbnVaW1LDV0xxJA/i3o5vUFBZvh3mOpM5LMyQE8yOrf9O9b3sovZornRukOBn0SXJ6Hiw/HIVLajZKdwotVWDjZ7oO0w3+wm/Ow9xyCtXtNS2GtdUZDGzsO9j8sjfWHxx80HfdqmqY7DpyRkDx6ZUgsiGeIzzcvM0sjpJHPe4uc45JPMrZNoN4N31DLI2V0kEYDI89BjitY3cgnoEEh4hTNJaM9cJEwOcSfZAyVWp3wsqGvqMmMcS0c3eCDI2jTtXdHx4buRv8AZyMk+OO5UrzQQ0NyqKZsv9Sd3Dhk5wO5dw2MWWLUen665cYnid1Nk8d1ga04b554nwXJ9b0jmasvUbcOaypkGQegJCD0bqproth87GnlaYW/5WLg2xtrhtLseXuI7V54nP5HL0Frdm5sdrGtwSLbEPP1Wrz7slfIzaTYQ5jQHVBGWjva4JpI3v7ULBIbBI5wGBKMkc/ZXG7TZRcoJuxe4uiLeIGRggnOOfRdz+03TOkt1iexrnESyNOBnoFz7ZZZqy5S3WKga11VCIpS3eGcAuB+o4INIlp6mz1rGy4cMZaR7L2nmFRrIxTzNfHxglbvM8W93mDkLYtcz+i3mrttXA6J8Tg5pPONxGSMdywlDishdRPI3nHehd3P7vf9cKrFjNF2bmuYd6Jwy0/wXQtkmt/9nbgKK4PP3ZUO9YnlE79ry71odOwCR1NPlgccZP5XKMNDUvrfRWRuM+cYH18lEew7zXNorTUV
 oIc2KIyAg5DuHBeeNd0clReqOgll/CpKft6uQcdxzzvO9+SAPHC2CwarlGyqrpap2/WUEzYWscfWc3OWt+PBajWxXC+XSC00jTPcq+YPnDeO8/oP3WjPzKlqyLzR1hj1Vdaisrz6Jpm0x9pPJ0awcmDvc7+KwevNSO1FdzJEwQUEDRFS07fZjjHIY7+pW1bTbpTWS0UuiLFK11LRuEtfMw/8RUdcnqG8lzA9VCqTuZVKbg0eJVU81TqOUfkT81pFFqmCgAohUTDgFOwjfaXDhniqanCguXDsqjDeLTxBW9V00ldom0XincRXWao9Ee4c+zJ34nHyO8Fz8P3mtB5tW+7NHNr5bhYpSNy6UzomZ6St9Zh+Ix70I9j7PtQR6n0ba7tGQTPCO0A/K8cHD4grYl51+ypqF0TrtpircQ9jzUQsd0PJ4+hXopZaRRAiAiIeSDRNtd7Nk2f15hP9KrMUcAHMvfw+mSuLaz3dM7I2UbDuy10racY6sjHH4u3z71t22G4m8bQrTZosvp7TE6unaORkOAwfEtH95c2+0VWiC42myRuyLfTDtPF7uf0+asiVxeV29K5x71Brd7eUFFp3TlVFMjCgVckteOSpvi6tPxQW7uYVxQ00lRKOzx6veVScx3cqkTXloczeyDxI6IMtYWSU11hlLctY71h1Cv8AVjWyXEzQtO4WjJx1WEhrpmFvaBsm7y3ufx5rY6K8ARPZBVFgkbh0dXHvNHk8Z+YQbRsiu1JWNq9HX14Fsu3CF7uUNRj1XDuzy+C06+WCus2oamx1bN2pZN2Y44B48HDwKtJIJI5hJHugggh0Tw4A94XXLhTf/E/QjbnA3/5qsrAypwMGpjHJ/nw+PmpxelJszt9TZ47mWztnJcXQPdkc8Y8e9afqjSplDRYomuha8tODjfI5uz3ZBAWYt+u66DQ9Rag13bvf2bZXHHYB3tH38x3HKtNQ6npaOyPorad6fdELZBwA7yPd9VUc7qoxC
 /0ZhDnMOHlvHLvDv7lCpt9RSthkqYXxiZu9Hnq3OM/JbZsv0jNqnUDGvzHQU5D6mbw/ZHif9VsG3mKlg1ZRwUrWQwxUTGsYOAwHO6qjqP2a4Ws2dzloPr10p49fVYFwXXjGv1tfXD2jWy4PX2yu7bBaptLs9ijzkuqZXZHLmB/BcE1fLK7Vd4d2nA1cvA/vlTcWPUOvRu7Jq9uDwt7OXgGrz3stAdtH06Bvg+lDO8OmDywu+atqRPszr2YPG35/ygrguzSYM2hWJ5LSRPybx/KVL0jqH2nIA7TdnfxwKpw597f9FrX2aWOF/vW8XEGkZje/fWzfaLnfPo+gMZLSysHED9Dlqn2cppBfb3JJ6zjTMBIH60/TGtfaDpmQ7TKx3EdpBE/AH6cc/cufx0tRFBHUdm5sD8lknTgcH4FdS+0DCZdeiU8A+kjPI8cZCzegNH0+qdljA1m5WR1E0bXO6jOeX95XUrl1PbpNQPiqKQAzhwbVDIGO6T39fHzW9Q22jpqiR73sjlDA2MuON5ueLfceXgVojRXaO1Q+GrheyWB25LEeG+w/zHIrMapnF3uVJFbnOe12HMOMk73glrTJ6iqrbb7fJTRAemySiofutyCcYxnp3qpbriNEWKStA39UXSPERxxpYXdf3iOSxPowoNStl1HSVEMEbTKyOaMs7Z4Hqg55An5LA3G51lzvRqcCWpdJvN3QTnHIAdwwsjG1LZvSH+k73ak5dvc8lSNhe7j6rR3udhZF9Lc7ncxCYJ31k7wwM3d0ucenFLrY6y2PlZW9hFNGcOjdKC74BCrFkEXHMgc7r0A96tq2OENHZuc57eBPT3LMUFLRSUTnysrZqzew2ONoDMd5d/orO5wOpoi2SJkbjybvZPvWmWJCKCigKdSKcDJGEDqOOFlrBXyW+5U1XA4tlhkbI3HeDlYk81VhduyAoOw1leNG7X7XqOk9W13Ps60FvLs5BiQe528vYEUjZY2vYQ5jgHNI6g
 rxvWwt1FsagmDgbhp+cgt/N2Dz9AcL0LsF1IdR7Obe6V+9VUQ9EmyeJ3eDT7xhStOjBRUFEKAqVXOymppZ5SBHGwvcT0AGSqq53t4vb7Ps8rYqckVdwc2jiA5kvPrf5c/FBzbZw52o9W1t8quIrax9U7PSng9ke+RzP8C4jtQuxvOsrnVudnfmdjyHAfRb7HrdukrVW2i3sBrewZRdr0jAy6QjvO84/BccuE/pFXJIM4J4Z5rUSrdCigURDJHIqq128FQTJHJBce9TxSujfmMlrvBWhecYyrmFu8wHnlBfPrB2uJ6eGZh7xun4hXdOy2VDC2OWakeeko32D3jj8liZxjd8lCM4IQZz7jq2kSRNbWU4OXOp3Bxx18R8FsuzuGCe9XB0lTU0sUbC5lOJCHPbvcieGd0cfMBaVBPJG4Oie5rh1BwVm7dqCqilikkLJnxHLDI0Fzffz93JTix6opaPQd703Nb4qWkYRD2jmygNkJxneL+ZPeV5d2kWKl0/q6roaGcz0bSHxPzn1SM/Lktz0zeRV08slvghmrWscDTukwR0wM8xhZbQ9ktetbhO+42KoqamLDZnOqzGGnljATC1ZaQ2oaZ01ZYaClste7d4vfvsBe7qSru47XNKV0glq9JSVMrRuh0zoycd2cLY77pXZNZap9Ld2+iVTOcIq5HO+RVpDYdk72B8dFWSMPJz5ZWNPkXEAqpKwtLtrs9FEIaLSz4YQSQxlQ1oGefANVu/a5piWV0k+iYnyOOXOMjCSf8AAs3XW/ZNScH2ipkd3Rzvd8w5YmpGzRjQ+DS1a+Pll1aW8fLeQXz9vFrdAYXaXkdERuljqhuCO7G7yVhFtk0/DUMnh0XEyZhy17ZWAtPgd1UoW6Dly5mjZ9wdXXJzc+8nCv30OgIYu0fpUO/Sy77x+AchqFVt2t9TH2c+lu1ZnO7JO1wz72qlS7b7ZSOc6j0m2Av9oxztbveeGKD37O2Ny3R83/7K1w/9Snh
 rNnspLIdHNBH53yveB54cgVG3WiqCDPpRkhAwC+oa4497Eh260cMXZwaakp2Z3sQ1TWjPluK4E2hm07pTpe1cOhqC048i7KxwvWzog9tpmKM/pBcP/Mg1HaJra26xjEzrVVwXFhAjndUMcA3q0gMGR15rYNg9bbbXd2XC7wsdTsa4GQtLjGRxGPetevE9FVzOFoslrZCSdwtY8ux45ctaF1rbXNNHA+NjXcHxsGWlB2bbJr+2ashNPRUTjHG134kow7GOncFyDTFfXWC4PraZlOXujdF/SBkYcMEjiCD4hUBXyzsc4u3d7njqreQE8fqsrrIXe81FyuDq2vrppqnII7IbobjgMHorA134hdDTxh/V8n4jvieHyVu9uG5Kg1mG5VkNVzWTvcDNM8gHOM4HwVhX1bqiRzuaqSn8N58MKwIwFUECgOKmCCIGVUYMcVTbzVbogpnmiHmUQZygutVFBIIZntMjDFIGnAe3lg96699l3UwteqqiyVL92C4szHk8O0by+IyuEwymN2Ry6jvWXtddNQV1LX0MhZU08jZYnjo4HIS/Vj6G54YURyWA0PqSm1Xpqiu1IW4nYO0YDxjePaafIrPZWVTLz39o7UjKS7UMMZD56KNz4GHiBM/h2hH6W8R4kdy9AzSNhifJIQ1jGlziegC8Ia+v8mpNW3O5yPJjlmd2WejBy+QQaxVyFgLiSZHdScnxJWPVSeQyyFx9yprTKBUColSk5QQUEKIJCVf231mEc8FWCvbW7FQWnkQqLurj/DBCtAcFZKoGYnLFOPFQVmuwVN2pY8FUA5HOyEF3T1csFQJqeR8cjTlrmnBC2rS2uLnYtQRXSOQlxIE7G8BM3rvDv8VpjOQVVnJB3jXWl6XX9AdXaQk368NBqaX8xIHMfq8Oq0C23GquEkdHXX58XZer2dVvYZ4LC6P1hcdJ3ptZb5HbhwJYSfVkHiP4rrd8sFl2qWx170w6Okv7BmenyG9ofEdD49UJ
 WqTNt9JIfSLzT1H/AOPTb+feQot1HBSMMdshtJZnIfU0bXyZ8yVqVE+a1189HfjXQSwHd7MMyQR3g8lsts1XHTAtoWUuD1mpWvfnzVaXf33eKqESVNVTCEcsNETfdwH1WbZbLXVUzXV2oLLl3ONrRJID3YI5rGVF1vddC2SaqAj/ACgtMLfdgYV4+C1Naw12paGTe5xCMucD1HrDCzZgt5JrLZJ+1tYpKmbG6TWUu/GfEMzgKzqtTXuohe2Gpo6eDr6NTCFuPcFWbc6C1VpntEdIZcYBfSteDx/Zzj5KnXXjVF3heacTmEDOKel7Jg94SC3t7aWpp3z3y/Q0bgThpgfK547wcYUkNisl9L/us1csUZBmrp2tjY0dzWjmT4qx0vYbnqmumfWPfDboD+NUPOR+6CebvDom0DVNLR0zbDp0CKlhBa9zOp68ep8VdSsPqe8QU85obNhkMTezDwMnGePHqSevuC1R7iSM+alxjcDuLj6xUXcQiJ45Dlu6cDPJZYtwwLDQ8x5rNDi3wUooytyQred5HBqrzSBuccSrBzy5/gkE8n/C5P5nfRWj+DVdznDImdwJ+P8A0VnPw4LQkaoqRToIqo0qmFMFBE80ToiAq9LIWv3SfVd9VQUQg779mfV5tWpXWCrkIpbifwg48GzAcPiOHuC9Ur542utlo6ukrad5bPBI2Vjh0c1wIPyX0EtNV6faqOrA3fSIWS4/eaD/ABUsxpq+2W5yWnZnfqiEkSug7FpHTfIb9CV4crH7sJaPzHHuXufa/apbzs4vtJTt3puwMrG95YQ7HyXhavB3W9OKQWCIVAqshUinUiCCg5RUpOUEFXonbtVGemcKgoxnErCP2gg2A+sCD3LGxRdqx+PaaVkGnIVnQHEkvcgsyC0kO4FDxW9WnQdwvumrheaJoLaY7oZ1fgZOPJaKWljyHDBBwQUE7TyVdnLwVBvgq8jTG0DvQQlAIz1V/pu+1unrnHW22Z0crDxxy
 cO4jqFjuhUuMoO/RXOw7VbextXI22alhbusnb+bwP7Q+YWmVd81VoKqktNxayMZy2UQscJB+01xHFc5pKmaknbNTyOjkachzTghdBbtBN6tEds1LSR1sTBwk5SNPe13Q/IoNgtH3/qqhNZHcpTA0kF0srIxkdMB2fkrsU2mqONrqy+3Wqf+aJlE9oB7t53Bc2kigtgbURxGutxdwmY4skZ+l46H5dxW0T610pFFH6BpmWomxgiprnuBP7oASzWlzValt1trxLpyJ9NUgboldGxznDxGFeaXt+p9e1b6m63SsisMR/EkLtwOP7DAOBP0WGr7lS1tPT3HUFvpaChaC6noqVpbLUnxJ9ln6jx6BUNTbVqusssdps9Oy30rW7p7Lhw/Zb3D5pjOsltQ1pSUFK3TumA2KmgG490Z4DvGepPUrjpJc7Ljk5UXuL3lziSSmOIQXDjvSZHIDAUruuUbxHkpX8kE7OAHxWT9IHo7ePrY4rG8gpomOldgchzKlixUfIXuwOJVMAteAQrpgbHwaPMlSzAb7TjqkRTqv60DuaB8lZzH11eVh/HPkPoFYyn11oSqYHKlUWqCZRCgooJ+idFADCigrU8QcR2g9V3DKlljMchaVM2YiMNA5KaJrpHAvOQEGTsdunuNwoLfSsMlRUyNiY0D8ziAF9A7ZSChttJSMOW08LIh/dAH8F5/+zfs5lhlZqq8w7nAihieOPHnJj6L0UpbrSL2hzSHDIPMd68lbddls+n6+ou9phMlmqHF7gxuTTuPMH9PcV62VOogiqYXwzxskieN1zHDIcO4hQfN+SIsOCqZGF6o2m7AqetMtfpBzYJjlzqKQ+oT+g/l8jwXnO+6cuNhrnUl5pJ6SYdJGYz5HqtRmxgSpSr6SmY1pLXb3DPBWZQUypFUdzVM8FQU0IzK3zUqqUx3Zd49AgyzD6pJVK3gYe49SpDUANdjuV5puiluNbS0VOCZZ5AwDzKD1bsrpoaLZzZo90
 ZqInTP4cy5x/hhcx2v7Mot+S62Bga92Xy045HvIWwa215T6BdarLS0zaoU9KGPG9gtwOHxOVzbVO1263imfT0tPFSROaWkglzsHx6LODmwBaSCMEcCCp5Hl5GeipEkuLicknPFTb2eaonA9VAMKAPBT8+SCmoA4OQcEKoQRzCpSEAoMlbbpLSPJaQQ4Yexwy1w7iOoWWjuVopWdvTWoelji0yTmSJh7wzH1JC1RjsE8VOXtI4qivcq+ouNS+aplfI9x4ucVYvbgqoXBQeRgIKYCjhTDB6qLQoINypjxx4KKg0cUFWOPfxngPmr5jWsYAOAVmwjHPBU2/hBXkPcqUxy3yUpflSF2QUCpdvODu9oVpL0VYuzGP0lUX8lRKgGUUWoJlEKAGVWjZg8UDsy1uT1UAM8lfUsEtTK2GnifNI44DGNJJ9wXV9C7CNQ34sqLqBaKJxzmVuZCPBn81KSOTUFDPW1EcFNDJNNIcMjjblzj4AL0nsj2FimdBdtZMaXNw+K38wO4yH/ANPxXWNCbOdPaLp2i1UvaVePXq58OlcfPoPALcQFNXErGNYxrWNAa0YAAwAFMFFFFEREBYy+2C13+jdS3mhgrIHfllbnHkeYPksmiDz3rX7OtJO6SfSdc6mcePo1SS5vudz+K4Xq3Z1qTS73fetrmZCP7aMb8Z/vD+K98KWSNkrCyRrXsPAtcMg+5XR82ZIiD5qm6PxC9x6t2MaM1G6SV9t9Aqn5Jmoj2WT3lvsn4LkOpPszXCIufp69xVDekVUzcd/iGQrKzjzxuHPRRYwk8wt4v2yPXFk3jU2GqnjH9pSDtgfc3j8lpc1JUU0/Y1MUsEgPFkrSxw9xQVvR2Cle9zvxC4Bo6Y45/gt42Smmor6bnWyNZFRMLxnq7HBaJNG4yxxZyQMlVJy6Cmc1r3DeOMAqjLa0vZ1DqWtuLj6kr/UB6NHALAOe3PAhW2T3qHvQXHaDwTtB3q2ypMlEX7HtPMqp24b
 yWNDiFHOUNXxqe4qk+YOOcDKtwg9pBXBJ5EKYMeeWFJGSrmJwHcglbC7GSQFUMLcZJJVVrh4KErsBBYvcGuIbyUzZBw4q3fxJUvvQXe/kc0DuGVbt5c1f253B46oqi0uPQ8FMXEniCCsnG3kc8FWbgjkD7lBiA1zhwHJSO3geKy80G+09PELDTdpFKWu5jkgg0OyQQcFRMT+4qXtnLsOyfZjNtAtD66nr6anbTy9jK1wLng4BBx3EH5K6OP8AYvzjdPwV9HSujhGYnFzuXBetLL9nzT8G4+7VtXWubxLIyImn4cV0ixaK03Yg37rs9HC9vKQxh7/8RyVnVx4x0xsu1XqEsdQWidkLv7WcdmwDvyV2LSX2b4mlkuqLo53U09IMZ8C4/wAl6OAwMDgiaY1vS2h9OaWiDLJaqendjjKRvSO83HJWyYUcIophERAREQEREBERAREQEREBabtZuFvs2hrpcrjS01QYoiImzxteDIeDefiVuS8zfa41RvG3abpn8j6ROAeZ5NH1+KDzzSl00s1Q/mc/NW1fJvPa0chxKyMUYjptzr1WFndvSuPitsqagVNlQREFDCiiCXCEYUyIJQohFEIJmFVo3KhlMkILztMKnLNkEA8Vb9UQQRCiAOSu6B+JwO9WmVWo3YqWE8soTrNjPLoqgOMBUwcFTA5UVWVvW0YqWDBw8ciqzSqgKDW3xuieWPBDguufZu1kNM62ZRVkm7b7niB+Twa/8h+PD3rn9zpRUU/aMH4rPmFhY3ujka5ji1zTkEHiCg+lg5KIXP8AYlrAay0JR1UzwbhTNFPVDqXtHte8YPxXQAstGEREBERAREQEREBERAREQEREBERAXmT7QOz++3PW1NXWuilrqWtIGWD+qf4noF6bUEHzxvNFUWysqaSsjdFUwPLHscMEELW3cSvU/wBqfQwdRM1ZbYvxGYjrmtHNv5ZPdyPuXlkrUrNiRFMeKYVRJzTCiiBhQwoq4pKWeqkE
 dNE+V56NGUFvjKiQt7tezW7VtGZZHxU8p4tifkk+ZHJYG+6VvFjefvCilZH0laN5h94QYFFPgqOCgkAUFOAp44ZJXhsTHPceADRklBSAUOK3Ww7Or7dQHug9FhP55uGfILc4tlVDHRdnPNM+frK3h8Ag4vxVWlYXzsA78rbNV6RgskvZx3FkshP9W5uCB5rE01MyAE53nHqhiq0bwx+Ycj3qUkjgPgouO6QR0UJD62eiiqkblWByrVrgSq7HdCguo+BHjwW5Q7D9UVOmWXyGmDnzyDs6AD8Xszyeeg8ueOK237P2gG6guRvl1i3rZRvxExw4TSj+A+q9StGAAOXcpq40HY3od2idMNgqtw3Cow+oLOQOODfHHeugBQUQooiIgIiICIiAiIgIiICIiAiIgIiICIiC0u1vp7rbamhrYxJTVEZjkaeoIwvn9tH0rUaN1hX2apB3Yn70LyP6yI8Wu/56gr6GFcZ+0rs/OqdMNu9ui3rvbGlw3R60sPNzfHHMe/vViV4zRRx38FDC0yIiICvrPdKu0Vbaihl3JBzyMgjuIViohB3HRe022VBjgvcYo5uXat4xn+IXTavU+l6WigdWXClljqTuMY0iTez4d3mvIIOFNvHhx5Ir2JVbLNH3vEzrTB6/HtKWQx8/3ThY5/2fdJyu/DddIvBswP1avL9r1BdrYc2+51tKf+5mc36FbHBtS1tA3cj1Lcd0ftSb31QehqXYNo+jcHSQVtQR/wBtOcH3DCyzrDpfTZhgipaGifJwjy0Bzj4ErzHJtS1pJnf1JcDnn64/ksDedTXm9Fhutzq6vcOW9rITunwQevK2KONhfugMHUkALmOtNfWuzB8VO9lVVfsRngD4lcartSXie2wwSXSqfSgboj7Q4HgtdcS45PNBlNQ3ysvle+pq38SfVa3k0LHMqJGHg4kdxVNQwiavI6vePr8Mq6a7eaOKxOFe0bssIzyRV21bHobTlVqzUlHaaIEOmdl78
 cI2D2nHyCwEMT5ZWRxNc+R5DWtaMkk8gF7F2E7PW6NsBrK+MffVc0GYnnEzmIx9T4+SmrG/aes9JYbLSWy3RiOmpmBjR395PieayYUMKKyoiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgKDhkcVFEHlrb9sYmp6qq1JpGmL6aQmSroom5MZ6vYOo6kLzo4YPXyX0wIyuHbX9hdDqZ0900wIqC7uy58Xswznxx7J8VqVMeP0WV1Dp+7aer30d8oKihqGnG7KzAPiDyI8QsUqyAZRRxwygCCCKYAdVVLW7oPeiqIGVEc1ULQDwBUjvBESuUBwKi5QwUFUvzCG/qzhSckwUPFFSoimY3PQkoiVX1op5qupZT00L5ppXBrI2NLnOJ5AAc1m9EaGvusbi2lstE+VuR2kzvVjiHe53IeXPwXrzZPsmtOg4G1DgysvDm+tVOb7HeGDp581LVka3sP2QN06Yr5qWNkl2xvQQHBFNnqe9/wBF29FFZaEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREGPvNmtt7o3Ut2oqesgP5JmBwHlnkuSam+zrpK5ufJa5Ky1SniBE7fZn91y7WiDypcvs232kLjbbharlGOTKgSU7z7xvBYSXY3e7e4mv0fcaho60FwikB8gcFexkV1MeLajRFvpg4VulNZ0h6kUwkA94KxT9M6YjJEr9V0//AIlszj5r3OoOaHDiAfNNMeEptO6Ve3Db/c4T/wB7aJP4FSM0rpVzcnVtQ09xs0/817rdTQO9qGM+bAoeiU3/ANvD/gCaY8Iu0rpgZ3NVVDj4WedUxpaw/lvdzl8I7NL/ABK95CkpxygiH9wKdsMbfZjYPIBX2MeEo9J2hxAbHquqPdFatzPxcVlqPQMtSALfofVdUejqjdhafkvbaBTTHk6y7FtUVu7u6bs1ojP9pXVZneP7rcj6Loml/s/2aje2bUNbJcpOZhijEEOfIcSPMrtqgm0xZW
 u20Vpo46S20sNLSs4NihYGtHuV6EAUVFEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERB//9k=
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/32247295/lib/server-core/src/test/resources/batchWithPost.batch
----------------------------------------------------------------------
diff --git a/lib/server-core/src/test/resources/batchWithPost.batch b/lib/server-core/src/test/resources/batchWithPost.batch
new file mode 100644
index 0000000..b7038e9
--- /dev/null
+++ b/lib/server-core/src/test/resources/batchWithPost.batch
@@ -0,0 +1,39 @@
+--batch_8194-cf13-1f56
+Content-Type: application/http
+Content-Transfer-Encoding: binary
+
+GET http://localhost/odata/Employees('2')/EmployeeName?$format=json HTTP/1.1
+Accept: application/atomsvc+xml;q=0.8, application/json;odata=verbose;q=0.5, */*;q=0.1
+Accept-Language:en-US,en;q=0.7,en-UK;q=0.9
+MaxDataServiceVersion: 2.0
+
+
+--batch_8194-cf13-1f56
+Content-Type: multipart/mixed; boundary=changeset_f980-1cb6-94dd
+
+--changeset_f980-1cb6-94dd
+Content-Type: application/http
+Content-Transfer-Encoding: binary
+Content-ID: changeRequest1
+
+PUT Employees('2')/EmployeeName HTTP/1.1
+Content-Length: 100000
+Accept: application/atomsvc+xml;q=0.8, application/json;odata=verbose;q=0.5, */*;q=0.1
+DataServiceVersion: 1.0
+Content-Type: application/json;odata=verbose
+MaxDataServiceVersion: 2.0
+
+{"EmployeeName":"Frederic Fall MODIFIED"}
+
+--changeset_f980-1cb6-94dd--
+
+--batch_8194-cf13-1f56
+Content-Type: application/http
+Content-Transfer-Encoding: binary
+
+GET Employees('2')/EmployeeName?$format=json HTTP/1.1
+Accept: application/atomsvc+xml;q=0.8, application/json;odata=verbose;q=0.5, */*;q=0.1
+MaxDataServiceVersion: 2.0
+
+
+--batch_8194-cf13-1f56--


[19/22] olingo-odata4 git commit: [OLINGO-472] Batch Refactoring

Posted by ch...@apache.org.
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/BatchRequestParserTest.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/BatchRequestParserTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/BatchRequestParserTest.java
deleted file mode 100644
index cc249be..0000000
--- a/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/BatchRequestParserTest.java
+++ /dev/null
@@ -1,1326 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.olingo.server.core.batch;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
-import java.net.URISyntaxException;
-import java.util.List;
-
-import org.apache.olingo.commons.api.http.HttpHeader;
-import org.apache.olingo.commons.api.http.HttpMethod;
-import org.apache.olingo.server.api.ODataRequest;
-import org.apache.olingo.server.api.batch.BatchException;
-import org.apache.olingo.server.api.batch.BatchRequestPart;
-import org.apache.olingo.server.api.batch.BatchException.MessageKeys;
-import org.apache.olingo.server.core.batch.parser.BatchParser;
-import org.apache.olingo.server.core.batch.parser.BatchParserCommon;
-import org.junit.Test;
-
-public class BatchRequestParserTest {
-
-  private static final String SERVICE_ROOT = "http://localhost/odata";
-  private static final String CONTENT_TYPE = "multipart/mixed;boundary=batch_8194-cf13-1f56";
-  private static final String CRLF = "\r\n";
-  private static final String MIME_HEADERS = "Content-Type: application/http" + CRLF
-      + "Content-Transfer-Encoding: binary" + CRLF;
-  private static final String GET_REQUEST = ""
-      + MIME_HEADERS
-      + CRLF
-      + "GET Employees('1')/EmployeeName HTTP/1.1" + CRLF
-      + CRLF
-      + CRLF;
-
-  @Test
-  public void test() throws IOException, BatchException, URISyntaxException {
-    final InputStream in = readFile("/batchWithPost.batch");
-    final List<BatchRequestPart> batchRequestParts = parse(in);
-
-    assertNotNull(batchRequestParts);
-    assertFalse(batchRequestParts.isEmpty());
-
-    for (BatchRequestPart object : batchRequestParts) {
-      if (!object.isChangeSet()) {
-        assertEquals(1, object.getRequests().size());
-        ODataRequest retrieveRequest = object.getRequests().get(0);
-        assertEquals(HttpMethod.GET, retrieveRequest.getMethod());
-
-        if (retrieveRequest.getHeaders(HttpHeader.ACCEPT_LANGUAGE) != null) {
-          assertEquals(3, retrieveRequest.getHeaders(HttpHeader.ACCEPT_LANGUAGE).size());
-        }
-
-        assertEquals(SERVICE_ROOT, retrieveRequest.getRawBaseUri());
-        assertEquals("/Employees('2')/EmployeeName", retrieveRequest.getRawODataPath());
-        assertEquals("http://localhost/odata/Employees('2')/EmployeeName?$format=json", retrieveRequest
-            .getRawRequestUri());
-        assertEquals("$format=json", retrieveRequest.getRawQueryPath());
-      } else {
-        List<ODataRequest> requests = object.getRequests();
-        for (ODataRequest request : requests) {
-
-          assertEquals(HttpMethod.PUT, request.getMethod());
-          assertEquals("100000", request.getHeader(HttpHeader.CONTENT_LENGTH));
-          assertEquals("application/json;odata=verbose", request.getHeader(HttpHeader.CONTENT_TYPE));
-
-          List<String> acceptHeader = request.getHeaders(HttpHeader.ACCEPT);
-          assertEquals(3, request.getHeaders(HttpHeader.ACCEPT).size());
-          assertEquals("application/atomsvc+xml;q=0.8", acceptHeader.get(0));
-          assertEquals("*/*;q=0.1", acceptHeader.get(2));
-
-          assertEquals("http://localhost/odata/Employees('2')/EmployeeName", request.getRawRequestUri());
-          assertEquals("http://localhost/odata", request.getRawBaseUri());
-          assertEquals("/Employees('2')/EmployeeName", request.getRawODataPath());
-          assertEquals("", request.getRawQueryPath()); // No query parameter
-        }
-      }
-    }
-  }
-
-  @Test
-  public void testImageInContent() throws IOException, BatchException, URISyntaxException {
-    final InputStream contentInputStream = readFile("/batchWithContent.batch");
-    final String content = StringUtil.toString(contentInputStream);
-    final String batch = ""
-        + "--batch_8194-cf13-1f56" + CRLF
-        + MIME_HEADERS
-        + CRLF
-        + "GET Employees?$filter=Age%20gt%2040 HTTP/1.1" + CRLF
-        + "Accept: application/atomsvc+xml;q=0.8, application/json;odata=verbose;q=0.5, */*;q=0.1" + CRLF
-        + "MaxDataServiceVersion: 2.0" + CRLF
-        + CRLF
-        + CRLF
-        + "--batch_8194-cf13-1f56" + CRLF
-        + "Content-Type: multipart/mixed; boundary=changeset_f980-1cb6-94dd" + CRLF
-        + CRLF
-        + "--changeset_f980-1cb6-94dd" + CRLF
-        + "content-type:     Application/http" + CRLF
-        + "content-transfer-encoding: Binary" + CRLF
-        + "Content-ID: 1" + CRLF
-        + CRLF
-        + "POST Employees HTTP/1.1" + CRLF
-        + "Content-length: 100000" + CRLF
-        + "Content-type: application/octet-stream" + CRLF
-        + CRLF
-        + content
-        + CRLF
-        + "--changeset_f980-1cb6-94dd--" + CRLF
-        + "--batch_8194-cf13-1f56--";
-    final List<BatchRequestPart> BatchRequestParts = parse(batch);
-
-    for (BatchRequestPart part : BatchRequestParts) {
-      if (!part.isChangeSet()) {
-        assertEquals(1, part.getRequests().size());
-        final ODataRequest retrieveRequest = part.getRequests().get(0);
-
-        assertEquals(HttpMethod.GET, retrieveRequest.getMethod());
-        assertEquals("http://localhost/odata/Employees?$filter=Age%20gt%2040", retrieveRequest.getRawRequestUri());
-        assertEquals("http://localhost/odata", retrieveRequest.getRawBaseUri());
-        assertEquals("/Employees", retrieveRequest.getRawODataPath());
-        assertEquals("$filter=Age%20gt%2040", retrieveRequest.getRawQueryPath());
-      } else {
-        final List<ODataRequest> requests = part.getRequests();
-        for (ODataRequest request : requests) {
-          assertEquals(HttpMethod.POST, request.getMethod());
-          assertEquals("100000", request.getHeader(HttpHeader.CONTENT_LENGTH));
-          assertEquals("1", request.getHeader(BatchParserCommon.HTTP_CONTENT_ID));
-          assertEquals("application/octet-stream", request.getHeader(HttpHeader.CONTENT_TYPE));
-
-          final InputStream body = request.getBody();
-          assertEquals(content, StringUtil.toString(body));
-        }
-      }
-    }
-  }
-
-  @Test
-  public void testPostWithoutBody() throws IOException, BatchException, URISyntaxException {
-    final String batch = CRLF
-        + "--batch_8194-cf13-1f56" + CRLF
-        + "Content-Type: multipart/mixed; boundary=changeset_f980-1cb6-94dd" + CRLF
-        + CRLF
-        + "--changeset_f980-1cb6-94dd" + CRLF
-        + MIME_HEADERS
-        + "Content-ID: changeRequest1" + CRLF
-        + CRLF
-        + "POST Employees('2') HTTP/1.1" + CRLF
-        + "Content-Length: 100" + CRLF
-        + "Content-Type: application/octet-stream" + CRLF
-        + CRLF
-        + CRLF
-        + "--changeset_f980-1cb6-94dd--" + CRLF
-        + CRLF
-        + "--batch_8194-cf13-1f56--";
-    final List<BatchRequestPart> batchRequestParts = parse(batch);
-
-    for (BatchRequestPart object : batchRequestParts) {
-      if (object.isChangeSet()) {
-        final List<ODataRequest> requests = object.getRequests();
-
-        for (ODataRequest request : requests) {
-          assertEquals(HttpMethod.POST, request.getMethod());
-          assertEquals("100", request.getHeader(HttpHeader.CONTENT_LENGTH));
-          assertEquals("application/octet-stream", request.getHeader(HttpHeader.CONTENT_TYPE));
-          assertNotNull(request.getBody());
-        }
-      }
-    }
-  }
-
-  @Test
-  public void testBoundaryParameterWithQuotas() throws BatchException, UnsupportedEncodingException {
-    final String contentType = "multipart/mixed; boundary=\"batch_1.2+34:2j)0?\"";
-    final String batch = ""
-        + "--batch_1.2+34:2j)0?" + CRLF
-        + GET_REQUEST
-        + "--batch_1.2+34:2j)0?--";
-    final BatchParser parser = new BatchParser();
-    final List<BatchRequestPart> batchRequestParts =
-        parser.parseBatchRequest(StringUtil.toInputStream(batch), contentType, SERVICE_ROOT, "", true);
-
-    assertNotNull(batchRequestParts);
-    assertFalse(batchRequestParts.isEmpty());
-  }
-
-  @Test
-  public void testBatchWithInvalidContentType() throws UnsupportedEncodingException {
-    final String invalidContentType = "multipart;boundary=batch_1740-bb84-2f7f";
-    final String batch = ""
-        + "--batch_1740-bb84-2f7f" + CRLF
-        + GET_REQUEST
-        + "--batch_1740-bb84-2f7f--";
-    final BatchParser parser = new BatchParser();
-
-    try {
-      parser.parseBatchRequest(StringUtil.toInputStream(batch), invalidContentType, SERVICE_ROOT, "", true);
-      fail();
-    } catch (BatchException e) {
-      assertMessageKey(e, BatchException.MessageKeys.INVALID_CONTENT_TYPE);
-    }
-  }
-
-  @Test
-  public void testContentTypeCharset() throws BatchException {
-    final String contentType = "multipart/mixed; charset=UTF-8;boundary=batch_14d1-b293-b99a";
-    final String batch = ""
-        + "--batch_14d1-b293-b99a" + CRLF
-        + GET_REQUEST
-        + "--batch_14d1-b293-b99a--";
-    final BatchParser parser = new BatchParser();
-    final List<BatchRequestPart> parts =
-        parser.parseBatchRequest(StringUtil.toInputStream(batch), contentType, SERVICE_ROOT, "", true);
-
-    assertEquals(1, parts.size());
-  }
-
-  @Test
-  public void testBatchWithoutBoundaryParameter() throws UnsupportedEncodingException {
-    final String invalidContentType = "multipart/mixed";
-    final String batch = ""
-        + "--batch_1740-bb84-2f7f" + CRLF
-        + GET_REQUEST
-        + "--batch_1740-bb84-2f7f--";
-    final BatchParser parser = new BatchParser();
-
-    try {
-      parser.parseBatchRequest(StringUtil.toInputStream(batch), invalidContentType, SERVICE_ROOT, "", true);
-      fail();
-    } catch (BatchException e) {
-      assertMessageKey(e, BatchException.MessageKeys.INVALID_CONTENT_TYPE);
-    }
-  }
-
-  @Test
-  public void testBoundaryParameterWithoutQuota() throws UnsupportedEncodingException {
-    final String invalidContentType = "multipart/mixed;boundary=batch_1740-bb:84-2f7f";
-    final String batch = ""
-        + "--batch_1740-bb:84-2f7f" + CRLF
-        + GET_REQUEST
-        + "--batch_1740-bb:84-2f7f--";
-    final BatchParser parser = new BatchParser();
-
-    try {
-      parser.parseBatchRequest(StringUtil.toInputStream(batch), invalidContentType, SERVICE_ROOT, "", true);
-      fail();
-    } catch (BatchException e) {
-      assertMessageKey(e, BatchException.MessageKeys.INVALID_BOUNDARY);
-    }
-  }
-
-  @Test
-  public void testWrongBoundaryString() throws BatchException, UnsupportedEncodingException {
-    final String batch = ""
-        + "--batch_8194-cf13-1f5" + CRLF
-        + GET_REQUEST
-        + "--batch_8194-cf13-1f56--";
-
-    final List<BatchRequestPart> parts = parse(batch);
-    assertEquals(0, parts.size());
-  }
-
-  @Test
-  public void testMissingHttpVersion() throws UnsupportedEncodingException {
-    final String batch = ""
-        + "--batch_8194-cf13-1f56" + CRLF
-        + "Content-Type: application/http" + CRLF
-        + "Content-Transfer-Encoding:binary" + CRLF
-        + CRLF
-        + "GET Employees?$format=json" + CRLF
-        + "Host: localhost:8080" + CRLF
-        + CRLF
-        + CRLF
-        + "--batch_8194-cf13-1f56--";
-
-    parseInvalidBatchBody(batch, BatchException.MessageKeys.INVALID_STATUS_LINE);
-  }
-
-  @Test
-  public void testMissingHttpVersion2() throws UnsupportedEncodingException {
-    final String batch = ""
-        + "--batch_8194-cf13-1f56" + CRLF
-        + "Content-Type: application/http" + CRLF
-        + "Content-Transfer-Encoding:binary" + CRLF
-        + CRLF
-        + "GET Employees?$format=json " + CRLF
-        + "Host: localhost:8080" + CRLF
-        + CRLF
-        + CRLF
-        + "--batch_8194-cf13-1f56--";
-
-    parseInvalidBatchBody(batch, BatchException.MessageKeys.INVALID_HTTP_VERSION);
-  }
-
-  @Test
-  public void testMissingHttpVersion3() throws UnsupportedEncodingException {
-    final String batch = ""
-        + "--batch_8194-cf13-1f56" + CRLF
-        + "Content-Type: application/http" + CRLF
-        + "Content-Transfer-Encoding:binary" + CRLF
-        + CRLF
-        + "GET Employees?$format=json SMTP:3.1" + CRLF
-        + "Host: localhost:8080" + CRLF
-        + CRLF
-        + CRLF
-        + "--batch_8194-cf13-1f56--";
-
-    parseInvalidBatchBody(batch, BatchException.MessageKeys.INVALID_HTTP_VERSION);
-  }
-
-  @Test
-  public void testBoundaryWithoutHyphen() throws UnsupportedEncodingException {
-    final String batch = ""
-        + "--batch_8194-cf13-1f56" + CRLF
-        + GET_REQUEST
-        + "batch_8194-cf13-1f56" + CRLF
-        + GET_REQUEST
-        + "--batch_8194-cf13-1f56--";
-
-    parseInvalidBatchBody(batch, BatchException.MessageKeys.INVALID_CONTENT);
-  }
-
-  @Test
-  public void testNoBoundaryString() throws UnsupportedEncodingException {
-    final String batch = ""
-        + "--batch_8194-cf13-1f56" + CRLF
-        + GET_REQUEST
-        // + no boundary string
-        + GET_REQUEST
-        + "--batch_8194-cf13-1f56--";
-
-    parseInvalidBatchBody(batch, BatchException.MessageKeys.INVALID_CONTENT);
-  }
-
-  @Test
-  public void testBatchBoundaryEqualsChangeSetBoundary() throws UnsupportedEncodingException {
-    final String batch = ""
-        + "--batch_8194-cf13-1f56" + CRLF
-        + "Content-Type: multipart/mixed;boundary=batch_8194-cf13-1f56" + CRLF
-        + CRLF
-        + "--batch_8194-cf13-1f56" + CRLF
-        + MIME_HEADERS
-        + CRLF
-        + "PUT Employees('2')/EmployeeName HTTP/1.1" + CRLF
-        + "Accept: application/atomsvc+xml;q=0.8, application/json;odata=verbose;q=0.5, */*;q=0.1" + CRLF
-        + "Content-Type: application/json;odata=verbose" + CRLF
-        + "MaxDataServiceVersion: 2.0" + CRLF
-        + CRLF
-        + "{\"EmployeeName\":\"Frederic Fall MODIFIED\"}" + CRLF
-        + CRLF
-        + "--batch_8194-cf13-1f56--"
-        + CRLF
-        + "--batch_8194-cf13-1f56--";
-
-    parseInvalidBatchBody(batch, BatchException.MessageKeys.MISSING_BLANK_LINE);
-  }
-
-  @Test
-  public void testNoContentType() throws UnsupportedEncodingException {
-    final String batch = ""
-        + "--batch_8194-cf13-1f56" + CRLF
-        + "Content-Transfer-Encoding: binary" + CRLF
-        + CRLF
-        + "GET Employees('1')/EmployeeName HTTP/1.1" + CRLF
-        + CRLF
-        + "--batch_8194-cf13-1f56--";
-
-    parseInvalidBatchBody(batch, BatchException.MessageKeys.MISSING_CONTENT_TYPE);
-  }
-
-  @Test
-  public void testMimeHeaderContentType() throws UnsupportedEncodingException {
-    final String batch = "--batch_8194-cf13-1f56" + CRLF
-        + "Content-Type: text/plain" + CRLF
-        + "Content-Transfer-Encoding: binary" + CRLF
-        + CRLF
-        + "GET Employees('1')/EmployeeName HTTP/1.1" + CRLF
-        + CRLF
-        + CRLF
-        + "--batch_8194-cf13-1f56--";
-
-    parseInvalidBatchBody(batch, BatchException.MessageKeys.INVALID_CONTENT_TYPE);
-  }
-
-  @Test
-  public void testMimeHeaderEncoding() throws UnsupportedEncodingException {
-    String batch = ""
-        + "--batch_8194-cf13-1f56" + CRLF
-        + "Content-Type: application/http" + CRLF
-        + "Content-Transfer-Encoding: 8bit" + CRLF
-        + CRLF
-        + "GET Employees('1')/EmployeeName HTTP/1.1" + CRLF
-        + CRLF
-        + CRLF
-        + "--batch_8194-cf13-1f56--";
-
-    parseInvalidBatchBody(batch, BatchException.MessageKeys.INVALID_CONTENT_TRANSFER_ENCODING);
-  }
-
-  @Test
-  public void testGetRequestMissingCRLF() throws UnsupportedEncodingException {
-    final String batch = ""
-        + "--batch_8194-cf13-1f56" + CRLF
-        + MIME_HEADERS
-        + "Content-ID: 1" + CRLF
-        + CRLF
-        + "GET Employees('1')/EmployeeName HTTP/1.1" + CRLF
-        // + CRLF // Belongs to the GET request
-        + CRLF // Belongs to the
-        + "--batch_8194-cf13-1f56--";
-
-    parseInvalidBatchBody(batch, BatchException.MessageKeys.MISSING_BLANK_LINE);
-  }
-
-  @Test
-  public void testInvalidMethodForBatch() throws UnsupportedEncodingException {
-    final String batch = "--batch_8194-cf13-1f56" + CRLF
-        + MIME_HEADERS
-        + CRLF
-        + "POST Employees('1')/EmployeeName HTTP/1.1" + CRLF
-        + CRLF
-        + CRLF
-        + "--batch_8194-cf13-1f56--";
-
-    parseInvalidBatchBody(batch, BatchException.MessageKeys.INVALID_QUERY_OPERATION_METHOD);
-  }
-
-  @Test
-  public void testNoBoundaryFound() throws UnsupportedEncodingException {
-    final String batch = "batch_8194-cf13-1f56" + CRLF
-        + MIME_HEADERS
-        + CRLF
-        + "POST Employees('1')/EmployeeName HTTP/1.1" + CRLF
-        + CRLF;
-
-    parseInvalidBatchBody(batch, BatchException.MessageKeys.MISSING_CLOSE_DELIMITER);
-  }
-
-  @Test
-  public void testEmptyRequest() throws BatchException, UnsupportedEncodingException {
-    final String batch = ""
-        + "--batch_8194-cf13-1f56--";
-
-    final List<BatchRequestPart> parts = parse(batch);
-    assertEquals(0, parts.size());
-  }
-
-  @Test
-  public void testBadRequest() throws UnsupportedEncodingException {
-    final String batch = "This is a bad request. There is no syntax and also no semantic";
-
-    parseInvalidBatchBody(batch, BatchException.MessageKeys.MISSING_CLOSE_DELIMITER);
-  }
-
-  @Test
-  public void testNoMethod() throws UnsupportedEncodingException {
-    final String batch = "--batch_8194-cf13-1f56" + CRLF
-        + MIME_HEADERS
-        + CRLF
-        + /* GET */"Employees('1')/EmployeeName HTTP/1.1" + CRLF
-        + CRLF
-        + CRLF
-        + "--batch_8194-cf13-1f56--";
-
-    parseInvalidBatchBody(batch, BatchException.MessageKeys.INVALID_STATUS_LINE);
-  }
-
-  @Test
-  public void testInvalidMethodForChangeset() throws UnsupportedEncodingException {
-    final String batch = ""
-        + "--batch_8194-cf13-1f56" + CRLF
-        + "Content-Type: multipart/mixed; boundary=changeset_f980-1cb6-94dd" + CRLF
-        + CRLF
-        + "--changeset_f980-1cb6-94dd" + CRLF
-        + MIME_HEADERS
-        + "Content-Id: 1" + CRLF
-        + CRLF
-        + "GET Employees('2')/EmployeeName HTTP/1.1" + CRLF
-        + "Content-Type: application/json;odata=verbose" + CRLF
-        + "MaxDataServiceVersion: 2.0" + CRLF
-        + CRLF
-        + CRLF
-        + "--changeset_f980-1cb6-94dd--"
-        + CRLF
-        + "--batch_8194-cf13-1f56--";
-
-    parseInvalidBatchBody(batch, BatchException.MessageKeys.INVALID_CHANGESET_METHOD);
-  }
-
-  @Test
-  public void testInvalidChangeSetBoundary() throws UnsupportedEncodingException, BatchException {
-    final String batch = "--batch_8194-cf13-1f56" + CRLF
-        + "Content-Type: multipart/mixed;boundary=changeset_f980-1cb6-94dd" + CRLF
-        + CRLF
-        + "--changeset_f980-1cb6-94d"/* +"d" */+ CRLF
-        + MIME_HEADERS
-        + CRLF
-        + "POST Employees('2') HTTP/1.1" + CRLF
-        + "Content-Type: application/json;odata=verbose" + CRLF
-        + "MaxDataServiceVersion: 2.0" + CRLF
-        + CRLF
-        + "--changeset_f980-1cb6-94dd--" + CRLF
-        + CRLF
-        + "--batch_8194-cf13-1f56--";
-
-    final List<BatchRequestPart> parts = parse(batch);
-    assertEquals(1, parts.size());
-
-    final BatchRequestPart part = parts.get(0);
-    assertTrue(part.isChangeSet());
-    assertEquals(0, part.getRequests().size());
-  }
-
-  @Test
-  public void testNestedChangeset() throws UnsupportedEncodingException {
-    final String batch = ""
-        + "--batch_8194-cf13-1f56" + CRLF
-        + "Content-Type: multipart/mixed;boundary=changeset_f980-1cb6-94dd" + CRLF
-        + CRLF
-        + "--changeset_f980-1cb6-94dd" + CRLF
-        + "Content-Transfer-Encoding: binary" + CRLF
-        + "Content-Type: multipart/mixed;boundary=changeset_f980-1cb6-94dd2" + CRLF
-        + CRLF
-        + "--changeset_f980-1cb6-94dd2" + CRLF
-        + MIME_HEADERS
-        + "Content-Id: 1" + CRLF
-        + CRLF
-        + "POST Employees('2') HTTP/1.1" + CRLF
-        + "Content-Type: application/json;odata=verbose" + CRLF
-        + "MaxDataServiceVersion: 2.0" + CRLF
-        + "Content-Id: 2"
-        + CRLF
-        + "--changeset_f980-1cb6-94dd--" + CRLF
-        + CRLF
-        + "--changeset_f980-1cb6-94dd--" + CRLF
-        + CRLF
-        + "--batch_8194-cf13-1f56--";
-
-    parseInvalidBatchBody(batch, BatchException.MessageKeys.INVALID_CONTENT_TYPE);
-  }
-
-  @Test
-  public void testMissingContentTransferEncoding() throws UnsupportedEncodingException {
-    final String batch = "--batch_8194-cf13-1f56" + CRLF
-        + "Content-Type: multipart/mixed;boundary=changeset_f980-1cb6-94dd" + CRLF
-        + CRLF
-        + "--changeset_f980-1cb6-94dd" + CRLF
-        + "Content-Id: 1" + CRLF
-        + "Content-Type: application/http" + CRLF
-        // + "Content-Transfer-Encoding: binary" + CRLF
-        + CRLF
-        + "POST Employees('2') HTTP/1.1" + CRLF
-        + "Content-Type: application/json;odata=verbose" + CRLF
-        + "MaxDataServiceVersion: 2.0" + CRLF
-        + CRLF
-        + CRLF
-        + "--changeset_f980-1cb6-94dd--" + CRLF
-        + "--batch_8194-cf13-1f56--";
-
-    parseInvalidBatchBody(batch, BatchException.MessageKeys.MISSING_CONTENT_TRANSFER_ENCODING);
-  }
-
-  @Test
-  public void testMissingContentType() throws UnsupportedEncodingException {
-    final String batch = "--batch_8194-cf13-1f56" + CRLF
-        + "Content-Type: multipart/mixed;boundary=changeset_f980-1cb6-94dd" + CRLF
-        + CRLF
-        + "--changeset_f980-1cb6-94dd" + CRLF
-        + "Content-Id: 1"
-        // + "Content-Type: application/http" + CRLF
-        + "Content-Transfer-Encoding: binary" + CRLF
-        + CRLF
-        + "POST Employees('2') HTTP/1.1" + CRLF
-        + "Content-Type: application/json;odata=verbose" + CRLF
-        + "MaxDataServiceVersion: 2.0" + CRLF
-        + CRLF
-        + CRLF
-        + "--changeset_f980-1cb6-94dd--" + CRLF
-        + "--batch_8194-cf13-1f56--";
-
-    parseInvalidBatchBody(batch, BatchException.MessageKeys.MISSING_CONTENT_TYPE);
-  }
-
-  @Test
-  public void testNoCloseDelimiter() throws BatchException, UnsupportedEncodingException {
-    final String batch = ""
-        + "--batch_8194-cf13-1f56" + CRLF
-        + GET_REQUEST;
-
-    parseInvalidBatchBody(batch, BatchException.MessageKeys.MISSING_CLOSE_DELIMITER);
-  }
-
-  @Test
-  public void testNoCloseDelimiter2() throws BatchException, UnsupportedEncodingException {
-    final String batch = ""
-        + "--batch_8194-cf13-1f56" + CRLF
-        + MIME_HEADERS
-        + CRLF
-        + "GET Employees('1')/EmployeeName HTTP/1.1" + CRLF;
-
-    parseInvalidBatchBody(batch, BatchException.MessageKeys.MISSING_CLOSE_DELIMITER);
-  }
-
-  @Test
-  public void testInvalidUri() throws UnsupportedEncodingException {
-    final String batch = ""
-        + "--batch_8194-cf13-1f56" + CRLF
-        + MIME_HEADERS
-        + CRLF
-        + "GET http://localhost/aa/odata/Employees('1')/EmployeeName HTTP/1.1" + CRLF
-        + CRLF
-        + CRLF
-        + "--batch_8194-cf13-1f56--";
-
-    parseInvalidBatchBody(batch, BatchException.MessageKeys.INVALID_URI);
-  }
-
-  @Test
-  public void testUriWithAbsolutePath() throws BatchException, UnsupportedEncodingException {
-    final String batch = ""
-        + "--batch_8194-cf13-1f56" + CRLF
-        + MIME_HEADERS
-        + CRLF
-        + "GET /odata/Employees('1')/EmployeeName HTTP/1.1" + CRLF
-        + "Host: http://localhost" + CRLF
-        + CRLF
-        + CRLF
-        + "--batch_8194-cf13-1f56--";
-
-    final List<BatchRequestPart> parts = parse(batch);
-    assertEquals(1, parts.size());
-
-    final BatchRequestPart part = parts.get(0);
-    assertEquals(1, part.getRequests().size());
-    final ODataRequest request = part.getRequests().get(0);
-
-    assertEquals("http://localhost/odata/Employees('1')/EmployeeName", request.getRawRequestUri());
-    assertEquals("http://localhost/odata", request.getRawBaseUri());
-    assertEquals("/Employees('1')/EmployeeName", request.getRawODataPath());
-    assertEquals("", request.getRawQueryPath());
-  }
-
-  @Test
-  public void testUriWithAbsolutePathMissingHostHeader() throws BatchException, UnsupportedEncodingException {
-    final String batch = ""
-        + "--batch_8194-cf13-1f56" + CRLF
-        + MIME_HEADERS
-        + CRLF
-        + "GET /odata/Employees('1')/EmployeeName HTTP/1.1" + CRLF
-        + CRLF
-        + CRLF
-        + "--batch_8194-cf13-1f56--";
-
-    parseInvalidBatchBody(batch, MessageKeys.MISSING_MANDATORY_HEADER);
-  }
-
-  @Test
-  public void testUriWithAbsolutePathMissingHostDulpicatedHeader() throws BatchException, UnsupportedEncodingException {
-    final String batch = ""
-        + "--batch_8194-cf13-1f56" + CRLF
-        + MIME_HEADERS
-        + CRLF
-        + "GET /odata/Employees('1')/EmployeeName HTTP/1.1" + CRLF
-        + "Host: http://localhost" + CRLF
-        + "Host: http://localhost/odata" + CRLF
-        + CRLF
-        + CRLF
-        + "--batch_8194-cf13-1f56--";
-
-    parseInvalidBatchBody(batch, MessageKeys.MISSING_MANDATORY_HEADER);
-  }
-
-  @Test
-  public void testUriWithAbsolutePathOtherHost() throws BatchException, UnsupportedEncodingException {
-    final String batch = ""
-        + "--batch_8194-cf13-1f56" + CRLF
-        + MIME_HEADERS
-        + CRLF
-        + "GET /odata/Employees('1')/EmployeeName HTTP/1.1" + CRLF
-        + "Host: http://localhost2" + CRLF
-        + CRLF
-        + CRLF
-        + "--batch_8194-cf13-1f56--";
-
-    parseInvalidBatchBody(batch, MessageKeys.INVALID_URI);
-  }
-
-  @Test
-  public void testUriWithAbsolutePathWrongPath() throws BatchException, UnsupportedEncodingException {
-    final String batch = ""
-        + "--batch_8194-cf13-1f56" + CRLF
-        + MIME_HEADERS
-        + CRLF
-        + "GET /myservice/Employees('1')/EmployeeName HTTP/1.1" + CRLF
-        + "Host: http://localhost" + CRLF
-        + CRLF
-        + CRLF
-        + "--batch_8194-cf13-1f56--";
-
-    parseInvalidBatchBody(batch, MessageKeys.INVALID_URI);
-  }
-
-  @Test
-  public void testNoCloseDelimiter3() throws UnsupportedEncodingException {
-    final String batch = "--batch_8194-cf13-1f56" + CRLF + GET_REQUEST + "--batch_8194-cf13-1f56-"/* no hyphen */;
-
-    parseInvalidBatchBody(batch, BatchException.MessageKeys.MISSING_CLOSE_DELIMITER);
-  }
-
-  @Test
-  public void testNegativeContentLengthChangeSet() throws BatchException, IOException {
-    final String batch = ""
-        + "--batch_8194-cf13-1f56" + CRLF
-        + "Content-Type: multipart/mixed; boundary=changeset_f980-1cb6-94dd" + CRLF
-        + CRLF
-        + "--changeset_f980-1cb6-94dd" + CRLF
-        + MIME_HEADERS
-        + "Content-ID: 1" + CRLF
-        + "Content-Length: -2" + CRLF
-        + CRLF
-        + "PUT EmployeeName HTTP/1.1" + CRLF
-        + "Content-Type: application/json;odata=verbose" + CRLF
-        + "Content-Id: 1" + CRLF
-        + CRLF
-        + "{\"EmployeeName\":\"Peter Fall\"}" + CRLF
-        + "--changeset_f980-1cb6-94dd--" + CRLF
-        + CRLF
-        + "--batch_8194-cf13-1f56--";
-
-    parse(batch);
-  }
-
-  @Test
-  public void testNegativeContentLengthRequest() throws BatchException, IOException {
-    final String batch = ""
-        + "--batch_8194-cf13-1f56" + CRLF
-        + "Content-Type: multipart/mixed; boundary=changeset_f980-1cb6-94dd" + CRLF
-        + CRLF
-        + "--changeset_f980-1cb6-94dd" + CRLF
-        + MIME_HEADERS
-        + "Content-ID: 1" + CRLF
-        + CRLF
-        + "PUT EmployeeName HTTP/1.1" + CRLF
-        + "Content-Type: application/json;odata=verbose" + CRLF
-        + "Content-Id: 1" + CRLF
-        + "Content-Length: 2" + CRLF
-        + CRLF
-        + "{\"EmployeeName\":\"Peter Fall\"}" + CRLF
-        + "--changeset_f980-1cb6-94dd--" + CRLF
-        + CRLF
-        + "--batch_8194-cf13-1f56--";
-
-    parse(batch);
-  }
-
-  @Test
-  public void testContentLengthGreatherThanBodyLength() throws BatchException, IOException {
-    final String batch = ""
-        + "--batch_8194-cf13-1f56" + CRLF
-        + "Content-Type: multipart/mixed; boundary=changeset_f980-1cb6-94dd" + CRLF
-        + CRLF
-        + "--changeset_f980-1cb6-94dd" + CRLF
-        + MIME_HEADERS
-        + "Content-ID: 1" + CRLF
-        + CRLF
-        + "PUT Employee/Name HTTP/1.1" + CRLF
-        + "Content-Type: application/json;odata=verbose" + CRLF
-        + "Content-Length: 100000" + CRLF
-        + CRLF
-        + "{\"EmployeeName\":\"Peter Fall\"}" + CRLF
-        + "--changeset_f980-1cb6-94dd--" + CRLF
-        + CRLF
-        + "--batch_8194-cf13-1f56--";
-    final List<BatchRequestPart> batchRequestParts = parse(batch);
-
-    assertNotNull(batchRequestParts);
-
-    for (BatchRequestPart multipart : batchRequestParts) {
-      if (multipart.isChangeSet()) {
-        assertEquals(1, multipart.getRequests().size());
-
-        final ODataRequest request = multipart.getRequests().get(0);
-        assertEquals("{\"EmployeeName\":\"Peter Fall\"}", StringUtil.toString(request.getBody()));
-      }
-    }
-  }
-
-  @Test
-  public void testContentLengthSmallerThanBodyLength() throws BatchException, IOException {
-    final String batch = ""
-        + "--batch_8194-cf13-1f56" + CRLF
-        + "Content-Type: multipart/mixed; boundary=changeset_f980-1cb6-94dd" + CRLF
-        + CRLF
-        + "--changeset_f980-1cb6-94dd" + CRLF
-        + MIME_HEADERS
-        + "Content-ID: 1" + CRLF
-        + CRLF
-        + "PUT EmployeeName HTTP/1.1" + CRLF
-        + "Content-Type: application/json;odata=verbose" + CRLF
-        + "Content-Length: 10" + CRLF
-        + CRLF
-        + "{\"EmployeeName\":\"Peter Fall\"}" + CRLF
-        + "--changeset_f980-1cb6-94dd--" + CRLF
-        + CRLF
-        + "--batch_8194-cf13-1f56--";
-    final List<BatchRequestPart> batchRequestParts = parse(batch);
-
-    assertNotNull(batchRequestParts);
-
-    for (BatchRequestPart multipart : batchRequestParts) {
-      if (multipart.isChangeSet()) {
-        assertEquals(1, multipart.getRequests().size());
-
-        final ODataRequest request = multipart.getRequests().get(0);
-        assertEquals("{\"Employee", StringUtil.toString(request.getBody()));
-      }
-    }
-  }
-
-  @Test
-  public void testNonNumericContentLength() throws UnsupportedEncodingException {
-    final String batch = ""
-        + "--batch_8194-cf13-1f56" + CRLF
-        + "Content-Type: multipart/mixed; boundary=changeset_f980-1cb6-94dd" + CRLF
-        + CRLF
-        + "--changeset_f980-1cb6-94dd" + CRLF
-        + MIME_HEADERS
-        + "Content-ID: 1" + CRLF
-        + CRLF
-        + "PUT EmployeeName HTTP/1.1" + CRLF
-        + "Content-Type: application/json;odata=verbose" + CRLF
-        + "Content-Length: 10abc" + CRLF
-        + CRLF
-        + "{\"EmployeeName\":\"Peter Fall\"}" + CRLF
-        + "--changeset_f980-1cb6-94dd--" + CRLF
-        + CRLF
-        + "--batch_8194-cf13-1f56--";
-
-    parseInvalidBatchBody(batch, BatchException.MessageKeys.INVALID_HEADER);
-  }
-
-  @Test
-  public void testNonStrictParser() throws BatchException, IOException {
-    final String batch = ""
-        + "--batch_8194-cf13-1f56" + CRLF
-        + "Content-Type: multipart/mixed;boundary=changeset_8194-cf13-1f56" + CRLF
-        + "--changeset_8194-cf13-1f56" + CRLF
-        + MIME_HEADERS
-        + "Content-ID: myRequest" + CRLF
-        + "PUT Employees('2')/EmployeeName HTTP/1.1" + CRLF
-        + "Accept: application/atomsvc+xml;q=0.8, application/json;odata=verbose;q=0.5, */*;q=0.1" + CRLF
-        + "Content-Type: application/json;odata=verbose" + CRLF
-        + "MaxDataServiceVersion: 2.0" + CRLF
-        + "{\"EmployeeName\":\"Frederic Fall MODIFIED\"}" + CRLF
-        + "--changeset_8194-cf13-1f56--" + CRLF
-        + "--batch_8194-cf13-1f56--";
-
-    final List<BatchRequestPart> requests = parse(batch, false);
-
-    assertNotNull(requests);
-    assertEquals(1, requests.size());
-
-    final BatchRequestPart part = requests.get(0);
-    assertTrue(part.isChangeSet());
-    assertNotNull(part.getRequests());
-    assertEquals(1, part.getRequests().size());
-
-    final ODataRequest changeRequest = part.getRequests().get(0);
-    assertEquals("{\"EmployeeName\":\"Frederic Fall MODIFIED\"}",
-        StringUtil.toString(changeRequest.getBody()));
-    assertEquals("application/json;odata=verbose", changeRequest.getHeader(HttpHeader.CONTENT_TYPE));
-    assertEquals(HttpMethod.PUT, changeRequest.getMethod());
-  }
-
-  @Test
-  public void testNonStrictParserMoreCRLF() throws UnsupportedEncodingException {
-    final String batch = ""
-        + "--batch_8194-cf13-1f56" + CRLF
-        + "Content-Type: multipart/mixed;boundary=changeset_8194-cf13-1f56" + CRLF
-        + "--changeset_8194-cf13-1f56" + CRLF
-        + MIME_HEADERS
-        + CRLF
-        + CRLF // Only one CRLF allowed
-        + "PUT Employees('2')/EmployeeName HTTP/1.1" + CRLF
-        + "Accept: application/atomsvc+xml;q=0.8, application/json;odata=verbose;q=0.5, */*;q=0.1" + CRLF
-        + "Content-Type: application/json;odata=verbose" + CRLF
-        + "MaxDataServiceVersion: 2.0" + CRLF
-        + "{\"EmployeeName\":\"Frederic Fall MODIFIED\"}" + CRLF
-        + "--changeset_8194-cf13-1f56--" + CRLF
-        + "--batch_8194-cf13-1f56--";
-
-    parseInvalidBatchBody(batch, BatchException.MessageKeys.INVALID_STATUS_LINE, false);
-  }
-
-  @Test
-  public void testContentId() throws BatchException, UnsupportedEncodingException {
-    final String batch = ""
-        + "--batch_8194-cf13-1f56" + CRLF
-        + MIME_HEADERS
-        + CRLF
-        + "GET Employees HTTP/1.1" + CRLF
-        + "accept: */*,application/atom+xml,application/atomsvc+xml,application/xml" + CRLF
-        + "Content-Id: BBB" + CRLF
-        + CRLF + CRLF
-        + "--batch_8194-cf13-1f56" + CRLF
-        + "Content-Type: multipart/mixed; boundary=changeset_f980-1cb6-94dd" + CRLF
-        + CRLF
-        + "--changeset_f980-1cb6-94dd" + CRLF
-        + MIME_HEADERS
-        + "Content-Id: 1" + CRLF
-        + CRLF
-        + "POST Employees HTTP/1.1" + CRLF
-        + "Content-type: application/octet-stream" + CRLF
-        + CRLF
-        + "/9j/4AAQSkZJRgABAQEBLAEsAAD/4RM0RXhpZgAATU0AKgAAAAgABwESAAMAAAABAAEAAAEaAAUAAAABAAAAYgEbAAUAAAA" + CRLF
-        + CRLF
-        + "--changeset_f980-1cb6-94dd" + CRLF
-        + MIME_HEADERS
-        + CRLF
-        + "PUT $1/EmployeeName HTTP/1.1" + CRLF
-        + "Content-Type: application/json;odata=verbose" + CRLF
-        + "Content-Id: 2" + CRLF
-        + CRLF
-        + "{\"EmployeeName\":\"Peter Fall\"}" + CRLF
-        + "--changeset_f980-1cb6-94dd--" + CRLF
-        + CRLF
-        + "--batch_8194-cf13-1f56--";
-
-    final List<BatchRequestPart> batchRequestParts = parse(batch);
-    assertNotNull(batchRequestParts);
-
-    for (BatchRequestPart multipart : batchRequestParts) {
-      if (!multipart.isChangeSet()) {
-        assertEquals(1, multipart.getRequests().size());
-        final ODataRequest retrieveRequest = multipart.getRequests().get(0);
-
-        assertEquals("BBB", retrieveRequest.getHeader(BatchParserCommon.HTTP_CONTENT_ID));
-      } else {
-        for (ODataRequest request : multipart.getRequests()) {
-          if (HttpMethod.POST.equals(request.getMethod())) {
-            assertEquals("1", request.getHeader(BatchParserCommon.HTTP_CONTENT_ID));
-          } else if (HttpMethod.PUT.equals(request.getMethod())) {
-            assertEquals("2", request.getHeader(BatchParserCommon.HTTP_CONTENT_ID));
-            assertEquals("/$1/EmployeeName", request.getRawODataPath());
-            assertEquals("http://localhost/odata/$1/EmployeeName", request.getRawRequestUri());
-          }
-        }
-      }
-    }
-  }
-
-  @Test
-  public void testNoContentId() throws BatchException, UnsupportedEncodingException {
-    final String batch = ""
-        + "--batch_8194-cf13-1f56" + CRLF
-        + MIME_HEADERS
-        + CRLF
-        + "GET Employees HTTP/1.1" + CRLF
-        + "accept: */*,application/atom+xml,application/atomsvc+xml,application/xml" + CRLF
-        + CRLF + CRLF
-        + "--batch_8194-cf13-1f56" + CRLF
-        + "Content-Type: multipart/mixed; boundary=changeset_f980-1cb6-94dd" + CRLF
-        + CRLF
-        + "--changeset_f980-1cb6-94dd" + CRLF
-        + MIME_HEADERS
-        + "Content-Id: 1" + CRLF
-        + CRLF
-        + "POST Employees HTTP/1.1" + CRLF
-        + "Content-type: application/octet-stream" + CRLF
-        + CRLF
-        + "/9j/4AAQSkZJRgABAQEBLAEsAAD/4RM0RXhpZgAATU0AKgAAAAgABwESAAMAAAABAAEAAAEaAAUAAAABAAAAYgEbAAUAAAA" + CRLF
-        + CRLF
-        + "--changeset_f980-1cb6-94dd" + CRLF
-        + MIME_HEADERS
-        + "Content-Id: 1" + CRLF
-        + CRLF
-        + "PUT $1/EmployeeName HTTP/1.1" + CRLF
-        + "Content-Type: application/json;odata=verbose" + CRLF
-        + CRLF
-        + "{\"EmployeeName\":\"Peter Fall\"}" + CRLF
-        + "--changeset_f980-1cb6-94dd--" + CRLF
-        + CRLF
-        + "--batch_8194-cf13-1f56--";
-
-    parse(batch);
-  }
-
-  @Test
-  public void testPreamble() throws BatchException, IOException {
-    final String batch = ""
-        + "This is a preamble and must be ignored" + CRLF
-        + CRLF
-        + CRLF
-        + "----1242" + CRLF
-        + "--batch_8194-cf13-1f56" + CRLF
-        + MIME_HEADERS
-        + CRLF
-        + "GET Employees HTTP/1.1" + CRLF
-        + "accept: */*,application/atom+xml,application/atomsvc+xml,application/xml" + CRLF
-        + "Content-Id: BBB" + CRLF
-        + CRLF
-        + CRLF
-        + "--batch_8194-cf13-1f56" + CRLF
-        + "Content-Type: multipart/mixed; boundary=changeset_f980-1cb6-94dd" + CRLF
-        + CRLF
-        + "This is a preamble and must be ignored" + CRLF
-        + CRLF
-        + CRLF
-        + "----1242" + CRLF
-        + "--changeset_f980-1cb6-94dd" + CRLF
-        + MIME_HEADERS
-        + "Content-Id: 1" + CRLF
-        + CRLF
-        + "POST Employees HTTP/1.1" + CRLF
-        + "Content-type: application/octet-stream" + CRLF
-        + CRLF
-        + "/9j/4AAQSkZJRgABAQEBLAEsAAD/4RM0RXhpZgAATU0AKgAAAAgABwESAAMAAAABAAEAAAEaAAUAAAABAAAAYgEbAAUAAAA" + CRLF
-        + CRLF
-        + "--changeset_f980-1cb6-94dd" + CRLF
-        + MIME_HEADERS
-        + "Content-ID: 2" + CRLF
-        + CRLF
-        + "PUT $1/EmployeeName HTTP/1.1" + CRLF
-        + "Content-Type: application/json;odata=verbose" + CRLF
-        + CRLF
-        + "{\"EmployeeName\":\"Peter Fall\"}" + CRLF
-        + "--changeset_f980-1cb6-94dd--" + CRLF
-        + CRLF
-        + "--batch_8194-cf13-1f56--";
-    final List<BatchRequestPart> batchRequestParts = parse(batch);
-
-    assertNotNull(batchRequestParts);
-    assertEquals(2, batchRequestParts.size());
-
-    final BatchRequestPart getRequestPart = batchRequestParts.get(0);
-    assertEquals(1, getRequestPart.getRequests().size());
-
-    final ODataRequest getRequest = getRequestPart.getRequests().get(0);
-    assertEquals(HttpMethod.GET, getRequest.getMethod());
-
-    final BatchRequestPart changeSetPart = batchRequestParts.get(1);
-    assertEquals(2, changeSetPart.getRequests().size());
-    assertEquals("/9j/4AAQSkZJRgABAQEBLAEsAAD/4RM0RXhpZgAATU0AKgAAAAgABwESAAMAAAABAAEAAAEaAAUAAAABAAAAYgEbAAUAAAA"
-        + CRLF,
-        StringUtil.toString(changeSetPart.getRequests().get(0).getBody()));
-    assertEquals("{\"EmployeeName\":\"Peter Fall\"}",
-        StringUtil.toString(changeSetPart.getRequests().get(1).getBody()));
-  }
-
-  @Test
-  public void testContentTypeCaseInsensitive() throws BatchException, IOException {
-    final String batch = ""
-        + "--batch_8194-cf13-1f56" + CRLF
-        + "Content-Type: muLTiParT/mixed; boundary=changeset_f980-1cb6-94dd" + CRLF
-        + CRLF
-        + "--changeset_f980-1cb6-94dd" + CRLF
-        + MIME_HEADERS
-        + "Content-ID: 1" + CRLF
-        + "Content-Length: 200" + CRLF
-        + CRLF
-        + "PUT EmployeeName HTTP/1.1" + CRLF
-        + "Content-Type: application/json;odata=verbose" + CRLF
-        + CRLF
-        + "{\"EmployeeName\":\"Peter Fall\"}" + CRLF
-        + "--changeset_f980-1cb6-94dd--" + CRLF
-        + CRLF
-        + "--batch_8194-cf13-1f56--";
-
-    parse(batch);
-  }
-
-  @Test
-  public void testContentTypeBoundaryCaseInsensitive() throws BatchException, IOException {
-    final String batch = ""
-        + "--batch_8194-cf13-1f56" + CRLF
-        + "Content-Type: multipart/mixed; bOunDaRy=changeset_f980-1cb6-94dd" + CRLF
-        + CRLF
-        + "--changeset_f980-1cb6-94dd" + CRLF
-        + MIME_HEADERS
-        + "Content-ID: 1" + CRLF
-        + CRLF
-        + "PUT EmployeeName HTTP/1.1" + CRLF
-        + "Content-Type: application/json;odata=verbose" + CRLF
-        + CRLF
-        + "{\"EmployeeName\":\"Peter Fall\"}" + CRLF
-        + "--changeset_f980-1cb6-94dd--" + CRLF
-        + CRLF
-        + "--batch_8194-cf13-1f56--";
-    final List<BatchRequestPart> batchRequestParts = parse(batch);
-
-    assertNotNull(batchRequestParts);
-    assertEquals(1, batchRequestParts.size());
-    assertTrue(batchRequestParts.get(0).isChangeSet());
-    assertEquals(1, batchRequestParts.get(0).getRequests().size());
-  }
-
-  @Test
-  public void testEpilog() throws BatchException, IOException {
-    String batch = ""
-        + "--batch_8194-cf13-1f56" + CRLF
-        + MIME_HEADERS
-        + CRLF
-        + "GET Employees HTTP/1.1" + CRLF
-        + "accept: */*,application/atom+xml,application/atomsvc+xml,application/xml" + CRLF
-        + "Content-Id: BBB" + CRLF
-        + CRLF
-        + CRLF
-        + "--batch_8194-cf13-1f56" + CRLF
-        + "Content-Type: multipart/mixed; boundary=changeset_f980-1cb6-94dd" + CRLF
-        + CRLF
-        + "--changeset_f980-1cb6-94dd" + CRLF
-        + MIME_HEADERS
-        + "Content-Id: 1" + CRLF
-        + CRLF
-        + "POST Employees HTTP/1.1" + CRLF
-        + "Content-type: application/octet-stream" + CRLF
-        + CRLF
-        + "/9j/4AAQSkZJRgABAQEBLAEsAAD/4RM0RXhpZgAATU0AKgAAAAgABwESAAMAAAABAAEAAAEaAAUAAAABAAAAYgEbAAUAAAA" + CRLF
-        + CRLF
-        + "--changeset_f980-1cb6-94dd" + CRLF
-        + MIME_HEADERS
-        + "Content-ID: 2" + CRLF
-        + CRLF
-        + "PUT $1/EmployeeName HTTP/1.1" + CRLF
-        + "Content-Type: application/json;odata=verbose" + CRLF
-        + CRLF
-        + "{\"EmployeeName\":\"Peter Fall\"}" + CRLF
-        + "--changeset_f980-1cb6-94dd--" + CRLF
-        + CRLF
-        + "This is an epilog and must be ignored" + CRLF
-        + CRLF
-        + CRLF
-        + "----1242"
-        + CRLF
-        + "--batch_8194-cf13-1f56--"
-        + CRLF
-        + "This is an epilog and must be ignored" + CRLF
-        + CRLF
-        + CRLF
-        + "----1242";
-    final List<BatchRequestPart> batchRequestParts = parse(batch);
-
-    assertNotNull(batchRequestParts);
-    assertEquals(2, batchRequestParts.size());
-
-    BatchRequestPart getRequestPart = batchRequestParts.get(0);
-    assertEquals(1, getRequestPart.getRequests().size());
-    ODataRequest getRequest = getRequestPart.getRequests().get(0);
-    assertEquals(HttpMethod.GET, getRequest.getMethod());
-
-    BatchRequestPart changeSetPart = batchRequestParts.get(1);
-    assertEquals(2, changeSetPart.getRequests().size());
-    assertEquals("/9j/4AAQSkZJRgABAQEBLAEsAAD/4RM0RXhpZgAATU0AKgAAAAgABwESAAMAAAABAAEAAAEaAAUAAAABAAAAYgEbAAUAAAA"
-        + CRLF,
-        StringUtil.toString(changeSetPart.getRequests().get(0).getBody()));
-    assertEquals("{\"EmployeeName\":\"Peter Fall\"}",
-        StringUtil.toString(changeSetPart.getRequests().get(1).getBody()));
-  }
-
-  @Test
-  public void testLargeBatch() throws BatchException, IOException {
-    final InputStream in = readFile("/batchLarge.batch");
-    parse(in);
-  }
-
-  @Test
-  public void testForddenHeaderAuthorisation() throws UnsupportedEncodingException {
-    final String batch = ""
-        + "--batch_8194-cf13-1f56" + CRLF
-        + MIME_HEADERS
-        + CRLF
-        + "GET Employees('1')/EmployeeName HTTP/1.1" + CRLF
-        + "Authorization: Basic QWxhZdsdsddsduIHNlc2FtZQ==" + CRLF
-        + CRLF
-        + CRLF
-        + "--batch_8194-cf13-1f56--";
-
-    parseInvalidBatchBody(batch, MessageKeys.FORBIDDEN_HEADER);
-  }
-
-  @Test
-  public void testForddenHeaderExpect() throws UnsupportedEncodingException {
-    final String batch = ""
-        + "--batch_8194-cf13-1f56" + CRLF
-        + MIME_HEADERS
-        + CRLF
-        + "GET Employees('1')/EmployeeName HTTP/1.1" + CRLF
-        + "Expect: 100-continue" + CRLF
-        + CRLF
-        + CRLF
-        + "--batch_8194-cf13-1f56--";
-
-    parseInvalidBatchBody(batch, MessageKeys.FORBIDDEN_HEADER);
-  }
-
-  @Test
-  public void testForddenHeaderFrom() throws UnsupportedEncodingException {
-    final String batch = ""
-        + "--batch_8194-cf13-1f56" + CRLF
-        + MIME_HEADERS
-        + CRLF
-        + "GET Employees('1')/EmployeeName HTTP/1.1" + CRLF
-        + "From: test@test.com" + CRLF
-        + CRLF
-        + CRLF
-        + "--batch_8194-cf13-1f56--";
-
-    parseInvalidBatchBody(batch, MessageKeys.FORBIDDEN_HEADER);
-  }
-
-  @Test
-  public void testForddenHeaderRange() throws UnsupportedEncodingException {
-    final String batch = ""
-        + "--batch_8194-cf13-1f56" + CRLF
-        + MIME_HEADERS
-        + CRLF
-        + "GET Employees('1')/EmployeeName HTTP/1.1" + CRLF
-        + "Range: 200-256" + CRLF
-        + CRLF
-        + CRLF
-        + "--batch_8194-cf13-1f56--";
-
-    parseInvalidBatchBody(batch, MessageKeys.FORBIDDEN_HEADER);
-  }
-
-  @Test
-  public void testForddenHeaderMaxForwards() throws UnsupportedEncodingException {
-    final String batch = ""
-        + "--batch_8194-cf13-1f56" + CRLF
-        + MIME_HEADERS
-        + CRLF
-        + "GET Employees('1')/EmployeeName HTTP/1.1" + CRLF
-        + "Max-Forwards: 3" + CRLF
-        + CRLF
-        + CRLF
-        + "--batch_8194-cf13-1f56--";
-
-    parseInvalidBatchBody(batch, MessageKeys.FORBIDDEN_HEADER);
-  }
-
-  @Test
-  public void testForddenHeaderTE() throws UnsupportedEncodingException {
-    final String batch = ""
-        + "--batch_8194-cf13-1f56" + CRLF
-        + MIME_HEADERS
-        + CRLF
-        + "GET Employees('1')/EmployeeName HTTP/1.1" + CRLF
-        + "TE: deflate" + CRLF
-        + CRLF
-        + CRLF
-        + "--batch_8194-cf13-1f56--";
-
-    parseInvalidBatchBody(batch, MessageKeys.FORBIDDEN_HEADER);
-  }
-
-  private List<BatchRequestPart> parse(final InputStream in, final boolean isStrict) throws BatchException {
-    final BatchParser parser = new BatchParser();
-    final List<BatchRequestPart> batchRequestParts =
-        parser.parseBatchRequest(in, CONTENT_TYPE, SERVICE_ROOT, "", isStrict);
-
-    assertNotNull(batchRequestParts);
-
-    return batchRequestParts;
-  }
-
-  private List<BatchRequestPart> parse(final InputStream in) throws BatchException {
-    return parse(in, true);
-  }
-
-  private List<BatchRequestPart> parse(final String batch) throws BatchException, UnsupportedEncodingException {
-    return parse(batch, true);
-  }
-
-  private List<BatchRequestPart> parse(final String batch, final boolean isStrict) throws BatchException,
-      UnsupportedEncodingException {
-    return parse(StringUtil.toInputStream(batch), isStrict);
-  }
-
-  private void parseInvalidBatchBody(final String batch, final MessageKeys key, final boolean isStrict)
-      throws UnsupportedEncodingException {
-    final BatchParser parser = new BatchParser();
-
-    try {
-      parser.parseBatchRequest(StringUtil.toInputStream(batch), CONTENT_TYPE, SERVICE_ROOT, "", isStrict);
-      fail("No exception thrown. Expect: " + key.toString());
-    } catch (BatchException e) {
-      assertMessageKey(e, key);
-    }
-  }
-
-  private void parseInvalidBatchBody(final String batch, final MessageKeys key) throws UnsupportedEncodingException {
-    parseInvalidBatchBody(batch, key, true);
-  }
-
-  private void assertMessageKey(final BatchException e, final MessageKeys key) {
-    assertEquals(key, e.getMessageKey());
-  }
-
-  private InputStream readFile(final String fileName) throws IOException {
-    final InputStream in = ClassLoader.class.getResourceAsStream(fileName);
-    if (in == null) {
-      throw new IOException("Requested file '" + fileName + "' was not found.");
-    }
-    return in;
-  }
-}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/StringUtil.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/StringUtil.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/StringUtil.java
deleted file mode 100644
index 2602852..0000000
--- a/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/StringUtil.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.olingo.server.core.batch;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.UnsupportedEncodingException;
-
-import org.apache.olingo.commons.api.ODataRuntimeException;
-import org.apache.olingo.server.core.batch.parser.BufferedReaderIncludingLineEndings;
-
-public class StringUtil {
-  
-  
-  public static String toString(final InputStream in) throws IOException {
-    final StringBuilder builder = new StringBuilder();
-    final BufferedReaderIncludingLineEndings reader = new BufferedReaderIncludingLineEndings(new InputStreamReader(in));
-    String currentLine;
-    
-    while((currentLine = reader.readLine()) != null) {
-      builder.append(currentLine);
-    }
-    
-    reader.close();
-    
-    return builder.toString();
-  }
-
-  public static InputStream toInputStream(final String string) {
-    try {
-      return new ByteArrayInputStream(string.getBytes("UTF-8"));
-    } catch (UnsupportedEncodingException e) {
-      throw new ODataRuntimeException("Charset UTF-8 not found");
-    }
-  }
-}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/BatchChangeSetSorterTest.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/BatchChangeSetSorterTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/BatchChangeSetSorterTest.java
deleted file mode 100644
index c8b725a..0000000
--- a/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/BatchChangeSetSorterTest.java
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.olingo.server.core.batch.handler;
-
-import static org.junit.Assert.*;
-
-import java.io.ByteArrayInputStream;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-import org.apache.olingo.commons.api.http.HttpMethod;
-import org.apache.olingo.server.api.ODataRequest;
-import org.apache.olingo.server.api.batch.BatchException;
-import org.apache.olingo.server.core.batch.parser.BatchParserCommon;
-import org.junit.Test;
-
-public class BatchChangeSetSorterTest {
-
-  private static final String BASE_URI = "http://localhost/odata.src";
-  
-  @Test
-  public void test() throws BatchException {
-    final List<ODataRequest> changeSet = new ArrayList<ODataRequest>();
-    changeSet.add(createRequest(HttpMethod.POST, BASE_URI, "$1/Adress", "2"));
-    changeSet.add(createRequest(HttpMethod.POST, BASE_URI, "/Employees", "1"));
-    
-   BatchChangeSetSorter sorter = new BatchChangeSetSorter(changeSet);
-   final List<ODataRequest> sortedChangeSet = sorter.getOrderdRequests();
-   
-   assertEquals(2, sortedChangeSet.size());
-   assertEquals("1", getContentId(sortedChangeSet.get(0)));
-   assertEquals("2", getContentId(sortedChangeSet.get(1)));
-  }
-  
-  private String getContentId(ODataRequest request) {
-    return request.getHeader(BatchParserCommon.HTTP_CONTENT_ID);
-  }
-  
-  @Test
-  public void testNoContentId() throws BatchException {
-    final List<ODataRequest> changeSet = new ArrayList<ODataRequest>();
-    changeSet.add(createRequest(HttpMethod.POST, BASE_URI, "$1/Department", "2"));
-    changeSet.add(createRequest(HttpMethod.POST, BASE_URI, "Employees", "1"));
-    changeSet.add(createRequest(HttpMethod.POST, BASE_URI, "Employee('2')/Address"));
-    changeSet.add(createRequest(HttpMethod.POST, BASE_URI, "Employee('3')/Address"));
-    changeSet.add(createRequest(HttpMethod.POST, BASE_URI, "$2/Manager", "3"));
-    
-    BatchChangeSetSorter sorter = new BatchChangeSetSorter(changeSet);
-   final List<ODataRequest> sortedChangeSet = sorter.getOrderdRequests();
-   
-   assertEquals(5, sortedChangeSet.size());
-   assertEquals("1", getContentId(sortedChangeSet.get(0)));
-   assertEquals(null, getContentId(sortedChangeSet.get(1)));
-   assertEquals(null, getContentId(sortedChangeSet.get(2)));
-   assertEquals("2", getContentId(sortedChangeSet.get(3)));
-   assertEquals("3", getContentId(sortedChangeSet.get(4)));
-  }
-  
-  @SuppressWarnings("unused")
-  @Test(expected=BatchException.class)
-  public void testContentIdNotAvailable() throws BatchException {
-    final List<ODataRequest> changeSet = new ArrayList<ODataRequest>();
-    changeSet.add(createRequest(HttpMethod.POST, BASE_URI, "$1/Department", "2"));
-    changeSet.add(createRequest(HttpMethod.POST, BASE_URI, "Employees", "1"));
-    changeSet.add(createRequest(HttpMethod.POST, BASE_URI, "Employee('2')/Address"));
-    changeSet.add(createRequest(HttpMethod.POST, BASE_URI, "Employee('3')/Address"));
-    changeSet.add(createRequest(HttpMethod.POST, BASE_URI, "$4/Manager", "3")); //4 is not available
-    
-   BatchChangeSetSorter sorter = new BatchChangeSetSorter(changeSet);
-   final List<ODataRequest> sortedChangeSet = sorter.getOrderdRequests();
-  }
-  
-  @Test
-  public void testStringAsContentId() throws BatchException {
-    final List<ODataRequest> changeSet = new ArrayList<ODataRequest>();
-    changeSet.add(createRequest(HttpMethod.POST, BASE_URI, "$One/Department", "Two"));
-    changeSet.add(createRequest(HttpMethod.POST, BASE_URI, "Employees", "One"));
-    changeSet.add(createRequest(HttpMethod.POST, BASE_URI, "Employee('2')/Address"));
-    changeSet.add(createRequest(HttpMethod.POST, BASE_URI, "Employee('3')/Address"));
-    changeSet.add(createRequest(HttpMethod.POST, BASE_URI, "$Two/Manager", "Three"));
-    
-   BatchChangeSetSorter sorter = new BatchChangeSetSorter(changeSet);
-   final List<ODataRequest> sortedChangeSet = sorter.getOrderdRequests();
-   
-   assertEquals(5, sortedChangeSet.size());
-   assertEquals("One", getContentId(sortedChangeSet.get(0)));
-   assertEquals(null, getContentId(sortedChangeSet.get(1)));
-   assertEquals(null, getContentId(sortedChangeSet.get(2)));
-   assertEquals("Two", getContentId(sortedChangeSet.get(3)));
-   assertEquals("Three", getContentId(sortedChangeSet.get(4)));
-  }
-  
-  @Test
-  public void testRewriting() {
-    final String CONTENT_ID = "1";
-    final String ODATA_PATH ="/$" + CONTENT_ID + "/Address";
-    final String RESOURCE_URI = "Employee('1')";
-    final ODataRequest request = createRequest(HttpMethod.POST, BASE_URI, ODATA_PATH);
-    
-    BatchChangeSetSorter.replaceContentIdReference(request, CONTENT_ID, RESOURCE_URI);
-    assertEquals(BASE_URI + "/" + "Employee('1')/Address", request.getRawRequestUri());
-    assertEquals("Employee('1')/Address", request.getRawODataPath());
-  }
-  
-  @Test
-  public void testRewritingNoContentId() {
-    final String CONTENT_ID = "1";
-    final String ODATA_PATH = /* "$" + CONTENT_ID + */ "Address";
-    final String RESOURCE_URI = "Employee('1')";
-    final ODataRequest request = createRequest(HttpMethod.POST, BASE_URI, ODATA_PATH);
-    
-    BatchChangeSetSorter.replaceContentIdReference(request, CONTENT_ID, RESOURCE_URI);
-    assertEquals(BASE_URI + "/" + "Address", request.getRawRequestUri());
-    assertEquals("Address", request.getRawODataPath());
-  }
-  
-  @Test
-  public void testWrongRewriting() {
-    final String CONTENT_ID = "1";
-    final String ODATA_PATH = /*"$1" */ "$2" + "/Address";
-    final String RESOURCE_URI = "Employee('1')";
-    final ODataRequest request = createRequest(HttpMethod.POST, BASE_URI, ODATA_PATH);
-    
-    BatchChangeSetSorter.replaceContentIdReference(request, CONTENT_ID, RESOURCE_URI);
-    assertEquals(BASE_URI + "/" + "$2/Address", request.getRawRequestUri());
-    assertEquals("$2/Address", request.getRawODataPath());
-  }
-  
-  private ODataRequest createRequest(HttpMethod method, String baseUrl, String oDataPath) {
-    return createRequest(method, baseUrl, oDataPath, null);
-  }
-  
-  private ODataRequest createRequest(HttpMethod method, String baseUrl, String oDataPath, String contentId) {
-    final ODataRequest request = new ODataRequest();
-    request.setBody(new ByteArrayInputStream(new byte[0]));
-    request.setMethod(HttpMethod.GET);
-    request.setRawBaseUri(baseUrl);
-    request.setRawODataPath(oDataPath);
-    request.setRawRequestUri(baseUrl + "/" + oDataPath);
-    request.setRawQueryPath("");
-    
-    if(contentId != null) {
-      request.addHeader(BatchParserCommon.HTTP_CONTENT_ID, Arrays.asList(new String[] { contentId }));
-    }
-    return request;
-  }
-}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/MockedBatchHandlerTest.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/MockedBatchHandlerTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/MockedBatchHandlerTest.java
deleted file mode 100644
index c68919c..0000000
--- a/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/MockedBatchHandlerTest.java
+++ /dev/null
@@ -1,664 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.olingo.server.core.batch.handler;
-
-import static org.junit.Assert.*;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.UnsupportedEncodingException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import org.apache.olingo.commons.api.ODataRuntimeException;
-import org.apache.olingo.commons.api.http.HttpHeader;
-import org.apache.olingo.commons.api.http.HttpMethod;
-import org.apache.olingo.commons.api.http.HttpStatusCode;
-import org.apache.olingo.server.api.OData;
-import org.apache.olingo.server.api.ODataRequest;
-import org.apache.olingo.server.api.ODataResponse;
-import org.apache.olingo.server.api.ServiceMetadata;
-import org.apache.olingo.server.api.batch.BatchException;
-import org.apache.olingo.server.api.batch.BatchOperation;
-import org.apache.olingo.server.api.batch.BatchRequestPart;
-import org.apache.olingo.server.api.batch.ODataResponsePart;
-import org.apache.olingo.server.api.processor.BatchProcessor;
-import org.apache.olingo.server.core.ODataHandler;
-import org.apache.olingo.server.core.batch.parser.BatchParserCommon;
-import org.apache.olingo.server.core.batch.parser.BufferedReaderIncludingLineEndings;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class MockedBatchHandlerTest {
-
-  private static final String BATCH_CONTENT_TYPE = "multipart/mixed;boundary=batch_12345";
-  private static final String BATCH_ODATA_PATH = "/$batch";
-  private static final String BATCH_REQUEST_URI = "http://localhost:8080/odata/$batch";
-  private static final String BASE_URI = "http://localhost:8080/odata";
-  private static final String CRLF = "\r\n";
-  private ODataHandler oDataHandler;
-  private BatchHandler batchHandler;
-  private int entityCounter = 1;
-
-  @Before
-  public void setup() {
-    final BatchProcessor batchProcessor = new BatchTestProcessorImpl();
-
-    entityCounter = 1;
-    oDataHandler = mock(ODataHandler.class);
-    batchHandler = new BatchHandler(oDataHandler, batchProcessor);
-  }
-
-  @Test
-  public void test() throws BatchException, IOException {
-    final String content = "--batch_12345" + CRLF
-        + "Content-Type: multipart/mixed; boundary=changeset_12345" + CRLF
-        + CRLF
-        + "--changeset_12345" + CRLF
-        + "Content-Type: application/http" + CRLF
-        + "Content-Transfer-Encoding: binary" + CRLF
-        + "Content-Id: 4" + CRLF
-        + CRLF
-        + "PUT /$3/PropertyInt32 HTTP/1.1" + CRLF // Absolute URI with separate Host header and ref.
-        + "Host: http://localhost:8080/odata" + CRLF
-        + "Content-Type: application/json;odata=verbose" + CRLF
-        + CRLF
-        + CRLF
-        + "--changeset_12345" + CRLF
-        + "Content-Type: application/http" + CRLF
-        + "Content-Transfer-Encoding: binary" + CRLF
-        + "Content-Id: 5" + CRLF
-        + CRLF
-        + "POST http://localhost:8080/odata/$1/NavPropertyETTwoPrimMany HTTP/1.1" + CRLF // Absolute URI with ref.
-        + "Content-Type: application/json;odata=verbose" + CRLF
-        + CRLF
-        + CRLF
-        + "--changeset_12345" + CRLF
-        + "Content-Type: application/http" + CRLF
-        + "Content-Transfer-Encoding: binary" + CRLF
-        + "Content-Id: 2" + CRLF
-        + CRLF
-        + "POST $1/NavPropertyETTwoPrimMany HTTP/1.1" + CRLF // Relative URI with ref.
-        + "Content-Type: application/json;odata=verbose" + CRLF
-        + CRLF
-        + CRLF
-        + "--changeset_12345" + CRLF
-        + "Content-Type: application/http" + CRLF
-        + "Content-Transfer-Encoding: binary" + CRLF
-        + "Content-Id: 1" + CRLF
-        + CRLF
-        + "POST http://localhost:8080/odata/ESAllPrim HTTP/1.1" + CRLF // Absolute URI
-        + "Content-Type: application/json;odata=verbose" + CRLF
-        + CRLF
-        + CRLF
-        + "--changeset_12345" + CRLF
-        + "Content-Type: application/http" + CRLF
-        + "Content-Transfer-Encoding: binary" + CRLF
-        + "Content-Id: 3" + CRLF
-        + CRLF
-        + "PUT ESAllPrim(1) HTTP/1.1" + CRLF // Relative URI
-        + "Content-Type: application/json;odata=verbose" + CRLF
-        + CRLF
-        + CRLF
-        + "--changeset_12345" + CRLF
-        + "Content-Type: application/http" + CRLF
-        + "Content-Transfer-Encoding: binary" + CRLF
-        + "Content-Id: 6" + CRLF
-        + CRLF
-        + "PUT /ESAllPrim(1) HTTP/1.1" + CRLF // Absolute URI with separate Host header
-        + "Host: http://localhost:8080/odata"
-        + "Content-Type: application/json;odata=verbose" + CRLF
-        + CRLF
-        + CRLF
-        + "--changeset_12345--" + CRLF
-        + CRLF
-        + "--batch_12345--";
-    final Map<String, List<String>> header = getMimeHeader();
-    final ODataResponse response = new ODataResponse();
-    final ODataRequest request = buildODataRequest(content, header);
-
-    batchHandler.process(request, response, true);
-
-    BufferedReaderIncludingLineEndings reader =
-        new BufferedReaderIncludingLineEndings(new InputStreamReader(response.getContent()));
-
-    final List<String> responseContent = reader.toList();
-    reader.close();
-
-    int line = 0;
-    assertEquals(63, responseContent.size());
-
-    // Check change set
-    assertTrue(responseContent.get(line++).contains("--batch_"));
-    assertTrue(responseContent.get(line++).contains("Content-Type: multipart/mixed; boundary=changeset_"));
-
-    for (int i = 0; i < 6; i++) {
-      String contentId = checkChangeSetPartHeader(responseContent, line);
-      line += 6;
-
-      if ("1".equals(contentId)) {
-        assertEquals("HTTP/1.1 201 Created" + CRLF, responseContent.get(line++));
-        assertEquals("Location: " + BASE_URI + "/ESAllPrim(1)" + CRLF, responseContent.get(line++));
-        assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
-      } else if ("2".equals(contentId)) {
-        assertEquals("HTTP/1.1 201 Created" + CRLF, responseContent.get(line++));
-        assertEquals("Location: " + BASE_URI + "/ESTwoPrim(3)" + CRLF, responseContent.get(line++));
-        assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
-      } else if ("3".equals(contentId)) {
-        assertEquals("HTTP/1.1 200 OK" + CRLF, responseContent.get(line++));
-        assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
-      } else if ("4".equals(contentId)) {
-        assertEquals("HTTP/1.1 200 OK" + CRLF, responseContent.get(line++));
-        assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
-      } else if ("5".equals(contentId)) {
-        assertEquals("HTTP/1.1 201 Created" + CRLF, responseContent.get(line++));
-        assertEquals("Location: " + BASE_URI + "/ESTwoPrim(2)" + CRLF, responseContent.get(line++));
-        assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
-      } else if ("6".equals(contentId)) {
-        assertEquals("HTTP/1.1 200 OK" + CRLF, responseContent.get(line++));
-        assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
-      } else {
-        fail();
-      }
-
-      assertEquals(CRLF, responseContent.get(line++));
-    }
-
-    // Close body part (change set)
-    assertEquals(CRLF, responseContent.get(line++));
-    assertTrue(responseContent.get(line++).contains("--changeset_"));
-
-    // Close batch
-    assertEquals(CRLF, responseContent.get(line++));
-    assertTrue(responseContent.get(line++).contains("--batch_"));
-    assertEquals(63, line);
-  }
-
-  @Test
-  public void testGetRequest() throws BatchException, IOException {
-    final String content = ""
-        + "--batch_12345" + CRLF
-        + "Content-Type: application/http" + CRLF
-        + "Content-Transfer-Encoding: binary" + CRLF
-        + CRLF
-        + "GET ESAllPrim(0) HTTP/1.1" + CRLF
-        + CRLF
-        + CRLF
-        + "--batch_12345--";
-
-    final Map<String, List<String>> header = getMimeHeader();
-    final ODataResponse response = new ODataResponse();
-    final ODataRequest request = buildODataRequest(content, header);
-
-    batchHandler.process(request, response, true);
-
-    BufferedReaderIncludingLineEndings reader =
-        new BufferedReaderIncludingLineEndings(new InputStreamReader(response.getContent()));
-
-    final List<String> responseContent = reader.toList();
-    int line = 0;
-
-    assertEquals(9, responseContent.size());
-    assertTrue(responseContent.get(line++).contains("--batch_"));
-    assertEquals("Content-Type: application/http" + CRLF, responseContent.get(line++));
-    assertEquals("Content-Transfer-Encoding: binary" + CRLF, responseContent.get(line++));
-    assertEquals(CRLF, responseContent.get(line++));
-    assertEquals("HTTP/1.1 200 OK" + CRLF, responseContent.get(line++));
-    assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
-    assertEquals(CRLF, responseContent.get(line++));
-    assertEquals(CRLF, responseContent.get(line++));
-    assertTrue(responseContent.get(line++).contains("--batch_"));
-
-    assertEquals(9, line);
-
-    reader.close();
-  }
-
-  @Test
-  public void testMultipleChangeSets() throws BatchException, IOException {
-    final String content = ""
-        + "--batch_12345" + CRLF
-        + "Content-Type: multipart/mixed; boundary=changeset_12345" + CRLF
-        + CRLF
-        + "--changeset_12345" + CRLF
-        + "Content-Type: application/http" + CRLF
-        + "Content-Transfer-Encoding: binary" + CRLF
-        + "Content-Id: 2" + CRLF
-        + CRLF
-        + "POST /$1/NavPropertyETTwoPrimMany HTTP/1.1" + CRLF
-        + "Host: http://localhost:8080/odata" + CRLF
-        + "Content-Type: application/json;odata=verbose" + CRLF
-        + CRLF
-        + CRLF
-        + "--changeset_12345" + CRLF
-        + "Content-Type: application/http" + CRLF
-        + "Content-Transfer-Encoding: binary" + CRLF
-        + "Content-Id: 1" + CRLF
-        + CRLF
-        + "PUT ESAllPrim(1) HTTP/1.1" + CRLF
-        + "Content-Type: application/json;odata=verbose" + CRLF
-        + CRLF
-        + CRLF
-        + "--changeset_12345--" + CRLF
-
-        + "--batch_12345" + CRLF
-        + "Content-Type: multipart/mixed; boundary=changeset_54321" + CRLF
-        + CRLF
-        + "--changeset_54321" + CRLF
-        + "Content-Type: application/http" + CRLF
-        + "Content-Transfer-Encoding: binary" + CRLF
-        + "Content-Id: 2" + CRLF
-        + CRLF
-        + "POST /$1/NavPropertyETTwoPrimMany HTTP/1.1" + CRLF
-        + "Host: http://localhost:8080/odata" + CRLF
-        + "Content-Type: application/json;odata=verbose" + CRLF
-        + CRLF
-        + CRLF
-        + "--changeset_54321" + CRLF
-        + "Content-Type: application/http" + CRLF
-        + "Content-Transfer-Encoding: binary" + CRLF
-        + "Content-Id: 1" + CRLF
-        + CRLF
-        + "PUT ESAllPrim(2) HTTP/1.1" + CRLF
-        + "Content-Type: application/json;odata=verbose" + CRLF
-        + CRLF
-        + CRLF
-        + "--changeset_54321--" + CRLF
-
-        + CRLF
-        + "--batch_12345--";
-    final Map<String, List<String>> header = getMimeHeader();
-    final ODataResponse response = new ODataResponse();
-    final ODataRequest request = buildODataRequest(content, header);
-
-    batchHandler.process(request, response, true);
-
-    BufferedReaderIncludingLineEndings reader =
-        new BufferedReaderIncludingLineEndings(new InputStreamReader(response.getContent()));
-
-    final List<String> responseContent = reader.toList();
-    reader.close();
-
-    int line = 0;
-    assertEquals(49, responseContent.size());
-
-    // Check first change set
-    assertTrue(responseContent.get(line++).contains("--batch_"));
-    assertTrue(responseContent.get(line++).contains("Content-Type: multipart/mixed; boundary=changeset_"));
-
-    for (int i = 0; i < 2; i++) {
-      String contentId = checkChangeSetPartHeader(responseContent, line);
-      line += 6;
-
-      if ("1".equals(contentId)) {
-        assertEquals("HTTP/1.1 200 OK" + CRLF, responseContent.get(line++));
-        assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
-      } else if ("2".equals(contentId)) {
-        assertEquals("HTTP/1.1 201 Created" + CRLF, responseContent.get(line++));
-        assertEquals("Location: " + BASE_URI + "/ESTwoPrim(1)" + CRLF, responseContent.get(line++));
-        assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
-      } else {
-        fail();
-      }
-
-      assertEquals(CRLF, responseContent.get(line++));
-    }
-    // Close body part (1st change set)
-    assertEquals(CRLF, responseContent.get(line++));
-    assertTrue(responseContent.get(line++).contains("--changeset_"));
-
-    // Check second change set
-    assertEquals(CRLF, responseContent.get(line++));
-    assertTrue(responseContent.get(line++).contains("--batch_"));
-    assertTrue(responseContent.get(line++).contains("Content-Type: multipart/mixed; boundary=changeset_"));
-
-    for (int i = 0; i < 2; i++) {
-      String contentId = checkChangeSetPartHeader(responseContent, line);
-      line += 6;
-
-      if ("1".equals(contentId)) {
-        assertEquals("HTTP/1.1 200 OK" + CRLF, responseContent.get(line++));
-        assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
-      } else if ("2".equals(contentId)) {
-        assertEquals("HTTP/1.1 201 Created" + CRLF, responseContent.get(line++));
-        assertEquals("Location: " + BASE_URI + "/ESTwoPrim(2)" + CRLF, responseContent.get(line++));
-        assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
-      } else {
-        fail();
-      }
-
-      assertEquals(CRLF, responseContent.get(line++));
-    }
-    // Close body part (2nd change set)
-    assertEquals(CRLF, responseContent.get(line++));
-    assertTrue(responseContent.get(line++).contains("--changeset_"));
-
-    // Close batch
-    assertEquals(CRLF, responseContent.get(line++));
-    assertTrue(responseContent.get(line++).contains("--batch_"));
-
-    assertEquals(49, line);
-  }
-
-  @Test
-  public void testMineBodyPartTransitiv() throws BatchException, IOException {
-    final String content = ""
-        + "--batch_12345" + CRLF
-        + "Content-Type: multipart/mixed; boundary=changeset_12345" + CRLF
-        + CRLF
-        + "--changeset_12345" + CRLF
-        + "Content-Type: application/http" + CRLF
-        + "Content-Transfer-Encoding: binary" + CRLF
-        + "Content-Id: 4" + CRLF
-        + CRLF
-        + "POST $3/NavPropertyETTwoPrimOne HTTP/1.1" + CRLF
-        + "Content-Type: application/json;odata=verbose" + CRLF
-        + CRLF
-        + CRLF
-        + "--changeset_12345" + CRLF
-        + "Content-Type: application/http" + CRLF
-        + "Content-Transfer-Encoding: binary" + CRLF
-        + "Content-Id: 2" + CRLF
-        + CRLF
-        + "POST /$1/NavPropertyETTwoPrimMany HTTP/1.1" + CRLF
-        + "Host: http://localhost:8080/odata" + CRLF
-        + "Content-Type: application/json;odata=verbose" + CRLF
-        + CRLF
-        + CRLF
-        + "--changeset_12345" + CRLF
-        + "Content-Type: application/http" + CRLF
-        + "Content-Transfer-Encoding: binary" + CRLF
-        + "Content-Id: 1" + CRLF
-        + CRLF
-        + "PUT ESAllPrim(1) HTTP/1.1" + CRLF
-        + "Content-Type: application/json;odata=verbose" + CRLF
-        + CRLF
-        + CRLF
-        + "--changeset_12345" + CRLF
-        + "Content-Type: application/http" + CRLF
-        + "Content-Transfer-Encoding: binary" + CRLF
-        + "Content-Id: 3" + CRLF
-        + CRLF
-        + "POST $2/NavPropertyETAllPrimMany HTTP/1.1" + CRLF
-        + "Content-Type: application/json;odata=verbose" + CRLF
-        + CRLF
-        + CRLF
-        + "--changeset_12345--" + CRLF
-
-        + CRLF
-        + "--batch_12345--";
-
-    final Map<String, List<String>> header = getMimeHeader();
-    final ODataResponse response = new ODataResponse();
-    final ODataRequest request = buildODataRequest(content, header);
-
-    batchHandler.process(request, response, true);
-
-    BufferedReaderIncludingLineEndings reader =
-        new BufferedReaderIncludingLineEndings(new InputStreamReader(response.getContent()));
-
-    final List<String> responseContent = reader.toList();
-    reader.close();
-
-    int line = 0;
-    assertEquals(45, responseContent.size());
-
-    // Check change set
-    assertTrue(responseContent.get(line++).contains("--batch_"));
-    assertTrue(responseContent.get(line++).contains("Content-Type: multipart/mixed; boundary=changeset_"));
-
-    for (int i = 0; i < 4; i++) {
-      String contentId = checkChangeSetPartHeader(responseContent, line);
-      line += 6;
-
-      if ("1".equals(contentId)) {
-        assertEquals("HTTP/1.1 200 OK" + CRLF, responseContent.get(line++));
-        assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
-      } else if ("2".equals(contentId)) {
-        assertEquals("HTTP/1.1 201 Created" + CRLF, responseContent.get(line++));
-        assertEquals("Location: " + BASE_URI + "/ESTwoPrim(1)" + CRLF, responseContent.get(line++));
-        assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
-      } else if ("3".equals(contentId)) {
-        assertEquals("HTTP/1.1 201 Created" + CRLF, responseContent.get(line++));
-        assertEquals("Location: " + BASE_URI + "/ESAllPrim(2)" + CRLF, responseContent.get(line++));
-        assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
-      } else if ("4".equals(contentId)) {
-        assertEquals("HTTP/1.1 201 Created" + CRLF, responseContent.get(line++));
-        assertEquals("Location: " + BASE_URI + "/ESTwoPrim(3)" + CRLF, responseContent.get(line++));
-        assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
-      } else {
-        fail();
-      }
-
-      assertEquals(CRLF, responseContent.get(line++));
-    }
-
-    // Close body part (change set)
-    assertEquals(CRLF, responseContent.get(line++));
-    assertTrue(responseContent.get(line++).contains("--changeset_"));
-
-    // Close batch
-    assertEquals(CRLF, responseContent.get(line++));
-    assertTrue(responseContent.get(line++).contains("--batch_"));
-    assertEquals(45, line);
-  }
-
-  private String checkChangeSetPartHeader(final List<String> response, int line) {
-    assertEquals(CRLF, response.get(line++));
-    assertTrue(response.get(line++).contains("--changeset_"));
-    assertEquals("Content-Type: application/http" + CRLF, response.get(line++));
-    assertEquals("Content-Transfer-Encoding: binary" + CRLF, response.get(line++));
-
-    assertTrue(response.get(line).contains("Content-Id:"));
-    String contentId = response.get(line).split(":")[1].trim();
-    line++;
-
-    assertEquals(CRLF, response.get(line++));
-
-    return contentId;
-  }
-
-  @Test(expected = BatchException.class)
-  public void testInvalidMethod() throws UnsupportedEncodingException, BatchException {
-    final String content = ""
-        + "--batch_12345" + CRLF
-        + "Content-Type: multipart/mixed; boundary=changeset_12345" + CRLF
-        + CRLF
-        + "--changeset_12345" + CRLF
-        + "Content-Type: application/http" + CRLF
-        + "Content-Transfer-Encoding: binary" + CRLF
-        + "Content-Id: 1" + CRLF
-        + CRLF
-        + "PUT ESAllPrim(1) HTTP/1.1" + CRLF
-        + "Content-Type: application/json;odata=verbose" + CRLF
-        + CRLF
-        + CRLF
-        + "--changeset_12345--" + CRLF
-        + CRLF
-        + "--batch_12345--";
-
-    final Map<String, List<String>> header = getMimeHeader();
-    final ODataResponse response = new ODataResponse();
-    final ODataRequest request = buildODataRequest(content, header);
-    request.setMethod(HttpMethod.GET);
-
-    batchHandler.process(request, response, true);
-  }
-
-  @Test(expected = BatchException.class)
-  public void testInvalidContentType() throws UnsupportedEncodingException, BatchException {
-    final String content = ""
-        + "--batch_12345" + CRLF
-        + "Content-Type: multipart/mixed; boundary=changeset_12345" + CRLF
-        + CRLF
-        + "--changeset_12345" + CRLF
-        + "Content-Type: application/http" + CRLF
-        + "Content-Transfer-Encoding: binary" + CRLF
-        + "Content-Id: 1" + CRLF
-        + CRLF
-        + "PUT ESAllPrim(1) HTTP/1.1" + CRLF
-        + "Content-Type: application/json;odata=verbose" + CRLF
-        + CRLF
-        + CRLF
-        + "--changeset_12345--" + CRLF
-        + CRLF
-        + "--batch_12345--";
-
-    final Map<String, List<String>> header = new HashMap<String, List<String>>();
-    header.put(HttpHeader.CONTENT_TYPE, Arrays.asList(new String[] { "application/http" }));
-    final ODataResponse response = new ODataResponse();
-    final ODataRequest request = buildODataRequest(content, header);
-
-    batchHandler.process(request, response, true);
-  }
-
-  /*
-   * Helper methods
-   */
-  private Map<String, List<String>> getMimeHeader() {
-    final Map<String, List<String>> header = new HashMap<String, List<String>>();
-    header.put(HttpHeader.CONTENT_TYPE, Arrays.asList(new String[] { BATCH_CONTENT_TYPE }));
-
-    return header;
-  }
-
-  private ODataRequest buildODataRequest(final String content, final Map<String, List<String>> header)
-      throws UnsupportedEncodingException {
-    final ODataRequest request = new ODataRequest();
-
-    for (final String key : header.keySet()) {
-      request.addHeader(key, header.get(key));
-    }
-
-    request.setMethod(HttpMethod.POST);
-    request.setRawBaseUri(BASE_URI);
-    request.setRawODataPath(BATCH_ODATA_PATH);
-    request.setRawQueryPath("");
-    request.setRawRequestUri(BATCH_REQUEST_URI);
-    request.setRawServiceResolutionUri("");
-
-    request.setBody(new ByteArrayInputStream(content.getBytes("UTF-8")));
-
-    return request;
-  }
-
-  /**
-   * Batch processor
-   */
-  private class BatchTestProcessorImpl implements BatchProcessor {
-    @Override
-    public void init(OData odata, ServiceMetadata serviceMetadata) {}
-
-    @Override
-    public ODataResponsePart executeChangeSet(BatchOperation operation, List<ODataRequest> requests,
-        BatchRequestPart requestPart) {
-      List<ODataResponse> responses = new ArrayList<ODataResponse>();
-
-      for (ODataRequest request : requests) {
-        try {
-          responses.add(operation.handleODataRequest(request, requestPart));
-        } catch (BatchException e) {
-          fail();
-        }
-      }
-
-      return new ODataResponsePart(responses, true);
-    }
-
-    @Override
-    public void executeBatch(BatchOperation operation, ODataRequest request, ODataResponse response) {
-      try {
-        final List<BatchRequestPart> parts = operation.parseBatchRequest(request, true);
-        final List<ODataResponsePart> responseParts = new ArrayList<ODataResponsePart>();
-
-        for (BatchRequestPart part : parts) {
-          for (final ODataRequest oDataRequest : part.getRequests()) {
-            // Mock the processor for a given requests
-            when(oDataHandler.process(oDataRequest)).then(new Answer<ODataResponse>() {
-              @Override
-              public ODataResponse answer(InvocationOnMock invocation) throws Throwable {
-                Object[] arguments = invocation.getArguments();
-
-                return buildResponse((ODataRequest) arguments[0]);
-              }
-            });
-          }
-
-          responseParts.add(operation.handleBatchRequest(part));
-        }
-
-        operation.writeResponseParts(responseParts, response);
-      } catch (BatchException e) {
-        throw new ODataRuntimeException(e);
-      } catch (IOException e) {
-        throw new ODataRuntimeException(e);
-      }
-    }
-  }
-
-  private ODataResponse buildResponse(ODataRequest request) {
-    final ODataResponse oDataResponse = new ODataResponse();
-
-    if (request.getMethod() == HttpMethod.POST) {
-      oDataResponse.setStatusCode(HttpStatusCode.CREATED.getStatusCode());
-      oDataResponse.setHeader(HttpHeader.LOCATION, createResourceUri(request));
-    } else {
-      oDataResponse.setStatusCode(HttpStatusCode.OK.getStatusCode());
-    }
-
-    final String contentId = request.getHeader(BatchParserCommon.HTTP_CONTENT_ID);
-    if (contentId != null) {
-      oDataResponse.setHeader(BatchParserCommon.HTTP_CONTENT_ID, contentId);
-    }
-
-    return oDataResponse;
-  }
-
-  private String createResourceUri(final ODataRequest request) {
-    final String parts[] = request.getRawODataPath().split("/");
-    String oDataPath = "";
-
-    if (parts.length == 2) {
-      // Entity Collection
-      oDataPath = parts[1];
-    } else {
-      // Navigation property
-
-      final String navProperty = parts[parts.length - 1];
-      if (navProperty.equals("NavPropertyETTwoPrimMany")) {
-        oDataPath = "ESTwoPrim";
-      } else if (navProperty.equals("NavPropertyETAllPrimMany")) {
-        oDataPath = "ESAllPrim";
-      } else if (navProperty.equals("NavPropertyETTwoPrimOne")) {
-        oDataPath = "ESTwoPrim";
-      }
-    }
-
-    return BASE_URI + "/" + oDataPath + "(" + entityCounter++ + ")";
-  }
-}


[08/22] olingo-odata4 git commit: MockedBatchHandler Test

Posted by ch...@apache.org.
MockedBatchHandler Test

Signed-off-by: Christian Amend <ch...@apache.org>


Project: http://git-wip-us.apache.org/repos/asf/olingo-odata4/repo
Commit: http://git-wip-us.apache.org/repos/asf/olingo-odata4/commit/0bd32951
Tree: http://git-wip-us.apache.org/repos/asf/olingo-odata4/tree/0bd32951
Diff: http://git-wip-us.apache.org/repos/asf/olingo-odata4/diff/0bd32951

Branch: refs/heads/master
Commit: 0bd32951b8ebcfce895fa5d5eb8fd76954d6bfd3
Parents: ee2451c
Author: Christian Holzer <c....@sap.com>
Authored: Mon Nov 3 17:16:47 2014 +0100
Committer: Christian Amend <ch...@apache.org>
Committed: Thu Nov 13 17:10:56 2014 +0100

----------------------------------------------------------------------
 .../olingo/server/api/batch/BatchOperation.java |   2 +-
 .../server/api/processor/BatchProcessor.java    |   2 +-
 .../batch/handler/BatchChangeSetSorter.java     |  41 +-
 .../core/batch/handler/BatchOperationImpl.java  |   2 +-
 .../core/batch/handler/BatchPartHandler.java    |  56 +-
 .../BatchRequestTransformator.java              |  17 +-
 .../transformator/BatchTransformatorCommon.java | 159 -----
 .../transformator/HttpRequestStatusLine.java    | 229 ++++++++
 .../batch/handler/BatchChangeSetSorterTest.java |   4 +-
 .../batch/handler/MockedBatchHandlerTest.java   | 582 +++++++++++++++++++
 10 files changed, 894 insertions(+), 200 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/0bd32951/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchOperation.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchOperation.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchOperation.java
index af7d9c1..2d09a94 100644
--- a/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchOperation.java
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchOperation.java
@@ -27,7 +27,7 @@ import org.apache.olingo.server.api.ODataResponse;
 public interface BatchOperation {
   public List<BatchRequestPart> parseBatchRequest(InputStream in) throws BatchException;
 
-  public ODataResponse handleODataRequest(ODataRequest request, BatchRequestPart requestPart);
+  public ODataResponse handleODataRequest(ODataRequest request, BatchRequestPart requestPart) throws BatchException;
 
   public ODataResponsePart handleBatchRequest(BatchRequestPart request) throws BatchException;
 

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/0bd32951/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/BatchProcessor.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/BatchProcessor.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/BatchProcessor.java
index 221b38a..a0a7135 100644
--- a/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/BatchProcessor.java
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/BatchProcessor.java
@@ -26,7 +26,7 @@ import org.apache.olingo.server.api.batch.BatchOperation;
 import org.apache.olingo.server.api.batch.BatchRequestPart;
 
 public interface BatchProcessor extends Processor {
-  void executeBatch(BatchOperation operation, ODataRequest requests, ODataResponse response);
+  void executeBatch(BatchOperation operation, ODataRequest request, ODataResponse response);
 
   List<ODataResponse> executeChangeSet(BatchOperation operation, List<ODataRequest> requests,
       BatchRequestPart requestPart);

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/0bd32951/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchChangeSetSorter.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchChangeSetSorter.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchChangeSetSorter.java
index c348512..f8ac653 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchChangeSetSorter.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchChangeSetSorter.java
@@ -39,7 +39,7 @@ public class BatchChangeSetSorter {
 
   private static Pattern referencePattern = Pattern.compile(REG_EX_REFERENCE);
   private Set<String> knownContentId = new HashSet<String>();
-  private Map<String, List<ODataRequest>> requestContentIdMapping = new HashMap<String, List<ODataRequest>>();
+  private Map<String, List<ODataRequest>> requestReferenceMapping = new HashMap<String, List<ODataRequest>>();
 
   public BatchChangeSetSorter(List<ODataRequest> requests) throws BatchException {
     sort(requests);
@@ -50,9 +50,12 @@ public class BatchChangeSetSorter {
   }
 
   private List<ODataRequest> sort(final List<ODataRequest> requests) throws BatchException {
-    extractUrlContentId(requests);
-    orderdList.addAll(getRequestsWithoutReferences());
-
+    extractUrlReference(requests);
+    
+    final List<ODataRequest> requestsWithoutReferences = getRequestsWithoutReferences();
+    orderdList.addAll(requestsWithoutReferences);
+    addRequestsToKnownContentIds(requestsWithoutReferences);
+    
     boolean areRequestsProcessed = true;
     while (requestsToProcessAvailable() && areRequestsProcessed) {
       areRequestsProcessed = processRemainingRequests(orderdList);
@@ -66,7 +69,7 @@ public class BatchChangeSetSorter {
   }
 
   private boolean requestsToProcessAvailable() {
-    return requestContentIdMapping.keySet().size() != 0;
+    return requestReferenceMapping.keySet().size() != 0;
   }
 
   private boolean processRemainingRequests(List<ODataRequest> orderdList) {
@@ -81,10 +84,10 @@ public class BatchChangeSetSorter {
     List<ODataRequest> result = new ArrayList<ODataRequest>();
 
     for (String contextId : knownContentId) {
-      List<ODataRequest> processedRequests = requestContentIdMapping.get(contextId);
+      List<ODataRequest> processedRequests = requestReferenceMapping.get(contextId);
       if (processedRequests != null && processedRequests.size() != 0) {
         result.addAll(processedRequests);
-        requestContentIdMapping.remove(contextId);
+        requestReferenceMapping.remove(contextId);
       }
     }
 
@@ -92,10 +95,9 @@ public class BatchChangeSetSorter {
   }
 
   private List<ODataRequest> getRequestsWithoutReferences() {
-    final List<ODataRequest> requestsWithoutReference = requestContentIdMapping.get(null);
-    requestContentIdMapping.remove(null);
+    final List<ODataRequest> requestsWithoutReference = requestReferenceMapping.get(null);
+    requestReferenceMapping.remove(null);
 
-    addRequestsToKnownContentIds(requestsWithoutReference);
     return requestsWithoutReference;
   }
 
@@ -112,33 +114,38 @@ public class BatchChangeSetSorter {
     return request.getHeader(BatchParserCommon.HTTP_CONTENT_ID);
   }
 
-  private void extractUrlContentId(List<ODataRequest> requests) {
+  private void extractUrlReference(List<ODataRequest> requests) {
     for (ODataRequest request : requests) {
       final String reference = getReferenceInURI(request);
-      addRequestToMapping(reference, request);
+      addRequestToReferenceMapping(reference, request);
     }
   }
 
-  private void addRequestToMapping(final String reference, final ODataRequest request) {
-    List<ODataRequest> requestList = requestContentIdMapping.get(reference);
+  private void addRequestToReferenceMapping(final String reference, final ODataRequest request) {
+    List<ODataRequest> requestList = requestReferenceMapping.get(reference);
     requestList = (requestList == null) ? new ArrayList<ODataRequest>() : requestList;
 
     requestList.add(request);
-    requestContentIdMapping.put(reference, requestList);
+    requestReferenceMapping.put(reference, requestList);
   }
 
   public static String getReferenceInURI(ODataRequest request) {
-    Matcher matcher = referencePattern.matcher(removeFollingPathSegments(request.getRawODataPath()));
+    Matcher matcher = referencePattern.matcher(removeFollingPathSegments(removeFirstSplash(request.getRawODataPath())));
     return (matcher.matches()) ? matcher.group(1) : null;
   }
 
+  private static String removeFirstSplash(String rawODataPath) {
+    final int indexOfSlash = rawODataPath.indexOf("/");
+    return (indexOfSlash == 0) ? rawODataPath.substring(1) : rawODataPath;
+  }
+
   private static String removeFollingPathSegments(String rawODataPath) {
     final int indexOfSlash = rawODataPath.indexOf("/");
     return (indexOfSlash != -1) ? rawODataPath.substring(0, indexOfSlash) : rawODataPath;
   }
 
   public static void replaceContentIdReference(ODataRequest request, String contentId, String resourceUri) {
-    final String newUri = request.getRawODataPath().replace("$" + contentId, resourceUri);
+    final String newUri = request.getRawODataPath().replace("/$" + contentId, resourceUri);
     request.setRawODataPath(newUri);
     request.setRawRequestUri(request.getRawBaseUri() + "/" + newUri);
   }

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/0bd32951/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchOperationImpl.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchOperationImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchOperationImpl.java
index 1f5e243..cf7ad7c 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchOperationImpl.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchOperationImpl.java
@@ -53,7 +53,7 @@ public class BatchOperationImpl implements BatchOperation {
   }
   
   @Override
-  public ODataResponse handleODataRequest(ODataRequest request, BatchRequestPart requestPart) {
+  public ODataResponse handleODataRequest(ODataRequest request, BatchRequestPart requestPart) throws BatchException {
     return partHandler.handleODataRequest(request, requestPart);
   }
   

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/0bd32951/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchPartHandler.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchPartHandler.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchPartHandler.java
index df23c55..c678355 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchPartHandler.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchPartHandler.java
@@ -23,7 +23,9 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
+import org.apache.olingo.commons.api.ODataRuntimeException;
 import org.apache.olingo.commons.api.http.HttpHeader;
+import org.apache.olingo.commons.api.http.HttpMethod;
 import org.apache.olingo.server.api.ODataRequest;
 import org.apache.olingo.server.api.ODataResponse;
 import org.apache.olingo.server.api.batch.BatchException;
@@ -33,6 +35,7 @@ import org.apache.olingo.server.api.batch.ODataResponsePart;
 import org.apache.olingo.server.api.processor.BatchProcessor;
 import org.apache.olingo.server.core.ODataHandler;
 import org.apache.olingo.server.core.batch.parser.BatchParserCommon;
+import org.apache.olingo.server.core.batch.transformator.HttpRequestStatusLine.ODataURI;
 
 public class BatchPartHandler {
 
@@ -48,21 +51,18 @@ public class BatchPartHandler {
     this.batchOperation = batchOperation;
   }
 
-  public ODataResponse handleODataRequest(ODataRequest request, BatchRequestPart requestPart) {
+  public ODataResponse handleODataRequest(ODataRequest request, BatchRequestPart requestPart) throws BatchException {
     final ODataResponse response;
     
     if(requestPart.isChangeSet()) {
-      final UriMapping mapping = getUriMappingOrDefault(requestPart);
-      final String reference = BatchChangeSetSorter.getReferenceInURI(request);
-      if(reference != null) {
-        BatchChangeSetSorter.replaceContentIdReference(request, reference, mapping.getUri(reference));
-      }
-      
-       response = oDataHandler.process(request);
+      final UriMapping mapping = replaceReference(request, requestPart);
+
+      response = oDataHandler.process(request);
        
+      final String resourceUri = getODataPath(request, response);
       final String contentId = request.getHeader(BatchParserCommon.HTTP_CONTENT_ID);
-      final String locationHeader = request.getHeader(HttpHeader.LOCATION);
-      mapping.addMapping(contentId, locationHeader);
+
+      mapping.addMapping(contentId, resourceUri);
     } else {
       response = oDataHandler.process(request);
     }
@@ -74,11 +74,45 @@ public class BatchPartHandler {
     
     return  response;
   }
+
+  private String getODataPath(ODataRequest request, ODataResponse response) throws BatchException {
+    String resourceUri = null;
+    
+    if(request.getMethod() == HttpMethod.POST) {
+      // Create entity
+      // The URI of the new resource will be generated by the server and published in the location header
+      ODataURI uri = new ODataURI(response.getHeaders().get(HttpHeader.LOCATION), request.getRawBaseUri());
+      resourceUri = uri.getRawODataPath();
+    } else {
+      // Update, Upsert (PUT, PATCH, Delete)
+      // These methods still addresses a given URI, so we use the URI given by the request
+      resourceUri = request.getRawODataPath();
+    }
+    
+    return resourceUri;
+  }
+
+  private UriMapping replaceReference(ODataRequest request, BatchRequestPart requestPart) {
+    final UriMapping mapping = getUriMappingOrDefault(requestPart);
+    final String reference = BatchChangeSetSorter.getReferenceInURI(request);
+    
+    if(reference != null) {
+      final String replacement = mapping.getUri(reference);
+      
+      if(replacement != null) {
+        BatchChangeSetSorter.replaceContentIdReference(request, reference, replacement);
+      } else {
+        throw new ODataRuntimeException("Required Content-Id for reference \"" + reference + "\" not found.");
+      }
+    }
+    
+    return mapping;
+  }
   
   private UriMapping getUriMappingOrDefault(final BatchRequestPart requestPart) {
     UriMapping mapping = uriMapping.get(requestPart);
     
-    if(uriMapping == null) {
+    if(mapping == null) {
       mapping = new UriMapping();
     }
     uriMapping.put(requestPart, mapping);

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/0bd32951/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchRequestTransformator.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchRequestTransformator.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchRequestTransformator.java
index 94d5226..0bf0104 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchRequestTransformator.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchRequestTransformator.java
@@ -38,7 +38,7 @@ import org.apache.olingo.server.core.batch.parser.BatchQueryOperation;
 import org.apache.olingo.server.core.batch.parser.BatchRequestPartImpl;
 import org.apache.olingo.server.core.batch.parser.Header;
 import org.apache.olingo.server.core.batch.parser.HeaderField;
-import org.apache.olingo.server.core.batch.transformator.BatchTransformatorCommon.HttpRequestStatusLine;
+import org.apache.olingo.server.core.batch.transformator.HttpRequestStatusLine.ODataURI;
 
 public class BatchRequestTransformator implements BatchTransformator {
   private final String baseUri;
@@ -111,20 +111,21 @@ public class BatchRequestTransformator implements BatchTransformator {
         new HttpRequestStatusLine(operation.getHttpStatusLine(), baseUri, rawServiceResolutionUri, operation
             .getHeaders());
     statusLine.validateHttpMethod(isChangeSet);
-
+    final ODataURI uri = statusLine.getUri();
+    
     validateBody(statusLine, operation);
     InputStream bodyStrean = getBodyStream(operation, statusLine);
 
     validateForbiddenHeader(operation);
-
+    
     final ODataRequest request = new ODataRequest();
     request.setBody(bodyStrean);
     request.setMethod(statusLine.getMethod());
-    request.setRawBaseUri(statusLine.getRawBaseUri());
-    request.setRawODataPath(statusLine.getRawODataPath());
-    request.setRawQueryPath(statusLine.getRawQueryPath());
-    request.setRawRequestUri(statusLine.getRawRequestUri());
-    request.setRawServiceResolutionUri(statusLine.getRawServiceResolutionUri());
+    request.setRawBaseUri(uri.getRawBaseUri());
+    request.setRawODataPath(uri.getRawODataPath());
+    request.setRawQueryPath(uri.getRawQueryPath());
+    request.setRawRequestUri(uri.getRawRequestUri());
+    request.setRawServiceResolutionUri(uri.getRawServiceResolutionUri());
 
     for (final HeaderField field : operation.getHeaders()) {
       request.addHeader(field.getFieldName(), field.getValues());

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/0bd32951/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchTransformatorCommon.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchTransformatorCommon.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchTransformatorCommon.java
index c9da563..fc2035a 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchTransformatorCommon.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchTransformatorCommon.java
@@ -18,20 +18,14 @@
  */
 package org.apache.olingo.server.core.batch.transformator;
 
-import java.util.Arrays;
-import java.util.HashSet;
 import java.util.List;
-import java.util.Set;
-import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 import org.apache.olingo.commons.api.http.HttpContentType;
 import org.apache.olingo.commons.api.http.HttpHeader;
-import org.apache.olingo.commons.api.http.HttpMethod;
 import org.apache.olingo.server.api.batch.BatchException;
 import org.apache.olingo.server.api.batch.BatchException.MessageKeys;
 import org.apache.olingo.server.core.batch.parser.BatchParserCommon;
-import org.apache.olingo.server.core.batch.parser.BufferedReaderIncludingLineEndings.Line;
 import org.apache.olingo.server.core.batch.parser.Header;
 import org.apache.olingo.server.core.batch.parser.HeaderField;
 
@@ -94,157 +88,4 @@ public class BatchTransformatorCommon {
 
     return -1;
   }
-
-  public static class HttpRequestStatusLine {
-    private static final Pattern PATTERN_RELATIVE_URI = Pattern.compile("([^/][^?]*)(?:\\?(.*))?");
-    private static final Pattern PATTERN_ABSOLUTE_URI_WITH_HOST = Pattern.compile("(/[^?]*)(?:\\?(.*))?");
-    private static final Pattern PATTERN_ABSOLUTE_URI = Pattern.compile("(http[s]?://[^?]*)(?:\\?(.*))?");
-
-    private static final Set<String> HTTP_BATCH_METHODS = new HashSet<String>(Arrays.asList(new String[] { "GET" }));
-    private static final Set<String> HTTP_CHANGE_SET_METHODS = new HashSet<String>(Arrays.asList(new String[] { "POST",
-        "PUT", "DELETE", "MERGE", "PATCH" }));
-    private static final String HTTP_VERSION = "HTTP/1.1";
-
-    final private Line statusLine;
-    final String requestBaseUri;
-
-    private HttpMethod method;
-    private String httpVersion;
-    private String rawServiceResolutionUri;
-    private String rawQueryPath;
-    private String rawODataPath;
-    private String rawBaseUri;
-    private Header header;
-    private String rawRequestUri;
-
-    public HttpRequestStatusLine(final Line httpStatusLine, final String baseUri, final String serviceResolutionUri,
-        final Header requestHeader)
-        throws BatchException {
-      statusLine = httpStatusLine;
-      requestBaseUri = baseUri;
-      header = requestHeader;
-      rawServiceResolutionUri = serviceResolutionUri;
-      
-      parse();
-    }
-
-    private void parse() throws BatchException {
-      final String[] parts = statusLine.toString().split(" ");
-
-      if (parts.length == 3) {
-        method = parseMethod(parts[0]);
-        parseRawPaths(parts[1]);
-        httpVersion = parseHttpVersion(parts[2]);
-      } else {
-        throw new BatchException("Invalid status line", MessageKeys.INVALID_STATUS_LINE, statusLine.getLineNumber());
-      }
-    }
-
-    private HttpMethod parseMethod(final String method) throws BatchException {
-      try {
-        return HttpMethod.valueOf(method.trim());
-      } catch (IllegalArgumentException e) {
-        throw new BatchException("Illegal http method", MessageKeys.INVALID_METHOD, statusLine.getLineNumber());
-      }
-    }
-
-    private void parseRawPaths(final String rawUrl) throws BatchException {
-      final Matcher absoluteUriMatcher = PATTERN_ABSOLUTE_URI.matcher(rawUrl);
-      final Matcher absoluteUriWtithHostMatcher = PATTERN_ABSOLUTE_URI_WITH_HOST.matcher(rawUrl);
-      final Matcher relativeUriMatcher = PATTERN_RELATIVE_URI.matcher(rawUrl);
-
-      if (absoluteUriMatcher.matches()) {
-        buildUri(absoluteUriMatcher.group(1), absoluteUriMatcher.group(2));
-
-      } else if (absoluteUriWtithHostMatcher.matches()) {
-        final List<String> hostHeader = header.getHeaders(HttpHeader.HOST);
-        if (hostHeader.size() == 1) {
-          buildUri(hostHeader.get(0) + absoluteUriWtithHostMatcher.group(1), absoluteUriWtithHostMatcher.group(2));
-        } else {
-          throw new BatchException("Exactly one host header is required", MessageKeys.MISSING_MANDATORY_HEADER,
-              statusLine.getLineNumber());
-        }
-
-      } else if (relativeUriMatcher.matches()) {
-        buildUri(requestBaseUri + "/" + relativeUriMatcher.group(1), relativeUriMatcher.group(2));
-
-      } else {
-        throw new BatchException("Invalid uri", MessageKeys.INVALID_URI, statusLine.getLineNumber());
-      }
-    }
-
-    private void buildUri(final String resourceUri, final String queryOptions) throws BatchException {
-      if(!resourceUri.startsWith(requestBaseUri)) {
-        throw new BatchException("Host do not match", MessageKeys.INVALID_URI, statusLine.getLineNumber());
-      }
-      
-      final int oDataPathIndex = resourceUri.indexOf(requestBaseUri);
-
-      rawBaseUri = requestBaseUri;
-      rawODataPath = resourceUri.substring(oDataPathIndex + requestBaseUri.length());
-      rawServiceResolutionUri = "";
-      rawRequestUri = requestBaseUri + rawODataPath;
-
-      if (queryOptions != null) {
-        rawRequestUri += "?" + queryOptions;
-        rawQueryPath = queryOptions;
-      } else {
-        rawQueryPath = "";
-      }
-    }
-
-    private String parseHttpVersion(final String httpVersion) throws BatchException {
-      if (!HTTP_VERSION.equals(httpVersion.trim())) {
-        throw new BatchException("Invalid http version", MessageKeys.INVALID_HTTP_VERSION, statusLine.getLineNumber());
-      } else {
-        return HTTP_VERSION;
-      }
-    }
-
-    public void validateHttpMethod(boolean isChangeSet) throws BatchException {
-      Set<String> validMethods = (isChangeSet) ? HTTP_CHANGE_SET_METHODS : HTTP_BATCH_METHODS;
-
-      if (!validMethods.contains(getMethod().toString())) {
-        if (isChangeSet) {
-          throw new BatchException("Invalid change set method", MessageKeys.INVALID_CHANGESET_METHOD, statusLine
-              .getLineNumber());
-        } else {
-          throw new BatchException("Invalid query operation method", MessageKeys.INVALID_QUERY_OPERATION_METHOD,
-              statusLine.getLineNumber());
-        }
-      }
-    }
-
-    public HttpMethod getMethod() {
-      return method;
-    }
-
-    public String getHttpVersion() {
-      return httpVersion;
-    }
-
-    public int getLineNumber() {
-      return statusLine.getLineNumber();
-    }
-
-    public String getRawBaseUri() {
-      return rawBaseUri;
-    }
-
-    public String getRawODataPath() {
-      return rawODataPath;
-    }
-
-    public String getRawQueryPath() {
-      return rawQueryPath;
-    }
-
-    public String getRawRequestUri() {
-      return rawRequestUri;
-    }
-
-    public String getRawServiceResolutionUri() {
-      return rawServiceResolutionUri;
-    }
-  }
 }

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/0bd32951/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/HttpRequestStatusLine.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/HttpRequestStatusLine.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/HttpRequestStatusLine.java
new file mode 100644
index 0000000..416d593
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/HttpRequestStatusLine.java
@@ -0,0 +1,229 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.batch.transformator;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.olingo.commons.api.http.HttpHeader;
+import org.apache.olingo.commons.api.http.HttpMethod;
+import org.apache.olingo.server.api.batch.BatchException;
+import org.apache.olingo.server.api.batch.BatchException.MessageKeys;
+import org.apache.olingo.server.core.batch.parser.Header;
+import org.apache.olingo.server.core.batch.parser.BufferedReaderIncludingLineEndings.Line;
+
+public class HttpRequestStatusLine {
+  private static final Pattern PATTERN_RELATIVE_URI = Pattern.compile("([^/][^?]*)(?:\\?(.*))?");
+  private static final Pattern PATTERN_ABSOLUTE_URI_WITH_HOST = Pattern.compile("(/[^?]*)(?:\\?(.*))?");
+  private static final Pattern PATTERN_ABSOLUTE_URI = Pattern.compile("(http[s]?://[^?]*)(?:\\?(.*))?");
+
+  private static final Set<String> HTTP_BATCH_METHODS = new HashSet<String>(Arrays.asList(new String[] { "GET" }));
+  private static final Set<String> HTTP_CHANGE_SET_METHODS = new HashSet<String>(Arrays.asList(new String[] { "POST",
+      "PUT", "DELETE", "MERGE", "PATCH" }));
+  // TODO Is Merge still supported?
+  // What`s New in OData 4: 2.7.2 Pruned: MERGE
+  // MERGE was used to do PATCH before PATCH existed. Now that we have PATCH, we no longer need MERGE. => No
+  private static final String HTTP_VERSION = "HTTP/1.1";
+
+  final private Line statusLine;
+  final String requestBaseUri;
+
+  private HttpMethod method;
+  private String httpVersion;
+  private Header header;
+  private ODataURI uri;
+
+  public HttpRequestStatusLine(final Line httpStatusLine, final String baseUri, final String serviceResolutionUri,
+      final Header requestHeader)
+      throws BatchException {
+    statusLine = httpStatusLine;
+    requestBaseUri = baseUri;
+    header = requestHeader;
+
+    parse();
+  }
+
+  private void parse() throws BatchException {
+    final String[] parts = statusLine.toString().split(" ");
+
+    if (parts.length == 3) {
+      method = parseMethod(parts[0]);
+      uri = new ODataURI(parts[1], requestBaseUri, statusLine.getLineNumber(), header.getHeaders(HttpHeader.HOST));
+      httpVersion = parseHttpVersion(parts[2]);
+    } else {
+      throw new BatchException("Invalid status line", MessageKeys.INVALID_STATUS_LINE, statusLine.getLineNumber());
+    }
+  }
+
+  private HttpMethod parseMethod(final String method) throws BatchException {
+    try {
+      return HttpMethod.valueOf(method.trim());
+    } catch (IllegalArgumentException e) {
+      throw new BatchException("Illegal http method", MessageKeys.INVALID_METHOD, statusLine.getLineNumber());
+    }
+  }
+
+  private String parseHttpVersion(final String httpVersion) throws BatchException {
+    if (!HTTP_VERSION.equals(httpVersion.trim())) {
+      throw new BatchException("Invalid http version", MessageKeys.INVALID_HTTP_VERSION, statusLine.getLineNumber());
+    } else {
+      return HTTP_VERSION;
+    }
+  }
+
+  public void validateHttpMethod(boolean isChangeSet) throws BatchException {
+    Set<String> validMethods = (isChangeSet) ? HTTP_CHANGE_SET_METHODS : HTTP_BATCH_METHODS;
+
+    if (!validMethods.contains(getMethod().toString())) {
+      if (isChangeSet) {
+        throw new BatchException("Invalid change set method", MessageKeys.INVALID_CHANGESET_METHOD, statusLine
+            .getLineNumber());
+      } else {
+        throw new BatchException("Invalid query operation method", MessageKeys.INVALID_QUERY_OPERATION_METHOD,
+            statusLine.getLineNumber());
+      }
+    }
+  }
+
+  public HttpMethod getMethod() {
+    return method;
+  }
+
+  public String getHttpVersion() {
+    return httpVersion;
+  }
+
+  public int getLineNumber() {
+    return statusLine.getLineNumber();
+  }
+
+  public ODataURI getUri() {
+    return uri;
+  }
+
+  public static class ODataURI {
+    private String rawServiceResolutionUri;
+    private String rawQueryPath;
+    private String rawODataPath;
+    private String rawBaseUri;
+    private String rawRequestUri;
+    private final String requestBaseUri;
+    private final int lineNumber;
+
+    public ODataURI(final String rawUri, String requestBaseUri) throws BatchException {
+      this(rawUri, requestBaseUri, 0, new ArrayList<String>());
+    }
+
+    public ODataURI(final String rawUri, String requestBaseUri, int lineNumber, List<String> hostHeader)
+        throws BatchException {
+      this.lineNumber = lineNumber;
+      this.requestBaseUri = requestBaseUri;
+
+      final Matcher absoluteUriMatcher = PATTERN_ABSOLUTE_URI.matcher(rawUri);
+      final Matcher absoluteUriWtithHostMatcher = PATTERN_ABSOLUTE_URI_WITH_HOST.matcher(rawUri);
+      final Matcher relativeUriMatcher = PATTERN_RELATIVE_URI.matcher(rawUri);
+
+      if (absoluteUriMatcher.matches()) {
+        buildUri(absoluteUriMatcher.group(1), absoluteUriMatcher.group(2));
+
+      } else if (absoluteUriWtithHostMatcher.matches()) {
+        if (hostHeader != null && hostHeader.size() == 1) {
+          buildUri(hostHeader.get(0) + absoluteUriWtithHostMatcher.group(1), absoluteUriWtithHostMatcher.group(2));
+        } else {
+          throw new BatchException("Exactly one host header is required", MessageKeys.MISSING_MANDATORY_HEADER,
+              lineNumber);
+        }
+
+      } else if (relativeUriMatcher.matches()) {
+        buildUri(requestBaseUri + "/" + relativeUriMatcher.group(1), relativeUriMatcher.group(2));
+
+      } else {
+        throw new BatchException("Invalid uri", MessageKeys.INVALID_URI, lineNumber);
+      }
+    }
+
+    private void buildUri(final String resourceUri, final String queryOptions) throws BatchException {
+      if (!resourceUri.startsWith(requestBaseUri)) {
+        throw new BatchException("Host do not match", MessageKeys.INVALID_URI, lineNumber);
+      }
+
+      final int oDataPathIndex = resourceUri.indexOf(requestBaseUri);
+
+      rawBaseUri = requestBaseUri;
+      rawODataPath = resourceUri.substring(oDataPathIndex + requestBaseUri.length());
+      rawRequestUri = requestBaseUri + rawODataPath;
+
+      if (queryOptions != null) {
+        rawRequestUri += "?" + queryOptions;
+        rawQueryPath = queryOptions;
+      } else {
+        rawQueryPath = "";
+      }
+    }
+
+    public String getRawServiceResolutionUri() {
+      return rawServiceResolutionUri;
+    }
+
+    public void setRawServiceResolutionUri(String rawServiceResolutionUri) {
+      this.rawServiceResolutionUri = rawServiceResolutionUri;
+    }
+
+    public String getRawQueryPath() {
+      return rawQueryPath;
+    }
+
+    public void setRawQueryPath(String rawQueryPath) {
+      this.rawQueryPath = rawQueryPath;
+    }
+
+    public String getRawODataPath() {
+      return rawODataPath;
+    }
+
+    public void setRawODataPath(String rawODataPath) {
+      this.rawODataPath = rawODataPath;
+    }
+
+    public String getRawBaseUri() {
+      return rawBaseUri;
+    }
+
+    public void setRawBaseUri(String rawBaseUri) {
+      this.rawBaseUri = rawBaseUri;
+    }
+
+    public String getRawRequestUri() {
+      return rawRequestUri;
+    }
+
+    public void setRawRequestUri(String rawRequestUri) {
+      this.rawRequestUri = rawRequestUri;
+    }
+
+    public String getRequestBaseUri() {
+      return requestBaseUri;
+    }
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/0bd32951/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/BatchChangeSetSorterTest.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/BatchChangeSetSorterTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/BatchChangeSetSorterTest.java
index 9196514..c8b725a 100644
--- a/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/BatchChangeSetSorterTest.java
+++ b/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/BatchChangeSetSorterTest.java
@@ -39,7 +39,7 @@ public class BatchChangeSetSorterTest {
   public void test() throws BatchException {
     final List<ODataRequest> changeSet = new ArrayList<ODataRequest>();
     changeSet.add(createRequest(HttpMethod.POST, BASE_URI, "$1/Adress", "2"));
-    changeSet.add(createRequest(HttpMethod.POST, BASE_URI, "Employees", "1"));
+    changeSet.add(createRequest(HttpMethod.POST, BASE_URI, "/Employees", "1"));
     
    BatchChangeSetSorter sorter = new BatchChangeSetSorter(changeSet);
    final List<ODataRequest> sortedChangeSet = sorter.getOrderdRequests();
@@ -110,7 +110,7 @@ public class BatchChangeSetSorterTest {
   @Test
   public void testRewriting() {
     final String CONTENT_ID = "1";
-    final String ODATA_PATH ="$" + CONTENT_ID + "/Address";
+    final String ODATA_PATH ="/$" + CONTENT_ID + "/Address";
     final String RESOURCE_URI = "Employee('1')";
     final ODataRequest request = createRequest(HttpMethod.POST, BASE_URI, ODATA_PATH);
     

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/0bd32951/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/MockedBatchHandlerTest.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/MockedBatchHandlerTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/MockedBatchHandlerTest.java
new file mode 100644
index 0000000..ac111e0
--- /dev/null
+++ b/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/MockedBatchHandlerTest.java
@@ -0,0 +1,582 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.batch.handler;
+
+import static org.junit.Assert.*;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.olingo.commons.api.ODataRuntimeException;
+import org.apache.olingo.commons.api.http.HttpHeader;
+import org.apache.olingo.commons.api.http.HttpMethod;
+import org.apache.olingo.commons.api.http.HttpStatusCode;
+import org.apache.olingo.server.api.OData;
+import org.apache.olingo.server.api.ODataRequest;
+import org.apache.olingo.server.api.ODataResponse;
+import org.apache.olingo.server.api.ServiceMetadata;
+import org.apache.olingo.server.api.batch.BatchException;
+import org.apache.olingo.server.api.batch.BatchOperation;
+import org.apache.olingo.server.api.batch.BatchRequestPart;
+import org.apache.olingo.server.api.batch.ODataResponsePart;
+import org.apache.olingo.server.api.processor.BatchProcessor;
+import org.apache.olingo.server.core.ODataHandler;
+import org.apache.olingo.server.core.batch.parser.BatchParserCommon;
+import org.apache.olingo.server.core.batch.parser.BufferedReaderIncludingLineEndings;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class MockedBatchHandlerTest {
+
+  private static final String BATCH_CONTENT_TYPE = "multipart/mixed;boundary=batch_12345";
+  private static final String BATCH_ODATA_PATH = "/$batch";
+  private static final String BATCH_REQUEST_URI = "http://localhost:8080/odata/$batch";
+  private static final String BASE_URI = "http://localhost:8080/odata";
+  private static final String CRLF = "\r\n";
+  private ODataHandler handler;
+  private int entityCounter = 1;
+
+  @Test
+  public void test() throws BatchException, IOException {
+    final String content = "--batch_12345" + CRLF
+        + "Content-Type: multipart/mixed; boundary=changeset_12345" + CRLF
+        + CRLF
+        + "--changeset_12345" + CRLF
+        + "Content-Type: application/http" + CRLF
+        + "Content-Transfer-Encoding: binary" + CRLF
+        + "Content-Id: 4" + CRLF
+        + CRLF
+        + "PUT /$3/PropertyInt32 HTTP/1.1" + CRLF // Absolute URI with separate Host header and ref.
+        + "Host: http://localhost:8080/odata" + CRLF
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + CRLF
+        + CRLF
+        + "--changeset_12345" + CRLF
+        + "Content-Type: application/http" + CRLF
+        + "Content-Transfer-Encoding: binary" + CRLF
+        + "Content-Id: 5" + CRLF
+        + CRLF
+        + "POST http://localhost:8080/odata/$1/NavPropertyETTwoPrimMany HTTP/1.1" + CRLF // Absolute URI with ref.
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + CRLF
+        + CRLF
+        + "--changeset_12345" + CRLF
+        + "Content-Type: application/http" + CRLF
+        + "Content-Transfer-Encoding: binary" + CRLF
+        + "Content-Id: 2" + CRLF
+        + CRLF
+        + "POST $1/NavPropertyETTwoPrimMany HTTP/1.1" + CRLF // Relative URI with ref.
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + CRLF
+        + CRLF
+        + "--changeset_12345" + CRLF
+        + "Content-Type: application/http" + CRLF
+        + "Content-Transfer-Encoding: binary" + CRLF
+        + "Content-Id: 1" + CRLF
+        + CRLF
+        + "POST http://localhost:8080/odata/ESAllPrim HTTP/1.1" + CRLF // Absolute URI
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + CRLF
+        + CRLF
+        + "--changeset_12345" + CRLF
+        + "Content-Type: application/http" + CRLF
+        + "Content-Transfer-Encoding: binary" + CRLF
+        + "Content-Id: 3" + CRLF
+        + CRLF
+        + "PUT ESAllPrim(1) HTTP/1.1" + CRLF // Relative URI
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + CRLF
+        + CRLF
+        + "--changeset_12345" + CRLF
+        + "Content-Type: application/http" + CRLF
+        + "Content-Transfer-Encoding: binary" + CRLF
+        + "Content-Id: 6" + CRLF
+        + CRLF
+        + "PUT /ESAllPrim(1) HTTP/1.1" + CRLF // Absolute URI with separate Host header
+        + "Host: http://localhost:8080/odata"
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + CRLF
+        + CRLF
+        + "--changeset_12345--" + CRLF
+        + CRLF
+        + "--batch_12345--";
+    final Map<String, List<String>> header = getMimeHeader();
+    final ODataResponse response = new ODataResponse();
+    final BatchHandler batchHandler = buildBatchHandler(content, header);
+
+    batchHandler.process(response);
+
+    BufferedReaderIncludingLineEndings reader =
+        new BufferedReaderIncludingLineEndings(new InputStreamReader(response.getContent()));
+
+    final List<String> responseContent = reader.toList();
+    reader.close();
+
+    int line = 0;
+    assertEquals(63, responseContent.size());
+
+    // Check change set
+    assertTrue(responseContent.get(line++).contains("--batch_"));
+    assertTrue(responseContent.get(line++).contains("Content-Type: multipart/mixed; boundary=changeset_"));
+
+    for (int i = 0; i < 6; i++) {
+      String contentId = checkChangeSetPartHeader(responseContent, line);
+      line += 6;
+
+      if ("1".equals(contentId)) {
+        assertEquals("HTTP/1.1 201 Created" + CRLF, responseContent.get(line++));
+        assertEquals("Location: " + BASE_URI + "/ESAllPrim(1)" + CRLF, responseContent.get(line++));
+        assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
+      } else if ("2".equals(contentId)) {
+        assertEquals("HTTP/1.1 201 Created" + CRLF, responseContent.get(line++));
+        assertEquals("Location: " + BASE_URI + "/ESTwoPrim(3)" + CRLF, responseContent.get(line++));
+        assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
+      } else if ("3".equals(contentId)) {
+        assertEquals("HTTP/1.1 200 OK" + CRLF, responseContent.get(line++));
+        assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
+      } else if ("4".equals(contentId)) {
+        assertEquals("HTTP/1.1 200 OK" + CRLF, responseContent.get(line++));
+        assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
+      } else if ("5".equals(contentId)) {
+        assertEquals("HTTP/1.1 201 Created" + CRLF, responseContent.get(line++));
+        assertEquals("Location: " + BASE_URI + "/ESTwoPrim(2)" + CRLF, responseContent.get(line++));
+        assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
+      } else if ("6".equals(contentId)) {
+        assertEquals("HTTP/1.1 200 OK" + CRLF, responseContent.get(line++));
+        assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
+      } else {
+        fail();
+      }
+
+      assertEquals(CRLF, responseContent.get(line++));
+    }
+
+    // Close body part (change set)
+    assertEquals(CRLF, responseContent.get(line++));
+    assertTrue(responseContent.get(line++).contains("--changeset_"));
+
+    // Close batch
+    assertEquals(CRLF, responseContent.get(line++));
+    assertTrue(responseContent.get(line++).contains("--batch_"));
+    assertEquals(63, line);
+  }
+
+  @Test
+  public void testMultipleChangeSets() throws BatchException, IOException {
+    final String content = ""
+        + "--batch_12345" + CRLF
+        + "Content-Type: multipart/mixed; boundary=changeset_12345" + CRLF
+        + CRLF
+        + "--changeset_12345" + CRLF
+        + "Content-Type: application/http" + CRLF
+        + "Content-Transfer-Encoding: binary" + CRLF
+        + "Content-Id: 2" + CRLF
+        + CRLF
+        + "POST /$1/NavPropertyETTwoPrimMany HTTP/1.1" + CRLF
+        + "Host: http://localhost:8080/odata" + CRLF
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + CRLF
+        + CRLF
+        + "--changeset_12345" + CRLF
+        + "Content-Type: application/http" + CRLF
+        + "Content-Transfer-Encoding: binary" + CRLF
+        + "Content-Id: 1" + CRLF
+        + CRLF
+        + "PUT ESAllPrim(1) HTTP/1.1" + CRLF
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + CRLF
+        + CRLF
+        + "--changeset_12345--" + CRLF
+
+        + "--batch_12345" + CRLF
+        + "Content-Type: multipart/mixed; boundary=changeset_54321" + CRLF
+        + CRLF
+        + "--changeset_54321" + CRLF
+        + "Content-Type: application/http" + CRLF
+        + "Content-Transfer-Encoding: binary" + CRLF
+        + "Content-Id: 2" + CRLF
+        + CRLF
+        + "POST /$1/NavPropertyETTwoPrimMany HTTP/1.1" + CRLF
+        + "Host: http://localhost:8080/odata" + CRLF
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + CRLF
+        + CRLF
+        + "--changeset_54321" + CRLF
+        + "Content-Type: application/http" + CRLF
+        + "Content-Transfer-Encoding: binary" + CRLF
+        + "Content-Id: 1" + CRLF
+        + CRLF
+        + "PUT ESAllPrim(2) HTTP/1.1" + CRLF
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + CRLF
+        + CRLF
+        + "--changeset_54321--" + CRLF
+
+        + CRLF
+        + "--batch_12345--";
+    final Map<String, List<String>> header = getMimeHeader();
+    final ODataResponse response = new ODataResponse();
+    final BatchHandler batchHandler = buildBatchHandler(content, header);
+
+    batchHandler.process(response);
+
+    BufferedReaderIncludingLineEndings reader =
+        new BufferedReaderIncludingLineEndings(new InputStreamReader(response.getContent()));
+
+    final List<String> responseContent = reader.toList();
+    reader.close();
+
+    int line = 0;
+    assertEquals(49, responseContent.size());
+
+    // Check first change set
+    assertTrue(responseContent.get(line++).contains("--batch_"));
+    assertTrue(responseContent.get(line++).contains("Content-Type: multipart/mixed; boundary=changeset_"));
+
+    for (int i = 0; i < 2; i++) {
+      String contentId = checkChangeSetPartHeader(responseContent, line);
+      line += 6;
+
+      if ("1".equals(contentId)) {
+        assertEquals("HTTP/1.1 200 OK" + CRLF, responseContent.get(line++));
+        assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
+      } else if ("2".equals(contentId)) {
+        assertEquals("HTTP/1.1 201 Created" + CRLF, responseContent.get(line++));
+        assertEquals("Location: " + BASE_URI + "/ESTwoPrim(1)" + CRLF, responseContent.get(line++));
+        assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
+      } else {
+        fail();
+      }
+
+      assertEquals(CRLF, responseContent.get(line++));
+    }
+    // Close body part (1st change set)
+    assertEquals(CRLF, responseContent.get(line++));
+    assertTrue(responseContent.get(line++).contains("--changeset_"));
+
+    // Check second change set
+    assertEquals(CRLF, responseContent.get(line++));
+    assertTrue(responseContent.get(line++).contains("--batch_"));
+    assertTrue(responseContent.get(line++).contains("Content-Type: multipart/mixed; boundary=changeset_"));
+
+    for (int i = 0; i < 2; i++) {
+      String contentId = checkChangeSetPartHeader(responseContent, line);
+      line += 6;
+
+      if ("1".equals(contentId)) {
+        assertEquals("HTTP/1.1 200 OK" + CRLF, responseContent.get(line++));
+        assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
+      } else if ("2".equals(contentId)) {
+        assertEquals("HTTP/1.1 201 Created" + CRLF, responseContent.get(line++));
+        assertEquals("Location: " + BASE_URI + "/ESTwoPrim(2)" + CRLF, responseContent.get(line++));
+        assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
+      } else {
+        fail();
+      }
+
+      assertEquals(CRLF, responseContent.get(line++));
+    }
+    // Close body part (2nd change set)
+    assertEquals(CRLF, responseContent.get(line++));
+    assertTrue(responseContent.get(line++).contains("--changeset_"));
+
+    // Close batch
+    assertEquals(CRLF, responseContent.get(line++));
+    assertTrue(responseContent.get(line++).contains("--batch_"));
+
+    assertEquals(49, line);
+  }
+
+  @Test
+  public void testMineBodyPartTransitiv() throws BatchException, IOException {
+    final String content = ""
+        + "--batch_12345" + CRLF
+        + "Content-Type: multipart/mixed; boundary=changeset_12345" + CRLF
+        + CRLF
+        + "--changeset_12345" + CRLF
+        + "Content-Type: application/http" + CRLF
+        + "Content-Transfer-Encoding: binary" + CRLF
+        + "Content-Id: 4" + CRLF
+        + CRLF
+        + "POST $3/NavPropertyETTwoPrimOne HTTP/1.1" + CRLF
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + CRLF
+        + CRLF
+        + "--changeset_12345" + CRLF
+        + "Content-Type: application/http" + CRLF
+        + "Content-Transfer-Encoding: binary" + CRLF
+        + "Content-Id: 2" + CRLF
+        + CRLF
+        + "POST /$1/NavPropertyETTwoPrimMany HTTP/1.1" + CRLF
+        + "Host: http://localhost:8080/odata" + CRLF
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + CRLF
+        + CRLF
+        + "--changeset_12345" + CRLF
+        + "Content-Type: application/http" + CRLF
+        + "Content-Transfer-Encoding: binary" + CRLF
+        + "Content-Id: 1" + CRLF
+        + CRLF
+        + "PUT ESAllPrim(1) HTTP/1.1" + CRLF
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + CRLF
+        + CRLF
+        + "--changeset_12345" + CRLF
+        + "Content-Type: application/http" + CRLF
+        + "Content-Transfer-Encoding: binary" + CRLF
+        + "Content-Id: 3" + CRLF
+        + CRLF
+        + "POST $2/NavPropertyETAllPrimMany HTTP/1.1" + CRLF
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + CRLF
+        + CRLF
+        + "--changeset_12345--" + CRLF
+
+        + CRLF
+        + "--batch_12345--";
+
+    final Map<String, List<String>> header = getMimeHeader();
+    final ODataResponse response = new ODataResponse();
+    final BatchHandler batchHandler = buildBatchHandler(content, header);
+
+    batchHandler.process(response);
+
+    BufferedReaderIncludingLineEndings reader =
+        new BufferedReaderIncludingLineEndings(new InputStreamReader(response.getContent()));
+
+    final List<String> responseContent = reader.toList();
+    reader.close();
+
+    int line = 0;
+    assertEquals(45, responseContent.size());
+
+    // Check change set
+    assertTrue(responseContent.get(line++).contains("--batch_"));
+    assertTrue(responseContent.get(line++).contains("Content-Type: multipart/mixed; boundary=changeset_"));
+
+    for (int i = 0; i < 4; i++) {
+      String contentId = checkChangeSetPartHeader(responseContent, line);
+      line += 6;
+
+      if ("1".equals(contentId)) {
+        assertEquals("HTTP/1.1 200 OK" + CRLF, responseContent.get(line++));
+        assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
+      } else if ("2".equals(contentId)) {
+        assertEquals("HTTP/1.1 201 Created" + CRLF, responseContent.get(line++));
+        assertEquals("Location: " + BASE_URI + "/ESTwoPrim(1)" + CRLF, responseContent.get(line++));
+        assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
+      } else if ("3".equals(contentId)) {
+        assertEquals("HTTP/1.1 201 Created" + CRLF, responseContent.get(line++));
+        assertEquals("Location: " + BASE_URI + "/ESAllPrim(2)" + CRLF, responseContent.get(line++));
+        assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
+      } else if ("4".equals(contentId)) {
+        assertEquals("HTTP/1.1 201 Created" + CRLF, responseContent.get(line++));
+        assertEquals("Location: " + BASE_URI + "/ESTwoPrim(3)" + CRLF, responseContent.get(line++));
+        assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
+      } else {
+        fail();
+      }
+
+      assertEquals(CRLF, responseContent.get(line++));
+    }
+
+    // Close body part (change set)
+    assertEquals(CRLF, responseContent.get(line++));
+    assertTrue(responseContent.get(line++).contains("--changeset_"));
+
+    // Close batch
+    assertEquals(CRLF, responseContent.get(line++));
+    assertTrue(responseContent.get(line++).contains("--batch_"));
+    assertEquals(45, line);
+  }
+
+  @Before
+  public void setup() {
+    handler = null;
+    entityCounter = 1;
+  }
+
+  private String checkChangeSetPartHeader(final List<String> response, int line) {
+    assertEquals(CRLF, response.get(line++));
+    assertTrue(response.get(line++).contains("--changeset_"));
+    assertEquals("Content-Type: application/http" + CRLF, response.get(line++));
+    assertEquals("Content-Transfer-Encoding: binary" + CRLF, response.get(line++));
+
+    assertTrue(response.get(line).contains("Content-Id:"));
+    String contentId = response.get(line).split(":")[1].trim();
+    line++;
+
+    assertEquals(CRLF, response.get(line++));
+
+    return contentId;
+  }
+
+  /*
+   * Helper methods
+   */
+
+  private Map<String, List<String>> getMimeHeader() {
+    final Map<String, List<String>> header = new HashMap<String, List<String>>();
+    header.put(HttpHeader.CONTENT_TYPE, Arrays.asList(new String[] { BATCH_CONTENT_TYPE }));
+
+    return header;
+  }
+
+  private BatchHandler buildBatchHandler(final String content, Map<String, List<String>> header) throws BatchException,
+      UnsupportedEncodingException {
+
+    final ODataRequest request = buildODataRequest(content, header);
+    final ODataHandler oDataHandler = buildODataHandler(request);
+    final BatchProcessor batchProcessor = new BatchProcessorImpl();
+
+    return new BatchHandler(oDataHandler, request, batchProcessor, true);
+  }
+
+  private ODataHandler buildODataHandler(ODataRequest request) {
+    handler = mock(ODataHandler.class);
+    when(handler.process(request)).thenCallRealMethod();
+
+    return handler;
+  }
+
+  private ODataRequest buildODataRequest(String content, Map<String, List<String>> header)
+      throws UnsupportedEncodingException {
+    final ODataRequest request = new ODataRequest();
+
+    for (final String key : header.keySet()) {
+      request.addHeader(key, header.get(key));
+    }
+
+    request.setMethod(HttpMethod.POST);
+    request.setRawBaseUri(BASE_URI);
+    request.setRawODataPath(BATCH_ODATA_PATH);
+    request.setRawQueryPath("");
+    request.setRawRequestUri(BATCH_REQUEST_URI);
+    request.setRawServiceResolutionUri("");
+
+    request.setBody(new ByteArrayInputStream(content.getBytes("UTF-8")));
+
+    return request;
+  }
+
+  /**
+   * Batch processor
+   */
+  private class BatchProcessorImpl implements BatchProcessor {
+    @Override
+    public void init(OData odata, ServiceMetadata serviceMetadata) {}
+
+    @Override
+    public List<ODataResponse> executeChangeSet(BatchOperation operation, List<ODataRequest> requests,
+        BatchRequestPart requestPart) {
+      List<ODataResponse> responses = new ArrayList<ODataResponse>();
+
+      for (ODataRequest request : requests) {
+        // Mock the processor of the changeset requests
+        when(handler.process(request)).then(new Answer<ODataResponse>() {
+          @Override
+          public ODataResponse answer(InvocationOnMock invocation) throws Throwable {
+            Object[] arguments = invocation.getArguments();
+
+            return buildResponse((ODataRequest) arguments[0]);
+          }
+        });
+
+        try {
+          responses.add(operation.handleODataRequest(request, requestPart));
+        } catch (BatchException e) {
+          fail();
+        }
+      }
+
+      return responses;
+    }
+
+    @Override
+    public void executeBatch(BatchOperation operation, ODataRequest request, ODataResponse response) {
+      try {
+        final List<BatchRequestPart> parts = operation.parseBatchRequest(request.getBody());
+        final List<ODataResponsePart> responseParts = new ArrayList<ODataResponsePart>();
+
+        for (BatchRequestPart part : parts) {
+          responseParts.add(operation.handleBatchRequest(part));
+        }
+
+        operation.writeResponseParts(responseParts, response);
+      } catch (BatchException e) {
+        throw new ODataRuntimeException(e);
+      } catch (IOException e) {
+        throw new ODataRuntimeException(e);
+      }
+    }
+  }
+
+  private ODataResponse buildResponse(ODataRequest request) {
+    final ODataResponse oDataResponse = new ODataResponse();
+
+    if (request.getMethod() == HttpMethod.POST) {
+      oDataResponse.setStatusCode(HttpStatusCode.CREATED.getStatusCode());
+      oDataResponse.setHeader(HttpHeader.LOCATION, createResourceUri(request));
+    } else {
+      oDataResponse.setStatusCode(HttpStatusCode.OK.getStatusCode());
+    }
+
+    final String contentId = request.getHeader(BatchParserCommon.HTTP_CONTENT_ID);
+    if (contentId != null) {
+      oDataResponse.setHeader(BatchParserCommon.HTTP_CONTENT_ID, contentId);
+    }
+
+    return oDataResponse;
+  }
+
+  private String createResourceUri(final ODataRequest request) {
+    final String parts[] = request.getRawODataPath().split("/");
+    String oDataPath = "";
+
+    if (parts.length == 2) {
+      // Entity Collection
+      oDataPath = parts[1];
+    } else {
+      // Navigationproperty
+
+      final String navProperty = parts[parts.length - 1];
+      if (navProperty.equals("NavPropertyETTwoPrimMany")) {
+        oDataPath = "ESTwoPrim";
+      } else if (navProperty.equals("NavPropertyETAllPrimMany")) {
+        oDataPath = "ESAllPrim";
+      } else if (navProperty.equals("NavPropertyETTwoPrimOne")) {
+        oDataPath = "ESTwoPrim";
+      }
+    }
+
+    return BASE_URI + "/" + oDataPath + "(" + entityCounter++ + ")";
+  }
+}


[17/22] olingo-odata4 git commit: [OLINGO-472] Batch Refactoring

Posted by ch...@apache.org.
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/test/java/org/apache/olingo/server/core/deserializer/BatchRequestParserTest.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/deserializer/BatchRequestParserTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/deserializer/BatchRequestParserTest.java
new file mode 100644
index 0000000..68a219a
--- /dev/null
+++ b/lib/server-core/src/test/java/org/apache/olingo/server/core/deserializer/BatchRequestParserTest.java
@@ -0,0 +1,1326 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.deserializer;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.URISyntaxException;
+import java.util.List;
+
+import org.apache.olingo.commons.api.http.HttpHeader;
+import org.apache.olingo.commons.api.http.HttpMethod;
+import org.apache.olingo.server.api.ODataRequest;
+import org.apache.olingo.server.api.batch.BatchException;
+import org.apache.olingo.server.api.batch.BatchException.MessageKeys;
+import org.apache.olingo.server.api.deserializer.batch.BatchRequestPart;
+import org.apache.olingo.server.core.deserializer.batch.BatchParser;
+import org.apache.olingo.server.core.deserializer.batch.BatchParserCommon;
+import org.junit.Test;
+
+public class BatchRequestParserTest {
+
+  private static final String SERVICE_ROOT = "http://localhost/odata";
+  private static final String CONTENT_TYPE = "multipart/mixed;boundary=batch_8194-cf13-1f56";
+  private static final String CRLF = "\r\n";
+  private static final String MIME_HEADERS = "Content-Type: application/http" + CRLF
+      + "Content-Transfer-Encoding: binary" + CRLF;
+  private static final String GET_REQUEST = ""
+      + MIME_HEADERS
+      + CRLF
+      + "GET Employees('1')/EmployeeName HTTP/1.1" + CRLF
+      + CRLF
+      + CRLF;
+
+  @Test
+  public void test() throws IOException, BatchException, URISyntaxException {
+    final InputStream in = readFile("/batchWithPost.batch");
+    final List<BatchRequestPart> batchRequestParts = parse(in);
+
+    assertNotNull(batchRequestParts);
+    assertFalse(batchRequestParts.isEmpty());
+
+    for (BatchRequestPart object : batchRequestParts) {
+      if (!object.isChangeSet()) {
+        assertEquals(1, object.getRequests().size());
+        ODataRequest retrieveRequest = object.getRequests().get(0);
+        assertEquals(HttpMethod.GET, retrieveRequest.getMethod());
+
+        if (retrieveRequest.getHeaders(HttpHeader.ACCEPT_LANGUAGE) != null) {
+          assertEquals(3, retrieveRequest.getHeaders(HttpHeader.ACCEPT_LANGUAGE).size());
+        }
+
+        assertEquals(SERVICE_ROOT, retrieveRequest.getRawBaseUri());
+        assertEquals("/Employees('2')/EmployeeName", retrieveRequest.getRawODataPath());
+        assertEquals("http://localhost/odata/Employees('2')/EmployeeName?$format=json", retrieveRequest
+            .getRawRequestUri());
+        assertEquals("$format=json", retrieveRequest.getRawQueryPath());
+      } else {
+        List<ODataRequest> requests = object.getRequests();
+        for (ODataRequest request : requests) {
+
+          assertEquals(HttpMethod.PUT, request.getMethod());
+          assertEquals("100000", request.getHeader(HttpHeader.CONTENT_LENGTH));
+          assertEquals("application/json;odata=verbose", request.getHeader(HttpHeader.CONTENT_TYPE));
+
+          List<String> acceptHeader = request.getHeaders(HttpHeader.ACCEPT);
+          assertEquals(3, request.getHeaders(HttpHeader.ACCEPT).size());
+          assertEquals("application/atomsvc+xml;q=0.8", acceptHeader.get(0));
+          assertEquals("*/*;q=0.1", acceptHeader.get(2));
+
+          assertEquals("http://localhost/odata/Employees('2')/EmployeeName", request.getRawRequestUri());
+          assertEquals("http://localhost/odata", request.getRawBaseUri());
+          assertEquals("/Employees('2')/EmployeeName", request.getRawODataPath());
+          assertEquals("", request.getRawQueryPath()); // No query parameter
+        }
+      }
+    }
+  }
+
+  @Test
+  public void testImageInContent() throws IOException, BatchException, URISyntaxException {
+    final InputStream contentInputStream = readFile("/batchWithContent.batch");
+    final String content = StringUtil.toString(contentInputStream);
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + MIME_HEADERS
+        + CRLF
+        + "GET Employees?$filter=Age%20gt%2040 HTTP/1.1" + CRLF
+        + "Accept: application/atomsvc+xml;q=0.8, application/json;odata=verbose;q=0.5, */*;q=0.1" + CRLF
+        + "MaxDataServiceVersion: 2.0" + CRLF
+        + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56" + CRLF
+        + "Content-Type: multipart/mixed; boundary=changeset_f980-1cb6-94dd" + CRLF
+        + CRLF
+        + "--changeset_f980-1cb6-94dd" + CRLF
+        + "content-type:     Application/http" + CRLF
+        + "content-transfer-encoding: Binary" + CRLF
+        + "Content-ID: 1" + CRLF
+        + CRLF
+        + "POST Employees HTTP/1.1" + CRLF
+        + "Content-length: 100000" + CRLF
+        + "Content-type: application/octet-stream" + CRLF
+        + CRLF
+        + content
+        + CRLF
+        + "--changeset_f980-1cb6-94dd--" + CRLF
+        + "--batch_8194-cf13-1f56--";
+    final List<BatchRequestPart> BatchRequestParts = parse(batch);
+
+    for (BatchRequestPart part : BatchRequestParts) {
+      if (!part.isChangeSet()) {
+        assertEquals(1, part.getRequests().size());
+        final ODataRequest retrieveRequest = part.getRequests().get(0);
+
+        assertEquals(HttpMethod.GET, retrieveRequest.getMethod());
+        assertEquals("http://localhost/odata/Employees?$filter=Age%20gt%2040", retrieveRequest.getRawRequestUri());
+        assertEquals("http://localhost/odata", retrieveRequest.getRawBaseUri());
+        assertEquals("/Employees", retrieveRequest.getRawODataPath());
+        assertEquals("$filter=Age%20gt%2040", retrieveRequest.getRawQueryPath());
+      } else {
+        final List<ODataRequest> requests = part.getRequests();
+        for (ODataRequest request : requests) {
+          assertEquals(HttpMethod.POST, request.getMethod());
+          assertEquals("100000", request.getHeader(HttpHeader.CONTENT_LENGTH));
+          assertEquals("1", request.getHeader(BatchParserCommon.HTTP_CONTENT_ID));
+          assertEquals("application/octet-stream", request.getHeader(HttpHeader.CONTENT_TYPE));
+
+          final InputStream body = request.getBody();
+          assertEquals(content, StringUtil.toString(body));
+        }
+      }
+    }
+  }
+
+  @Test
+  public void testPostWithoutBody() throws IOException, BatchException, URISyntaxException {
+    final String batch = CRLF
+        + "--batch_8194-cf13-1f56" + CRLF
+        + "Content-Type: multipart/mixed; boundary=changeset_f980-1cb6-94dd" + CRLF
+        + CRLF
+        + "--changeset_f980-1cb6-94dd" + CRLF
+        + MIME_HEADERS
+        + "Content-ID: changeRequest1" + CRLF
+        + CRLF
+        + "POST Employees('2') HTTP/1.1" + CRLF
+        + "Content-Length: 100" + CRLF
+        + "Content-Type: application/octet-stream" + CRLF
+        + CRLF
+        + CRLF
+        + "--changeset_f980-1cb6-94dd--" + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+    final List<BatchRequestPart> batchRequestParts = parse(batch);
+
+    for (BatchRequestPart object : batchRequestParts) {
+      if (object.isChangeSet()) {
+        final List<ODataRequest> requests = object.getRequests();
+
+        for (ODataRequest request : requests) {
+          assertEquals(HttpMethod.POST, request.getMethod());
+          assertEquals("100", request.getHeader(HttpHeader.CONTENT_LENGTH));
+          assertEquals("application/octet-stream", request.getHeader(HttpHeader.CONTENT_TYPE));
+          assertNotNull(request.getBody());
+        }
+      }
+    }
+  }
+
+  @Test
+  public void testBoundaryParameterWithQuotas() throws BatchException, UnsupportedEncodingException {
+    final String contentType = "multipart/mixed; boundary=\"batch_1.2+34:2j)0?\"";
+    final String batch = ""
+        + "--batch_1.2+34:2j)0?" + CRLF
+        + GET_REQUEST
+        + "--batch_1.2+34:2j)0?--";
+    final BatchParser parser = new BatchParser();
+    final List<BatchRequestPart> batchRequestParts =
+        parser.parseBatchRequest(StringUtil.toInputStream(batch), contentType, SERVICE_ROOT, "", true);
+
+    assertNotNull(batchRequestParts);
+    assertFalse(batchRequestParts.isEmpty());
+  }
+
+  @Test
+  public void testBatchWithInvalidContentType() throws UnsupportedEncodingException {
+    final String invalidContentType = "multipart;boundary=batch_1740-bb84-2f7f";
+    final String batch = ""
+        + "--batch_1740-bb84-2f7f" + CRLF
+        + GET_REQUEST
+        + "--batch_1740-bb84-2f7f--";
+    final BatchParser parser = new BatchParser();
+
+    try {
+      parser.parseBatchRequest(StringUtil.toInputStream(batch), invalidContentType, SERVICE_ROOT, "", true);
+      fail();
+    } catch (BatchException e) {
+      assertMessageKey(e, BatchException.MessageKeys.INVALID_CONTENT_TYPE);
+    }
+  }
+
+  @Test
+  public void testContentTypeCharset() throws BatchException {
+    final String contentType = "multipart/mixed; charset=UTF-8;boundary=batch_14d1-b293-b99a";
+    final String batch = ""
+        + "--batch_14d1-b293-b99a" + CRLF
+        + GET_REQUEST
+        + "--batch_14d1-b293-b99a--";
+    final BatchParser parser = new BatchParser();
+    final List<BatchRequestPart> parts =
+        parser.parseBatchRequest(StringUtil.toInputStream(batch), contentType, SERVICE_ROOT, "", true);
+
+    assertEquals(1, parts.size());
+  }
+
+  @Test
+  public void testBatchWithoutBoundaryParameter() throws UnsupportedEncodingException {
+    final String invalidContentType = "multipart/mixed";
+    final String batch = ""
+        + "--batch_1740-bb84-2f7f" + CRLF
+        + GET_REQUEST
+        + "--batch_1740-bb84-2f7f--";
+    final BatchParser parser = new BatchParser();
+
+    try {
+      parser.parseBatchRequest(StringUtil.toInputStream(batch), invalidContentType, SERVICE_ROOT, "", true);
+      fail();
+    } catch (BatchException e) {
+      assertMessageKey(e, BatchException.MessageKeys.INVALID_CONTENT_TYPE);
+    }
+  }
+
+  @Test
+  public void testBoundaryParameterWithoutQuota() throws UnsupportedEncodingException {
+    final String invalidContentType = "multipart/mixed;boundary=batch_1740-bb:84-2f7f";
+    final String batch = ""
+        + "--batch_1740-bb:84-2f7f" + CRLF
+        + GET_REQUEST
+        + "--batch_1740-bb:84-2f7f--";
+    final BatchParser parser = new BatchParser();
+
+    try {
+      parser.parseBatchRequest(StringUtil.toInputStream(batch), invalidContentType, SERVICE_ROOT, "", true);
+      fail();
+    } catch (BatchException e) {
+      assertMessageKey(e, BatchException.MessageKeys.INVALID_BOUNDARY);
+    }
+  }
+
+  @Test
+  public void testWrongBoundaryString() throws BatchException, UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f5" + CRLF
+        + GET_REQUEST
+        + "--batch_8194-cf13-1f56--";
+
+    final List<BatchRequestPart> parts = parse(batch);
+    assertEquals(0, parts.size());
+  }
+
+  @Test
+  public void testMissingHttpVersion() throws UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + "Content-Type: application/http" + CRLF
+        + "Content-Transfer-Encoding:binary" + CRLF
+        + CRLF
+        + "GET Employees?$format=json" + CRLF
+        + "Host: localhost:8080" + CRLF
+        + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, BatchException.MessageKeys.INVALID_STATUS_LINE);
+  }
+
+  @Test
+  public void testMissingHttpVersion2() throws UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + "Content-Type: application/http" + CRLF
+        + "Content-Transfer-Encoding:binary" + CRLF
+        + CRLF
+        + "GET Employees?$format=json " + CRLF
+        + "Host: localhost:8080" + CRLF
+        + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, BatchException.MessageKeys.INVALID_HTTP_VERSION);
+  }
+
+  @Test
+  public void testMissingHttpVersion3() throws UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + "Content-Type: application/http" + CRLF
+        + "Content-Transfer-Encoding:binary" + CRLF
+        + CRLF
+        + "GET Employees?$format=json SMTP:3.1" + CRLF
+        + "Host: localhost:8080" + CRLF
+        + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, BatchException.MessageKeys.INVALID_HTTP_VERSION);
+  }
+
+  @Test
+  public void testBoundaryWithoutHyphen() throws UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + GET_REQUEST
+        + "batch_8194-cf13-1f56" + CRLF
+        + GET_REQUEST
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, BatchException.MessageKeys.INVALID_CONTENT);
+  }
+
+  @Test
+  public void testNoBoundaryString() throws UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + GET_REQUEST
+        // + no boundary string
+        + GET_REQUEST
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, BatchException.MessageKeys.INVALID_CONTENT);
+  }
+
+  @Test
+  public void testBatchBoundaryEqualsChangeSetBoundary() throws UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + "Content-Type: multipart/mixed;boundary=batch_8194-cf13-1f56" + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56" + CRLF
+        + MIME_HEADERS
+        + CRLF
+        + "PUT Employees('2')/EmployeeName HTTP/1.1" + CRLF
+        + "Accept: application/atomsvc+xml;q=0.8, application/json;odata=verbose;q=0.5, */*;q=0.1" + CRLF
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + "MaxDataServiceVersion: 2.0" + CRLF
+        + CRLF
+        + "{\"EmployeeName\":\"Frederic Fall MODIFIED\"}" + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--"
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, BatchException.MessageKeys.MISSING_BLANK_LINE);
+  }
+
+  @Test
+  public void testNoContentType() throws UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + "Content-Transfer-Encoding: binary" + CRLF
+        + CRLF
+        + "GET Employees('1')/EmployeeName HTTP/1.1" + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, BatchException.MessageKeys.MISSING_CONTENT_TYPE);
+  }
+
+  @Test
+  public void testMimeHeaderContentType() throws UnsupportedEncodingException {
+    final String batch = "--batch_8194-cf13-1f56" + CRLF
+        + "Content-Type: text/plain" + CRLF
+        + "Content-Transfer-Encoding: binary" + CRLF
+        + CRLF
+        + "GET Employees('1')/EmployeeName HTTP/1.1" + CRLF
+        + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, BatchException.MessageKeys.INVALID_CONTENT_TYPE);
+  }
+
+  @Test
+  public void testMimeHeaderEncoding() throws UnsupportedEncodingException {
+    String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + "Content-Type: application/http" + CRLF
+        + "Content-Transfer-Encoding: 8bit" + CRLF
+        + CRLF
+        + "GET Employees('1')/EmployeeName HTTP/1.1" + CRLF
+        + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, BatchException.MessageKeys.INVALID_CONTENT_TRANSFER_ENCODING);
+  }
+
+  @Test
+  public void testGetRequestMissingCRLF() throws UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + MIME_HEADERS
+        + "Content-ID: 1" + CRLF
+        + CRLF
+        + "GET Employees('1')/EmployeeName HTTP/1.1" + CRLF
+        // + CRLF // Belongs to the GET request
+        + CRLF // Belongs to the
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, BatchException.MessageKeys.MISSING_BLANK_LINE);
+  }
+
+  @Test
+  public void testInvalidMethodForBatch() throws UnsupportedEncodingException {
+    final String batch = "--batch_8194-cf13-1f56" + CRLF
+        + MIME_HEADERS
+        + CRLF
+        + "POST Employees('1')/EmployeeName HTTP/1.1" + CRLF
+        + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, BatchException.MessageKeys.INVALID_QUERY_OPERATION_METHOD);
+  }
+
+  @Test
+  public void testNoBoundaryFound() throws UnsupportedEncodingException {
+    final String batch = "batch_8194-cf13-1f56" + CRLF
+        + MIME_HEADERS
+        + CRLF
+        + "POST Employees('1')/EmployeeName HTTP/1.1" + CRLF
+        + CRLF;
+
+    parseInvalidBatchBody(batch, BatchException.MessageKeys.MISSING_CLOSE_DELIMITER);
+  }
+
+  @Test
+  public void testEmptyRequest() throws BatchException, UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56--";
+
+    final List<BatchRequestPart> parts = parse(batch);
+    assertEquals(0, parts.size());
+  }
+
+  @Test
+  public void testBadRequest() throws UnsupportedEncodingException {
+    final String batch = "This is a bad request. There is no syntax and also no semantic";
+
+    parseInvalidBatchBody(batch, BatchException.MessageKeys.MISSING_CLOSE_DELIMITER);
+  }
+
+  @Test
+  public void testNoMethod() throws UnsupportedEncodingException {
+    final String batch = "--batch_8194-cf13-1f56" + CRLF
+        + MIME_HEADERS
+        + CRLF
+        + /* GET */"Employees('1')/EmployeeName HTTP/1.1" + CRLF
+        + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, BatchException.MessageKeys.INVALID_STATUS_LINE);
+  }
+
+  @Test
+  public void testInvalidMethodForChangeset() throws UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + "Content-Type: multipart/mixed; boundary=changeset_f980-1cb6-94dd" + CRLF
+        + CRLF
+        + "--changeset_f980-1cb6-94dd" + CRLF
+        + MIME_HEADERS
+        + "Content-Id: 1" + CRLF
+        + CRLF
+        + "GET Employees('2')/EmployeeName HTTP/1.1" + CRLF
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + "MaxDataServiceVersion: 2.0" + CRLF
+        + CRLF
+        + CRLF
+        + "--changeset_f980-1cb6-94dd--"
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, BatchException.MessageKeys.INVALID_CHANGESET_METHOD);
+  }
+
+  @Test
+  public void testInvalidChangeSetBoundary() throws UnsupportedEncodingException, BatchException {
+    final String batch = "--batch_8194-cf13-1f56" + CRLF
+        + "Content-Type: multipart/mixed;boundary=changeset_f980-1cb6-94dd" + CRLF
+        + CRLF
+        + "--changeset_f980-1cb6-94d"/* +"d" */+ CRLF
+        + MIME_HEADERS
+        + CRLF
+        + "POST Employees('2') HTTP/1.1" + CRLF
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + "MaxDataServiceVersion: 2.0" + CRLF
+        + CRLF
+        + "--changeset_f980-1cb6-94dd--" + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    final List<BatchRequestPart> parts = parse(batch);
+    assertEquals(1, parts.size());
+
+    final BatchRequestPart part = parts.get(0);
+    assertTrue(part.isChangeSet());
+    assertEquals(0, part.getRequests().size());
+  }
+
+  @Test
+  public void testNestedChangeset() throws UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + "Content-Type: multipart/mixed;boundary=changeset_f980-1cb6-94dd" + CRLF
+        + CRLF
+        + "--changeset_f980-1cb6-94dd" + CRLF
+        + "Content-Transfer-Encoding: binary" + CRLF
+        + "Content-Type: multipart/mixed;boundary=changeset_f980-1cb6-94dd2" + CRLF
+        + CRLF
+        + "--changeset_f980-1cb6-94dd2" + CRLF
+        + MIME_HEADERS
+        + "Content-Id: 1" + CRLF
+        + CRLF
+        + "POST Employees('2') HTTP/1.1" + CRLF
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + "MaxDataServiceVersion: 2.0" + CRLF
+        + "Content-Id: 2"
+        + CRLF
+        + "--changeset_f980-1cb6-94dd--" + CRLF
+        + CRLF
+        + "--changeset_f980-1cb6-94dd--" + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, BatchException.MessageKeys.INVALID_CONTENT_TYPE);
+  }
+
+  @Test
+  public void testMissingContentTransferEncoding() throws UnsupportedEncodingException {
+    final String batch = "--batch_8194-cf13-1f56" + CRLF
+        + "Content-Type: multipart/mixed;boundary=changeset_f980-1cb6-94dd" + CRLF
+        + CRLF
+        + "--changeset_f980-1cb6-94dd" + CRLF
+        + "Content-Id: 1" + CRLF
+        + "Content-Type: application/http" + CRLF
+        // + "Content-Transfer-Encoding: binary" + CRLF
+        + CRLF
+        + "POST Employees('2') HTTP/1.1" + CRLF
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + "MaxDataServiceVersion: 2.0" + CRLF
+        + CRLF
+        + CRLF
+        + "--changeset_f980-1cb6-94dd--" + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, BatchException.MessageKeys.MISSING_CONTENT_TRANSFER_ENCODING);
+  }
+
+  @Test
+  public void testMissingContentType() throws UnsupportedEncodingException {
+    final String batch = "--batch_8194-cf13-1f56" + CRLF
+        + "Content-Type: multipart/mixed;boundary=changeset_f980-1cb6-94dd" + CRLF
+        + CRLF
+        + "--changeset_f980-1cb6-94dd" + CRLF
+        + "Content-Id: 1"
+        // + "Content-Type: application/http" + CRLF
+        + "Content-Transfer-Encoding: binary" + CRLF
+        + CRLF
+        + "POST Employees('2') HTTP/1.1" + CRLF
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + "MaxDataServiceVersion: 2.0" + CRLF
+        + CRLF
+        + CRLF
+        + "--changeset_f980-1cb6-94dd--" + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, BatchException.MessageKeys.MISSING_CONTENT_TYPE);
+  }
+
+  @Test
+  public void testNoCloseDelimiter() throws BatchException, UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + GET_REQUEST;
+
+    parseInvalidBatchBody(batch, BatchException.MessageKeys.MISSING_CLOSE_DELIMITER);
+  }
+
+  @Test
+  public void testNoCloseDelimiter2() throws BatchException, UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + MIME_HEADERS
+        + CRLF
+        + "GET Employees('1')/EmployeeName HTTP/1.1" + CRLF;
+
+    parseInvalidBatchBody(batch, BatchException.MessageKeys.MISSING_CLOSE_DELIMITER);
+  }
+
+  @Test
+  public void testInvalidUri() throws UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + MIME_HEADERS
+        + CRLF
+        + "GET http://localhost/aa/odata/Employees('1')/EmployeeName HTTP/1.1" + CRLF
+        + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, BatchException.MessageKeys.INVALID_URI);
+  }
+
+  @Test
+  public void testUriWithAbsolutePath() throws BatchException, UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + MIME_HEADERS
+        + CRLF
+        + "GET /odata/Employees('1')/EmployeeName HTTP/1.1" + CRLF
+        + "Host: http://localhost" + CRLF
+        + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    final List<BatchRequestPart> parts = parse(batch);
+    assertEquals(1, parts.size());
+
+    final BatchRequestPart part = parts.get(0);
+    assertEquals(1, part.getRequests().size());
+    final ODataRequest request = part.getRequests().get(0);
+
+    assertEquals("http://localhost/odata/Employees('1')/EmployeeName", request.getRawRequestUri());
+    assertEquals("http://localhost/odata", request.getRawBaseUri());
+    assertEquals("/Employees('1')/EmployeeName", request.getRawODataPath());
+    assertEquals("", request.getRawQueryPath());
+  }
+
+  @Test
+  public void testUriWithAbsolutePathMissingHostHeader() throws BatchException, UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + MIME_HEADERS
+        + CRLF
+        + "GET /odata/Employees('1')/EmployeeName HTTP/1.1" + CRLF
+        + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, MessageKeys.MISSING_MANDATORY_HEADER);
+  }
+
+  @Test
+  public void testUriWithAbsolutePathMissingHostDulpicatedHeader() throws BatchException, UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + MIME_HEADERS
+        + CRLF
+        + "GET /odata/Employees('1')/EmployeeName HTTP/1.1" + CRLF
+        + "Host: http://localhost" + CRLF
+        + "Host: http://localhost/odata" + CRLF
+        + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, MessageKeys.MISSING_MANDATORY_HEADER);
+  }
+
+  @Test
+  public void testUriWithAbsolutePathOtherHost() throws BatchException, UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + MIME_HEADERS
+        + CRLF
+        + "GET /odata/Employees('1')/EmployeeName HTTP/1.1" + CRLF
+        + "Host: http://localhost2" + CRLF
+        + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, MessageKeys.INVALID_URI);
+  }
+
+  @Test
+  public void testUriWithAbsolutePathWrongPath() throws BatchException, UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + MIME_HEADERS
+        + CRLF
+        + "GET /myservice/Employees('1')/EmployeeName HTTP/1.1" + CRLF
+        + "Host: http://localhost" + CRLF
+        + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, MessageKeys.INVALID_URI);
+  }
+
+  @Test
+  public void testNoCloseDelimiter3() throws UnsupportedEncodingException {
+    final String batch = "--batch_8194-cf13-1f56" + CRLF + GET_REQUEST + "--batch_8194-cf13-1f56-"/* no hyphen */;
+
+    parseInvalidBatchBody(batch, BatchException.MessageKeys.MISSING_CLOSE_DELIMITER);
+  }
+
+  @Test
+  public void testNegativeContentLengthChangeSet() throws BatchException, IOException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + "Content-Type: multipart/mixed; boundary=changeset_f980-1cb6-94dd" + CRLF
+        + CRLF
+        + "--changeset_f980-1cb6-94dd" + CRLF
+        + MIME_HEADERS
+        + "Content-ID: 1" + CRLF
+        + "Content-Length: -2" + CRLF
+        + CRLF
+        + "PUT EmployeeName HTTP/1.1" + CRLF
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + "Content-Id: 1" + CRLF
+        + CRLF
+        + "{\"EmployeeName\":\"Peter Fall\"}" + CRLF
+        + "--changeset_f980-1cb6-94dd--" + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parse(batch);
+  }
+
+  @Test
+  public void testNegativeContentLengthRequest() throws BatchException, IOException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + "Content-Type: multipart/mixed; boundary=changeset_f980-1cb6-94dd" + CRLF
+        + CRLF
+        + "--changeset_f980-1cb6-94dd" + CRLF
+        + MIME_HEADERS
+        + "Content-ID: 1" + CRLF
+        + CRLF
+        + "PUT EmployeeName HTTP/1.1" + CRLF
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + "Content-Id: 1" + CRLF
+        + "Content-Length: 2" + CRLF
+        + CRLF
+        + "{\"EmployeeName\":\"Peter Fall\"}" + CRLF
+        + "--changeset_f980-1cb6-94dd--" + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parse(batch);
+  }
+
+  @Test
+  public void testContentLengthGreatherThanBodyLength() throws BatchException, IOException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + "Content-Type: multipart/mixed; boundary=changeset_f980-1cb6-94dd" + CRLF
+        + CRLF
+        + "--changeset_f980-1cb6-94dd" + CRLF
+        + MIME_HEADERS
+        + "Content-ID: 1" + CRLF
+        + CRLF
+        + "PUT Employee/Name HTTP/1.1" + CRLF
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + "Content-Length: 100000" + CRLF
+        + CRLF
+        + "{\"EmployeeName\":\"Peter Fall\"}" + CRLF
+        + "--changeset_f980-1cb6-94dd--" + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+    final List<BatchRequestPart> batchRequestParts = parse(batch);
+
+    assertNotNull(batchRequestParts);
+
+    for (BatchRequestPart multipart : batchRequestParts) {
+      if (multipart.isChangeSet()) {
+        assertEquals(1, multipart.getRequests().size());
+
+        final ODataRequest request = multipart.getRequests().get(0);
+        assertEquals("{\"EmployeeName\":\"Peter Fall\"}", StringUtil.toString(request.getBody()));
+      }
+    }
+  }
+
+  @Test
+  public void testContentLengthSmallerThanBodyLength() throws BatchException, IOException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + "Content-Type: multipart/mixed; boundary=changeset_f980-1cb6-94dd" + CRLF
+        + CRLF
+        + "--changeset_f980-1cb6-94dd" + CRLF
+        + MIME_HEADERS
+        + "Content-ID: 1" + CRLF
+        + CRLF
+        + "PUT EmployeeName HTTP/1.1" + CRLF
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + "Content-Length: 10" + CRLF
+        + CRLF
+        + "{\"EmployeeName\":\"Peter Fall\"}" + CRLF
+        + "--changeset_f980-1cb6-94dd--" + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+    final List<BatchRequestPart> batchRequestParts = parse(batch);
+
+    assertNotNull(batchRequestParts);
+
+    for (BatchRequestPart multipart : batchRequestParts) {
+      if (multipart.isChangeSet()) {
+        assertEquals(1, multipart.getRequests().size());
+
+        final ODataRequest request = multipart.getRequests().get(0);
+        assertEquals("{\"Employee", StringUtil.toString(request.getBody()));
+      }
+    }
+  }
+
+  @Test
+  public void testNonNumericContentLength() throws UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + "Content-Type: multipart/mixed; boundary=changeset_f980-1cb6-94dd" + CRLF
+        + CRLF
+        + "--changeset_f980-1cb6-94dd" + CRLF
+        + MIME_HEADERS
+        + "Content-ID: 1" + CRLF
+        + CRLF
+        + "PUT EmployeeName HTTP/1.1" + CRLF
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + "Content-Length: 10abc" + CRLF
+        + CRLF
+        + "{\"EmployeeName\":\"Peter Fall\"}" + CRLF
+        + "--changeset_f980-1cb6-94dd--" + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, BatchException.MessageKeys.INVALID_HEADER);
+  }
+
+  @Test
+  public void testNonStrictParser() throws BatchException, IOException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + "Content-Type: multipart/mixed;boundary=changeset_8194-cf13-1f56" + CRLF
+        + "--changeset_8194-cf13-1f56" + CRLF
+        + MIME_HEADERS
+        + "Content-ID: myRequest" + CRLF
+        + "PUT Employees('2')/EmployeeName HTTP/1.1" + CRLF
+        + "Accept: application/atomsvc+xml;q=0.8, application/json;odata=verbose;q=0.5, */*;q=0.1" + CRLF
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + "MaxDataServiceVersion: 2.0" + CRLF
+        + "{\"EmployeeName\":\"Frederic Fall MODIFIED\"}" + CRLF
+        + "--changeset_8194-cf13-1f56--" + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    final List<BatchRequestPart> requests = parse(batch, false);
+
+    assertNotNull(requests);
+    assertEquals(1, requests.size());
+
+    final BatchRequestPart part = requests.get(0);
+    assertTrue(part.isChangeSet());
+    assertNotNull(part.getRequests());
+    assertEquals(1, part.getRequests().size());
+
+    final ODataRequest changeRequest = part.getRequests().get(0);
+    assertEquals("{\"EmployeeName\":\"Frederic Fall MODIFIED\"}",
+        StringUtil.toString(changeRequest.getBody()));
+    assertEquals("application/json;odata=verbose", changeRequest.getHeader(HttpHeader.CONTENT_TYPE));
+    assertEquals(HttpMethod.PUT, changeRequest.getMethod());
+  }
+
+  @Test
+  public void testNonStrictParserMoreCRLF() throws UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + "Content-Type: multipart/mixed;boundary=changeset_8194-cf13-1f56" + CRLF
+        + "--changeset_8194-cf13-1f56" + CRLF
+        + MIME_HEADERS
+        + CRLF
+        + CRLF // Only one CRLF allowed
+        + "PUT Employees('2')/EmployeeName HTTP/1.1" + CRLF
+        + "Accept: application/atomsvc+xml;q=0.8, application/json;odata=verbose;q=0.5, */*;q=0.1" + CRLF
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + "MaxDataServiceVersion: 2.0" + CRLF
+        + "{\"EmployeeName\":\"Frederic Fall MODIFIED\"}" + CRLF
+        + "--changeset_8194-cf13-1f56--" + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, BatchException.MessageKeys.INVALID_STATUS_LINE, false);
+  }
+
+  @Test
+  public void testContentId() throws BatchException, UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + MIME_HEADERS
+        + CRLF
+        + "GET Employees HTTP/1.1" + CRLF
+        + "accept: */*,application/atom+xml,application/atomsvc+xml,application/xml" + CRLF
+        + "Content-Id: BBB" + CRLF
+        + CRLF + CRLF
+        + "--batch_8194-cf13-1f56" + CRLF
+        + "Content-Type: multipart/mixed; boundary=changeset_f980-1cb6-94dd" + CRLF
+        + CRLF
+        + "--changeset_f980-1cb6-94dd" + CRLF
+        + MIME_HEADERS
+        + "Content-Id: 1" + CRLF
+        + CRLF
+        + "POST Employees HTTP/1.1" + CRLF
+        + "Content-type: application/octet-stream" + CRLF
+        + CRLF
+        + "/9j/4AAQSkZJRgABAQEBLAEsAAD/4RM0RXhpZgAATU0AKgAAAAgABwESAAMAAAABAAEAAAEaAAUAAAABAAAAYgEbAAUAAAA" + CRLF
+        + CRLF
+        + "--changeset_f980-1cb6-94dd" + CRLF
+        + MIME_HEADERS
+        + CRLF
+        + "PUT $1/EmployeeName HTTP/1.1" + CRLF
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + "Content-Id: 2" + CRLF
+        + CRLF
+        + "{\"EmployeeName\":\"Peter Fall\"}" + CRLF
+        + "--changeset_f980-1cb6-94dd--" + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    final List<BatchRequestPart> batchRequestParts = parse(batch);
+    assertNotNull(batchRequestParts);
+
+    for (BatchRequestPart multipart : batchRequestParts) {
+      if (!multipart.isChangeSet()) {
+        assertEquals(1, multipart.getRequests().size());
+        final ODataRequest retrieveRequest = multipart.getRequests().get(0);
+
+        assertEquals("BBB", retrieveRequest.getHeader(BatchParserCommon.HTTP_CONTENT_ID));
+      } else {
+        for (ODataRequest request : multipart.getRequests()) {
+          if (HttpMethod.POST.equals(request.getMethod())) {
+            assertEquals("1", request.getHeader(BatchParserCommon.HTTP_CONTENT_ID));
+          } else if (HttpMethod.PUT.equals(request.getMethod())) {
+            assertEquals("2", request.getHeader(BatchParserCommon.HTTP_CONTENT_ID));
+            assertEquals("/$1/EmployeeName", request.getRawODataPath());
+            assertEquals("http://localhost/odata/$1/EmployeeName", request.getRawRequestUri());
+          }
+        }
+      }
+    }
+  }
+
+  @Test
+  public void testNoContentId() throws BatchException, UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + MIME_HEADERS
+        + CRLF
+        + "GET Employees HTTP/1.1" + CRLF
+        + "accept: */*,application/atom+xml,application/atomsvc+xml,application/xml" + CRLF
+        + CRLF + CRLF
+        + "--batch_8194-cf13-1f56" + CRLF
+        + "Content-Type: multipart/mixed; boundary=changeset_f980-1cb6-94dd" + CRLF
+        + CRLF
+        + "--changeset_f980-1cb6-94dd" + CRLF
+        + MIME_HEADERS
+        + "Content-Id: 1" + CRLF
+        + CRLF
+        + "POST Employees HTTP/1.1" + CRLF
+        + "Content-type: application/octet-stream" + CRLF
+        + CRLF
+        + "/9j/4AAQSkZJRgABAQEBLAEsAAD/4RM0RXhpZgAATU0AKgAAAAgABwESAAMAAAABAAEAAAEaAAUAAAABAAAAYgEbAAUAAAA" + CRLF
+        + CRLF
+        + "--changeset_f980-1cb6-94dd" + CRLF
+        + MIME_HEADERS
+        + "Content-Id: 1" + CRLF
+        + CRLF
+        + "PUT $1/EmployeeName HTTP/1.1" + CRLF
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + CRLF
+        + "{\"EmployeeName\":\"Peter Fall\"}" + CRLF
+        + "--changeset_f980-1cb6-94dd--" + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parse(batch);
+  }
+
+  @Test
+  public void testPreamble() throws BatchException, IOException {
+    final String batch = ""
+        + "This is a preamble and must be ignored" + CRLF
+        + CRLF
+        + CRLF
+        + "----1242" + CRLF
+        + "--batch_8194-cf13-1f56" + CRLF
+        + MIME_HEADERS
+        + CRLF
+        + "GET Employees HTTP/1.1" + CRLF
+        + "accept: */*,application/atom+xml,application/atomsvc+xml,application/xml" + CRLF
+        + "Content-Id: BBB" + CRLF
+        + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56" + CRLF
+        + "Content-Type: multipart/mixed; boundary=changeset_f980-1cb6-94dd" + CRLF
+        + CRLF
+        + "This is a preamble and must be ignored" + CRLF
+        + CRLF
+        + CRLF
+        + "----1242" + CRLF
+        + "--changeset_f980-1cb6-94dd" + CRLF
+        + MIME_HEADERS
+        + "Content-Id: 1" + CRLF
+        + CRLF
+        + "POST Employees HTTP/1.1" + CRLF
+        + "Content-type: application/octet-stream" + CRLF
+        + CRLF
+        + "/9j/4AAQSkZJRgABAQEBLAEsAAD/4RM0RXhpZgAATU0AKgAAAAgABwESAAMAAAABAAEAAAEaAAUAAAABAAAAYgEbAAUAAAA" + CRLF
+        + CRLF
+        + "--changeset_f980-1cb6-94dd" + CRLF
+        + MIME_HEADERS
+        + "Content-ID: 2" + CRLF
+        + CRLF
+        + "PUT $1/EmployeeName HTTP/1.1" + CRLF
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + CRLF
+        + "{\"EmployeeName\":\"Peter Fall\"}" + CRLF
+        + "--changeset_f980-1cb6-94dd--" + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+    final List<BatchRequestPart> batchRequestParts = parse(batch);
+
+    assertNotNull(batchRequestParts);
+    assertEquals(2, batchRequestParts.size());
+
+    final BatchRequestPart getRequestPart = batchRequestParts.get(0);
+    assertEquals(1, getRequestPart.getRequests().size());
+
+    final ODataRequest getRequest = getRequestPart.getRequests().get(0);
+    assertEquals(HttpMethod.GET, getRequest.getMethod());
+
+    final BatchRequestPart changeSetPart = batchRequestParts.get(1);
+    assertEquals(2, changeSetPart.getRequests().size());
+    assertEquals("/9j/4AAQSkZJRgABAQEBLAEsAAD/4RM0RXhpZgAATU0AKgAAAAgABwESAAMAAAABAAEAAAEaAAUAAAABAAAAYgEbAAUAAAA"
+        + CRLF,
+        StringUtil.toString(changeSetPart.getRequests().get(0).getBody()));
+    assertEquals("{\"EmployeeName\":\"Peter Fall\"}",
+        StringUtil.toString(changeSetPart.getRequests().get(1).getBody()));
+  }
+
+  @Test
+  public void testContentTypeCaseInsensitive() throws BatchException, IOException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + "Content-Type: muLTiParT/mixed; boundary=changeset_f980-1cb6-94dd" + CRLF
+        + CRLF
+        + "--changeset_f980-1cb6-94dd" + CRLF
+        + MIME_HEADERS
+        + "Content-ID: 1" + CRLF
+        + "Content-Length: 200" + CRLF
+        + CRLF
+        + "PUT EmployeeName HTTP/1.1" + CRLF
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + CRLF
+        + "{\"EmployeeName\":\"Peter Fall\"}" + CRLF
+        + "--changeset_f980-1cb6-94dd--" + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parse(batch);
+  }
+
+  @Test
+  public void testContentTypeBoundaryCaseInsensitive() throws BatchException, IOException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + "Content-Type: multipart/mixed; bOunDaRy=changeset_f980-1cb6-94dd" + CRLF
+        + CRLF
+        + "--changeset_f980-1cb6-94dd" + CRLF
+        + MIME_HEADERS
+        + "Content-ID: 1" + CRLF
+        + CRLF
+        + "PUT EmployeeName HTTP/1.1" + CRLF
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + CRLF
+        + "{\"EmployeeName\":\"Peter Fall\"}" + CRLF
+        + "--changeset_f980-1cb6-94dd--" + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+    final List<BatchRequestPart> batchRequestParts = parse(batch);
+
+    assertNotNull(batchRequestParts);
+    assertEquals(1, batchRequestParts.size());
+    assertTrue(batchRequestParts.get(0).isChangeSet());
+    assertEquals(1, batchRequestParts.get(0).getRequests().size());
+  }
+
+  @Test
+  public void testEpilog() throws BatchException, IOException {
+    String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + MIME_HEADERS
+        + CRLF
+        + "GET Employees HTTP/1.1" + CRLF
+        + "accept: */*,application/atom+xml,application/atomsvc+xml,application/xml" + CRLF
+        + "Content-Id: BBB" + CRLF
+        + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56" + CRLF
+        + "Content-Type: multipart/mixed; boundary=changeset_f980-1cb6-94dd" + CRLF
+        + CRLF
+        + "--changeset_f980-1cb6-94dd" + CRLF
+        + MIME_HEADERS
+        + "Content-Id: 1" + CRLF
+        + CRLF
+        + "POST Employees HTTP/1.1" + CRLF
+        + "Content-type: application/octet-stream" + CRLF
+        + CRLF
+        + "/9j/4AAQSkZJRgABAQEBLAEsAAD/4RM0RXhpZgAATU0AKgAAAAgABwESAAMAAAABAAEAAAEaAAUAAAABAAAAYgEbAAUAAAA" + CRLF
+        + CRLF
+        + "--changeset_f980-1cb6-94dd" + CRLF
+        + MIME_HEADERS
+        + "Content-ID: 2" + CRLF
+        + CRLF
+        + "PUT $1/EmployeeName HTTP/1.1" + CRLF
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + CRLF
+        + "{\"EmployeeName\":\"Peter Fall\"}" + CRLF
+        + "--changeset_f980-1cb6-94dd--" + CRLF
+        + CRLF
+        + "This is an epilog and must be ignored" + CRLF
+        + CRLF
+        + CRLF
+        + "----1242"
+        + CRLF
+        + "--batch_8194-cf13-1f56--"
+        + CRLF
+        + "This is an epilog and must be ignored" + CRLF
+        + CRLF
+        + CRLF
+        + "----1242";
+    final List<BatchRequestPart> batchRequestParts = parse(batch);
+
+    assertNotNull(batchRequestParts);
+    assertEquals(2, batchRequestParts.size());
+
+    BatchRequestPart getRequestPart = batchRequestParts.get(0);
+    assertEquals(1, getRequestPart.getRequests().size());
+    ODataRequest getRequest = getRequestPart.getRequests().get(0);
+    assertEquals(HttpMethod.GET, getRequest.getMethod());
+
+    BatchRequestPart changeSetPart = batchRequestParts.get(1);
+    assertEquals(2, changeSetPart.getRequests().size());
+    assertEquals("/9j/4AAQSkZJRgABAQEBLAEsAAD/4RM0RXhpZgAATU0AKgAAAAgABwESAAMAAAABAAEAAAEaAAUAAAABAAAAYgEbAAUAAAA"
+        + CRLF,
+        StringUtil.toString(changeSetPart.getRequests().get(0).getBody()));
+    assertEquals("{\"EmployeeName\":\"Peter Fall\"}",
+        StringUtil.toString(changeSetPart.getRequests().get(1).getBody()));
+  }
+
+  @Test
+  public void testLargeBatch() throws BatchException, IOException {
+    final InputStream in = readFile("/batchLarge.batch");
+    parse(in);
+  }
+
+  @Test
+  public void testForddenHeaderAuthorisation() throws UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + MIME_HEADERS
+        + CRLF
+        + "GET Employees('1')/EmployeeName HTTP/1.1" + CRLF
+        + "Authorization: Basic QWxhZdsdsddsduIHNlc2FtZQ==" + CRLF
+        + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, MessageKeys.FORBIDDEN_HEADER);
+  }
+
+  @Test
+  public void testForddenHeaderExpect() throws UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + MIME_HEADERS
+        + CRLF
+        + "GET Employees('1')/EmployeeName HTTP/1.1" + CRLF
+        + "Expect: 100-continue" + CRLF
+        + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, MessageKeys.FORBIDDEN_HEADER);
+  }
+
+  @Test
+  public void testForddenHeaderFrom() throws UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + MIME_HEADERS
+        + CRLF
+        + "GET Employees('1')/EmployeeName HTTP/1.1" + CRLF
+        + "From: test@test.com" + CRLF
+        + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, MessageKeys.FORBIDDEN_HEADER);
+  }
+
+  @Test
+  public void testForddenHeaderRange() throws UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + MIME_HEADERS
+        + CRLF
+        + "GET Employees('1')/EmployeeName HTTP/1.1" + CRLF
+        + "Range: 200-256" + CRLF
+        + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, MessageKeys.FORBIDDEN_HEADER);
+  }
+
+  @Test
+  public void testForddenHeaderMaxForwards() throws UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + MIME_HEADERS
+        + CRLF
+        + "GET Employees('1')/EmployeeName HTTP/1.1" + CRLF
+        + "Max-Forwards: 3" + CRLF
+        + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, MessageKeys.FORBIDDEN_HEADER);
+  }
+
+  @Test
+  public void testForddenHeaderTE() throws UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + MIME_HEADERS
+        + CRLF
+        + "GET Employees('1')/EmployeeName HTTP/1.1" + CRLF
+        + "TE: deflate" + CRLF
+        + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, MessageKeys.FORBIDDEN_HEADER);
+  }
+
+  private List<BatchRequestPart> parse(final InputStream in, final boolean isStrict) throws BatchException {
+    final BatchParser parser = new BatchParser();
+    final List<BatchRequestPart> batchRequestParts =
+        parser.parseBatchRequest(in, CONTENT_TYPE, SERVICE_ROOT, "", isStrict);
+
+    assertNotNull(batchRequestParts);
+
+    return batchRequestParts;
+  }
+
+  private List<BatchRequestPart> parse(final InputStream in) throws BatchException {
+    return parse(in, true);
+  }
+
+  private List<BatchRequestPart> parse(final String batch) throws BatchException, UnsupportedEncodingException {
+    return parse(batch, true);
+  }
+
+  private List<BatchRequestPart> parse(final String batch, final boolean isStrict) throws BatchException,
+      UnsupportedEncodingException {
+    return parse(StringUtil.toInputStream(batch), isStrict);
+  }
+
+  private void parseInvalidBatchBody(final String batch, final MessageKeys key, final boolean isStrict)
+      throws UnsupportedEncodingException {
+    final BatchParser parser = new BatchParser();
+
+    try {
+      parser.parseBatchRequest(StringUtil.toInputStream(batch), CONTENT_TYPE, SERVICE_ROOT, "", isStrict);
+      fail("No exception thrown. Expect: " + key.toString());
+    } catch (BatchException e) {
+      assertMessageKey(e, key);
+    }
+  }
+
+  private void parseInvalidBatchBody(final String batch, final MessageKeys key) throws UnsupportedEncodingException {
+    parseInvalidBatchBody(batch, key, true);
+  }
+
+  private void assertMessageKey(final BatchException e, final MessageKeys key) {
+    assertEquals(key, e.getMessageKey());
+  }
+
+  private InputStream readFile(final String fileName) throws IOException {
+    final InputStream in = ClassLoader.class.getResourceAsStream(fileName);
+    if (in == null) {
+      throw new IOException("Requested file '" + fileName + "' was not found.");
+    }
+    return in;
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/test/java/org/apache/olingo/server/core/deserializer/BufferedReaderIncludingLineEndingsTest.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/deserializer/BufferedReaderIncludingLineEndingsTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/deserializer/BufferedReaderIncludingLineEndingsTest.java
new file mode 100644
index 0000000..d622600
--- /dev/null
+++ b/lib/server-core/src/test/java/org/apache/olingo/server/core/deserializer/BufferedReaderIncludingLineEndingsTest.java
@@ -0,0 +1,484 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.deserializer;
+
+import static org.junit.Assert.*;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+import java.util.List;
+
+import org.apache.olingo.server.core.deserializer.batch.BufferedReaderIncludingLineEndings;
+import org.apache.olingo.server.core.deserializer.batch.BufferedReaderIncludingLineEndings.Line;
+import org.junit.Test;
+
+public class BufferedReaderIncludingLineEndingsTest {
+
+
+  private static final String TEXT_COMBINED = "Test\r" +
+      "Test2\r\n" +
+      "Test3\n" +
+      "Test4\r" +
+      "\r" +
+      "\r\n" +
+      "\r\n" +
+      "Test5\n" +
+      "Test6\r\n" +
+      "Test7\n" +
+      "\n";
+
+  private static final String TEXT_SMALL = "Test\r" +
+      "123";
+  private static final String TEXT_EMPTY = "";
+
+  @Test
+  public void testSimpleText() throws IOException {
+    final String TEXT = "Test";
+    BufferedReaderIncludingLineEndings reader = create(TEXT);
+
+    assertEquals(TEXT, reader.readLine());
+    assertNull(reader.readLine());
+    assertNull(reader.readLine());
+    reader.close();
+  }
+
+  @Test
+  public void testNoText() throws IOException {
+    final String TEXT = "";
+    BufferedReaderIncludingLineEndings reader = create(TEXT);
+
+    assertNull(reader.readLine());
+    assertNull(reader.readLine());
+    reader.close();
+  }
+
+  @Test
+  public void testNoBytes() throws IOException {
+    BufferedReaderIncludingLineEndings reader =
+        new BufferedReaderIncludingLineEndings(new InputStreamReader(new ByteArrayInputStream(new byte[0])));
+
+    assertNull(reader.readLine());
+    assertNull(reader.readLine());
+    reader.close();
+  }
+
+  @Test
+  public void testCRLF() throws IOException {
+    final String TEXT = "Test\r\n" +
+        "Test2";
+
+    BufferedReaderIncludingLineEndings reader = create(TEXT);
+
+    assertEquals("Test\r\n", reader.readLine());
+    assertEquals("Test2", reader.readLine());
+    assertNull(reader.readLine());
+    assertNull(reader.readLine());
+    reader.close();
+  }
+
+  @Test
+  public void testLF() throws IOException {
+    final String TEXT = "Test\n" +
+        "Test2";
+
+    BufferedReaderIncludingLineEndings reader = create(TEXT);
+
+    assertEquals("Test\n", reader.readLine());
+    assertEquals("Test2", reader.readLine());
+    assertNull(reader.readLine());
+    assertNull(reader.readLine());
+    reader.close();
+  }
+
+  @Test
+  public void testCR() throws IOException {
+    final String TEXT = "Test\r" +
+        "Test2";
+
+    BufferedReaderIncludingLineEndings reader = create(TEXT);
+
+    assertEquals("Test\r", reader.readLine());
+    assertEquals("Test2", reader.readLine());
+    assertNull(reader.readLine());
+    assertNull(reader.readLine());
+    reader.close();
+  }
+
+  @Test
+  public void testCombined() throws IOException {
+    BufferedReaderIncludingLineEndings reader = create(TEXT_COMBINED);
+
+    assertEquals("Test\r", reader.readLine());
+    assertEquals("Test2\r\n", reader.readLine());
+    assertEquals("Test3\n", reader.readLine());
+    assertEquals("Test4\r", reader.readLine());
+    assertEquals("\r", reader.readLine());
+    assertEquals("\r\n", reader.readLine());
+    assertEquals("\r\n", reader.readLine());
+    assertEquals("Test5\n", reader.readLine());
+    assertEquals("Test6\r\n", reader.readLine());
+    assertEquals("Test7\n", reader.readLine());
+    assertEquals("\n", reader.readLine());
+    assertNull(reader.readLine());
+    assertNull(reader.readLine());
+    reader.close();
+  }
+
+  @Test
+  public void testCombinedBufferSizeTwo() throws IOException {
+    BufferedReaderIncludingLineEndings reader = create(TEXT_COMBINED, 2);
+
+    assertEquals("Test\r", reader.readLine());
+    assertEquals("Test2\r\n", reader.readLine());
+    assertEquals("Test3\n", reader.readLine());
+    assertEquals("Test4\r", reader.readLine());
+    assertEquals("\r", reader.readLine());
+    assertEquals("\r\n", reader.readLine());
+    assertEquals("\r\n", reader.readLine());
+    assertEquals("Test5\n", reader.readLine());
+    assertEquals("Test6\r\n", reader.readLine());
+    assertEquals("Test7\n", reader.readLine());
+    assertEquals("\n", reader.readLine());
+    assertNull(reader.readLine());
+    assertNull(reader.readLine());
+    reader.close();
+  }
+
+  @Test
+  public void testCombinedBufferSizeOne() throws IOException {
+    final String TEXT = "Test\r" +
+        "Test2\r\n" +
+        "Test3\n" +
+        "Test4\r" +
+        "\r" +
+        "\r\n" +
+        "\r\n" +
+        "Test5\n" +
+        "Test6\r\n" +
+        "Test7\n" +
+        "\r\n";
+
+    BufferedReaderIncludingLineEndings reader = create(TEXT, 1);
+
+    assertEquals("Test\r", reader.readLine());
+    assertEquals("Test2\r\n", reader.readLine());
+    assertEquals("Test3\n", reader.readLine());
+    assertEquals("Test4\r", reader.readLine());
+    assertEquals("\r", reader.readLine());
+    assertEquals("\r\n", reader.readLine());
+    assertEquals("\r\n", reader.readLine());
+    assertEquals("Test5\n", reader.readLine());
+    assertEquals("Test6\r\n", reader.readLine());
+    assertEquals("Test7\n", reader.readLine());
+    assertEquals("\r\n", reader.readLine());
+    assertNull(reader.readLine());
+    assertNull(reader.readLine());
+
+    reader.close();
+  }
+
+  @Test
+  public void testDoubleLF() throws IOException {
+    final String TEXT = "Test\r" +
+        "\r";
+
+    BufferedReaderIncludingLineEndings reader = create(TEXT, 1);
+
+    assertEquals("Test\r", reader.readLine());
+    assertEquals("\r", reader.readLine());
+    reader.close();
+  }
+
+  @Test
+  public void testSkipSimple() throws IOException {
+    BufferedReaderIncludingLineEndings reader = create(TEXT_SMALL);
+
+    assertEquals(5, reader.skip(5)); // Test\r
+    assertEquals("123", reader.readLine());
+    assertNull(reader.readLine());
+    assertNull(reader.readLine());
+    reader.close();
+  }
+
+  @Test
+  public void testSkipBufferOne() throws IOException {
+    BufferedReaderIncludingLineEndings reader = create(TEXT_SMALL, 1);
+
+    assertEquals(5, reader.skip(5)); // Test\r
+    assertEquals("123", reader.readLine());
+    assertNull(reader.readLine());
+    assertNull(reader.readLine());
+    reader.close();
+  }
+
+  @Test
+  public void testReadThanSkip() throws IOException {
+    final String TEXT = "Test\r" +
+        "\r" +
+        "123";
+
+    BufferedReaderIncludingLineEndings reader = create(TEXT);
+
+    assertEquals("Test\r", reader.readLine());
+    assertEquals(1, reader.skip(1)); // Test\r
+    assertEquals("123", reader.readLine());
+    assertNull(reader.readLine());
+    assertNull(reader.readLine());
+    reader.close();
+  }
+
+  @Test
+  public void testReadMoreBufferCapacityThanCharacterAvailable() throws IOException {
+    final String TEXT = "Foo";
+    char[] buffer = new char[20];
+
+    BufferedReaderIncludingLineEndings reader = create(TEXT);
+    assertEquals(3, reader.read(buffer, 0, 20));
+    assertEquals(-1, reader.read(buffer, 0, 20));
+    reader.close();
+
+    BufferedReaderIncludingLineEndings readerBufferOne = create(TEXT, 1);
+    assertEquals(3, readerBufferOne.read(buffer, 0, 20));
+    assertEquals(-1, readerBufferOne.read(buffer, 0, 20));
+    readerBufferOne.close();
+  }
+
+  @Test
+  public void testSkipZero() throws IOException {
+    final String TEXT = "Test\r" +
+        "123\r\n";
+
+    BufferedReaderIncludingLineEndings reader = create(TEXT);
+
+    assertEquals(0, reader.skip(0)); // Test\r
+    assertEquals("Test\r", reader.readLine());
+    assertEquals("123\r\n", reader.readLine());
+    assertNull(reader.readLine());
+    assertNull(reader.readLine());
+    reader.close();
+  }
+
+  @Test
+  public void testSkipToMuch() throws IOException {
+    BufferedReaderIncludingLineEndings reader = create(TEXT_SMALL);
+
+    assertEquals(8, reader.skip(10)); // Test\r
+    assertEquals(null, reader.readLine());
+    reader.close();
+  }
+
+  @Test
+  public void testReadBufferOne() throws IOException {
+    BufferedReaderIncludingLineEndings reader = create(TEXT_SMALL, 1);
+
+    assertEquals('T', reader.read());
+    assertEquals('e', reader.read());
+    assertEquals('s', reader.read());
+    assertEquals('t', reader.read());
+    assertEquals('\r', reader.read());
+    assertEquals('1', reader.read());
+    assertEquals('2', reader.read());
+    assertEquals('3', reader.read());
+    assertEquals(-1, reader.read());
+    assertEquals(-1, reader.read());
+  }
+
+  @Test
+  public void testReadZeroBytes() throws IOException {
+    BufferedReaderIncludingLineEndings reader = create(TEXT_SMALL, 1);
+
+    char[] buffer = new char[3];
+    assertEquals(0, reader.read(buffer, 0, 0));
+    assertEquals('T', reader.read());
+    assertEquals(0, reader.read(buffer, 0, 0));
+    assertEquals("est\r", reader.readLine());
+    assertEquals("123", reader.readLine());
+
+    reader.close();
+  }
+
+  @Test
+  public void testRead() throws IOException {
+    BufferedReaderIncludingLineEndings reader = create(TEXT_SMALL);
+
+    assertEquals('T', reader.read());
+    assertEquals('e', reader.read());
+    assertEquals('s', reader.read());
+    assertEquals('t', reader.read());
+    assertEquals('\r', reader.read());
+    assertEquals('1', reader.read());
+    assertEquals('2', reader.read());
+    assertEquals('3', reader.read());
+    assertEquals(-1, reader.read());
+    assertEquals(-1, reader.read());
+  }
+
+  @Test(expected = IndexOutOfBoundsException.class)
+  public void testFailReadBufferAndOffsetBiggerThanBuffer() throws IOException {
+    BufferedReaderIncludingLineEndings reader = create("");
+
+    final char[] buffer = new char[3];
+    reader.read(buffer, 1, 3);
+  }
+
+  @Test(expected = IndexOutOfBoundsException.class)
+  public void testFailLengthNegative() throws IOException {
+    final char[] buffer = new char[3];
+    BufferedReaderIncludingLineEndings reader = create("123");
+
+    reader.read(buffer, 1, -2);
+    reader.close();
+  }
+
+  @Test(expected = IndexOutOfBoundsException.class)
+  public void testFailOffsetNegative() throws IOException {
+    final char[] buffer = new char[3];
+    BufferedReaderIncludingLineEndings reader = create("123");
+
+    reader.read(buffer, -1, 2);
+    reader.close();
+  }
+
+  @Test
+  public void testReadAndReadLine() throws IOException {
+    final String TEXT = "Test\r" +
+        "bar\n" +
+        "123\r\n" +
+        "foo";
+
+    BufferedReaderIncludingLineEndings reader = create(TEXT);
+
+    assertEquals('T', reader.read());
+    assertEquals('e', reader.read());
+    assertEquals('s', reader.read());
+    assertEquals('t', reader.read());
+    assertEquals("\r", reader.readLine());
+    assertEquals("bar\n", reader.readLine());
+    assertEquals('1', reader.read());
+    assertEquals('2', reader.read());
+    assertEquals("3\r\n", reader.readLine());
+    assertEquals("foo", reader.readLine());
+    assertEquals(null, reader.readLine());
+    assertEquals(-1, reader.read());
+  }
+  
+  @Test
+  public void testLineEqualsAndHashCode() {
+    Line l1 = new Line("The first line", 1);
+    Line l2 = new Line("The first line", 1);
+    Line l3 = new Line("The second line", 2);
+    
+    assertEquals(l1, l2);
+    assertFalse(l1.equals(l3));
+    assertTrue(l1.hashCode() != l3.hashCode());
+  }
+  
+  @Test(expected = IllegalArgumentException.class)
+  public void testSkipNegative() throws IOException {
+    BufferedReaderIncludingLineEndings reader = create("123");
+    reader.skip(-1);
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testFailBufferSizeZero() throws IOException {
+    BufferedReaderIncludingLineEndings reader = create(TEXT_EMPTY, 0);
+    reader.close();
+  }
+
+  @Test(expected = NullPointerException.class)
+  public void testInputStreamIsNull() throws IOException {
+    // Same behaviour like BufferedReader
+    BufferedReaderIncludingLineEndings reader = new BufferedReaderIncludingLineEndings(null);
+    reader.close();
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testFailBufferSizeNegative() throws IOException {
+    BufferedReaderIncludingLineEndings reader = create(TEXT_EMPTY, -1);
+    reader.close();
+  }
+
+  @Test
+  public void testMarkSupoorted() throws IOException {
+    BufferedReaderIncludingLineEndings reader = create(TEXT_EMPTY);
+
+    assertEquals(false, reader.markSupported());
+    reader.close();
+  }
+
+  @Test(expected = IOException.class)
+  public void testFailMark() throws IOException {
+    BufferedReaderIncludingLineEndings reader = create("123");
+
+    reader.mark(1);
+  }
+
+  @Test(expected = IOException.class)
+  public void testFailReset() throws IOException {
+    BufferedReaderIncludingLineEndings reader = create("123");
+
+    reader.reset();
+  }
+
+  @Test
+  public void testReady() throws IOException {
+    BufferedReaderIncludingLineEndings reader = create("123\r123");
+    assertEquals(false, reader.ready());
+    assertEquals("123\r", reader.readLine());
+    assertEquals(true, reader.ready());
+    assertEquals("123", reader.readLine());
+    assertEquals(false, reader.ready());
+
+    reader.close();
+  }
+
+  @Test
+  public void testToList() throws IOException {
+    BufferedReaderIncludingLineEndings reader = create(TEXT_COMBINED);
+    List<Line> stringList = reader.toLineList();
+
+    assertEquals(11, stringList.size());
+    assertEquals("Test\r", stringList.get(0).toString());
+    assertEquals("Test2\r\n", stringList.get(1).toString());
+    assertEquals("Test3\n", stringList.get(2).toString());
+    assertEquals("Test4\r", stringList.get(3).toString());
+    assertEquals("\r", stringList.get(4).toString());
+    assertEquals("\r\n", stringList.get(5).toString());
+    assertEquals("\r\n", stringList.get(6).toString());
+    assertEquals("Test5\n", stringList.get(7).toString());
+    assertEquals("Test6\r\n", stringList.get(8).toString());
+    assertEquals("Test7\n", stringList.get(9).toString());
+    assertEquals("\n", stringList.get(10).toString());
+    reader.close();
+  }
+
+  private BufferedReaderIncludingLineEndings create(final String inputString) throws UnsupportedEncodingException {
+    return new BufferedReaderIncludingLineEndings(new InputStreamReader(new ByteArrayInputStream(inputString
+        .getBytes("UTF-8"))));
+  }
+
+  private BufferedReaderIncludingLineEndings create(final String inputString, int bufferSize)
+      throws UnsupportedEncodingException {
+    return new BufferedReaderIncludingLineEndings(new InputStreamReader(new ByteArrayInputStream(inputString
+        .getBytes("UTF-8"))), bufferSize);
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/test/java/org/apache/olingo/server/core/deserializer/HeaderTest.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/deserializer/HeaderTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/deserializer/HeaderTest.java
new file mode 100644
index 0000000..369e21e
--- /dev/null
+++ b/lib/server-core/src/test/java/org/apache/olingo/server/core/deserializer/HeaderTest.java
@@ -0,0 +1,179 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.deserializer;
+
+import static org.junit.Assert.*;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.olingo.commons.api.http.HttpContentType;
+import org.apache.olingo.commons.api.http.HttpHeader;
+import org.apache.olingo.server.core.deserializer.batch.BatchParserCommon;
+import org.apache.olingo.server.core.deserializer.batch.Header;
+import org.junit.Test;
+
+public class HeaderTest {
+
+  @Test
+  public void test() {
+    Header header = new Header(1);
+    header.addHeader(HttpHeader.CONTENT_TYPE, HttpContentType.MULTIPART_MIXED, 1);
+
+    assertEquals(HttpContentType.MULTIPART_MIXED, header.getHeader(HttpHeader.CONTENT_TYPE));
+    assertEquals(1, header.getHeaders(HttpHeader.CONTENT_TYPE).size());
+    assertEquals(HttpContentType.MULTIPART_MIXED, header.getHeaders(HttpHeader.CONTENT_TYPE).get(0));
+  }
+
+  @Test
+  public void testNotAvailable() {
+    Header header = new Header(1);
+
+    assertNull(header.getHeader(HttpHeader.CONTENT_TYPE));
+    assertEquals(0, header.getHeaders(HttpHeader.CONTENT_TYPE).size());
+    assertEquals("", header.getHeaderNotNull(HttpHeader.CONTENT_TYPE));
+  }
+
+  @Test
+  public void testCaseInsensitive() {
+    Header header = new Header(1);
+    header.addHeader(HttpHeader.CONTENT_TYPE, HttpContentType.MULTIPART_MIXED, 1);
+
+    assertEquals(HttpContentType.MULTIPART_MIXED, header.getHeader("cOnTenT-TyPE"));
+    assertEquals(1, header.getHeaders("cOnTenT-TyPE").size());
+    assertEquals(HttpContentType.MULTIPART_MIXED, header.getHeaders("cOnTenT-TyPE").get(0));
+  }
+
+  @Test
+  public void testDuplicatedAdd() {
+    Header header = new Header(1);
+    header.addHeader(HttpHeader.CONTENT_TYPE, HttpContentType.MULTIPART_MIXED, 1);
+    header.addHeader(HttpHeader.CONTENT_TYPE, HttpContentType.MULTIPART_MIXED, 2);
+
+    assertEquals(HttpContentType.MULTIPART_MIXED, header.getHeader(HttpHeader.CONTENT_TYPE));
+    assertEquals(1, header.getHeaders(HttpHeader.CONTENT_TYPE).size());
+    assertEquals(HttpContentType.MULTIPART_MIXED, header.getHeaders(HttpHeader.CONTENT_TYPE).get(0));
+  }
+
+  @Test
+  public void testMatcher() {
+    Header header = new Header(1);
+    header.addHeader(HttpHeader.CONTENT_TYPE, HttpContentType.MULTIPART_MIXED + ";boundary=123", 1);
+
+    assertTrue(header.isHeaderMatching(HttpHeader.CONTENT_TYPE, BatchParserCommon.PATTERN_MULTIPART_BOUNDARY));
+  }
+
+  @Test
+  public void testFieldName() {
+    Header header = new Header(0);
+    header.addHeader("MyFieldNamE", "myValue", 1);
+
+    assertEquals("MyFieldNamE", header.getHeaderField("myfieldname").getFieldName());
+    assertEquals("MyFieldNamE", header.toSingleMap().keySet().toArray(new String[0])[0]);
+    assertEquals("MyFieldNamE", header.toMultiMap().keySet().toArray(new String[0])[0]);
+
+    assertEquals("myValue", header.toMultiMap().get("MyFieldNamE").get(0));
+    assertEquals("myValue", header.toSingleMap().get("MyFieldNamE"));
+  }
+
+  @Test
+  public void testDeepCopy() {
+    Header header = new Header(1);
+    header.addHeader(HttpHeader.CONTENT_TYPE, HttpContentType.MULTIPART_MIXED + ";boundary=123", 1);
+
+    Header copy = header.clone();
+    assertEquals(header.getHeaders(HttpHeader.CONTENT_TYPE), copy.getHeaders(HttpHeader.CONTENT_TYPE));
+    assertEquals(header.getHeader(HttpHeader.CONTENT_TYPE), copy.getHeader(HttpHeader.CONTENT_TYPE));
+    assertEquals(header.getHeaderField(HttpHeader.CONTENT_TYPE), copy.getHeaderField(HttpHeader.CONTENT_TYPE));
+
+    assertTrue(header.getHeaders(HttpHeader.CONTENT_TYPE) != copy.getHeaders(HttpHeader.CONTENT_TYPE));
+    assertTrue(header.getHeaderField(HttpHeader.CONTENT_TYPE) != copy.getHeaderField(HttpHeader.CONTENT_TYPE));
+  }
+
+  @Test
+  public void testMatcherNoHeader() {
+    Header header = new Header(1);
+
+    assertFalse(header.isHeaderMatching(HttpHeader.CONTENT_TYPE, BatchParserCommon.PATTERN_MULTIPART_BOUNDARY));
+  }
+
+//  @Test
+//  public void testMatcherFail() {
+//    Header header = new Header(1);
+//    header.addHeader(HttpHeader.CONTENT_TYPE, HttpContentType.MULTIPART_MIXED + ";boundary=123", 1);
+//
+//    assertFalse(header.isHeaderMatching(HttpHeader.CONTENT_TYPE, BatchParserCommon.PATTERN_HEADER_LINE));
+//  }
+
+  @Test
+  public void testDuplicatedAddList() {
+    Header header = new Header(1);
+    header.addHeader(HttpHeader.CONTENT_TYPE, HttpContentType.MULTIPART_MIXED, 1);
+    header.addHeader(HttpHeader.CONTENT_TYPE, Arrays.asList(new String[] { HttpContentType.MULTIPART_MIXED,
+        HttpContentType.APPLICATION_ATOM_SVC }), 2);
+
+    assertEquals(HttpContentType.MULTIPART_MIXED + ", " + HttpContentType.APPLICATION_ATOM_SVC, header
+        .getHeader(HttpHeader.CONTENT_TYPE));
+    assertEquals(2, header.getHeaders(HttpHeader.CONTENT_TYPE).size());
+    assertEquals(HttpContentType.MULTIPART_MIXED, header.getHeaders(HttpHeader.CONTENT_TYPE).get(0));
+    assertEquals(HttpContentType.APPLICATION_ATOM_SVC, header.getHeaders(HttpHeader.CONTENT_TYPE).get(1));
+  }
+
+  @Test
+  public void testRemove() {
+    Header header = new Header(1);
+    header.addHeader(HttpHeader.CONTENT_TYPE, HttpContentType.MULTIPART_MIXED, 1);
+    header.removeHeader(HttpHeader.CONTENT_TYPE);
+
+    assertNull(header.getHeader(HttpHeader.CONTENT_TYPE));
+    assertEquals(0, header.getHeaders(HttpHeader.CONTENT_TYPE).size());
+  }
+
+  @Test
+  public void testMultipleValues() {
+    Header header = new Header(1);
+    header.addHeader(HttpHeader.CONTENT_TYPE, HttpContentType.MULTIPART_MIXED, 1);
+    header.addHeader(HttpHeader.CONTENT_TYPE, HttpContentType.APPLICATION_ATOM_SVC, 2);
+    header.addHeader(HttpHeader.CONTENT_TYPE, HttpContentType.APPLICATION_ATOM_XML, 3);
+
+    final String fullHeaderString =
+        HttpContentType.MULTIPART_MIXED + ", " + HttpContentType.APPLICATION_ATOM_SVC + ", "
+            + HttpContentType.APPLICATION_ATOM_XML;
+
+    assertEquals(fullHeaderString, header.getHeader(HttpHeader.CONTENT_TYPE));
+    assertEquals(3, header.getHeaders(HttpHeader.CONTENT_TYPE).size());
+    assertEquals(HttpContentType.MULTIPART_MIXED, header.getHeaders(HttpHeader.CONTENT_TYPE).get(0));
+    assertEquals(HttpContentType.APPLICATION_ATOM_SVC, header.getHeaders(HttpHeader.CONTENT_TYPE).get(1));
+    assertEquals(HttpContentType.APPLICATION_ATOM_XML, header.getHeaders(HttpHeader.CONTENT_TYPE).get(2));
+  }
+  
+  @Test
+  public void testSplitValues() {
+    final String values = "abc, def,123,77,   99, ysd";
+    List<String> splittedValues = Header.splitValuesByComma(values);
+
+    assertEquals(6, splittedValues.size());
+    assertEquals("abc", splittedValues.get(0));
+    assertEquals("def", splittedValues.get(1));
+    assertEquals("123", splittedValues.get(2));
+    assertEquals("77", splittedValues.get(3));
+    assertEquals("99", splittedValues.get(4));
+    assertEquals("ysd", splittedValues.get(5));
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/test/java/org/apache/olingo/server/core/deserializer/StringUtil.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/deserializer/StringUtil.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/deserializer/StringUtil.java
new file mode 100644
index 0000000..8fcebd0
--- /dev/null
+++ b/lib/server-core/src/test/java/org/apache/olingo/server/core/deserializer/StringUtil.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.deserializer;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+
+import org.apache.olingo.commons.api.ODataRuntimeException;
+import org.apache.olingo.server.core.deserializer.batch.BufferedReaderIncludingLineEndings;
+
+public class StringUtil {
+  
+  
+  public static String toString(final InputStream in) throws IOException {
+    final StringBuilder builder = new StringBuilder();
+    final BufferedReaderIncludingLineEndings reader = new BufferedReaderIncludingLineEndings(new InputStreamReader(in));
+    String currentLine;
+    
+    while((currentLine = reader.readLine()) != null) {
+      builder.append(currentLine);
+    }
+    
+    reader.close();
+    
+    return builder.toString();
+  }
+
+  public static InputStream toInputStream(final String string) {
+    try {
+      return new ByteArrayInputStream(string.getBytes("UTF-8"));
+    } catch (UnsupportedEncodingException e) {
+      throw new ODataRuntimeException("Charset UTF-8 not found");
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/test/java/org/apache/olingo/server/core/serializer/BatchResponseWriterTest.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/serializer/BatchResponseWriterTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/serializer/BatchResponseWriterTest.java
new file mode 100644
index 0000000..d44f3c3
--- /dev/null
+++ b/lib/server-core/src/test/java/org/apache/olingo/server/core/serializer/BatchResponseWriterTest.java
@@ -0,0 +1,180 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.serializer;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.olingo.commons.api.http.HttpHeader;
+import org.apache.olingo.commons.api.http.HttpStatusCode;
+import org.apache.olingo.server.api.ODataResponse;
+import org.apache.olingo.server.api.batch.BatchException;
+import org.apache.olingo.server.api.deserializer.batch.ODataResponsePart;
+import org.apache.olingo.server.core.deserializer.StringUtil;
+import org.apache.olingo.server.core.deserializer.batch.BatchParserCommon;
+import org.apache.olingo.server.core.deserializer.batch.BufferedReaderIncludingLineEndings;
+import org.apache.olingo.server.core.serializer.BatchResponseSerializer;
+import org.junit.Test;
+
+public class BatchResponseWriterTest {
+  private static final String CRLF = "\r\n";
+
+  @Test
+  public void testBatchResponse() throws IOException, BatchException {
+    final List<ODataResponsePart> parts = new ArrayList<ODataResponsePart>();
+    ODataResponse response = new ODataResponse();
+    response.setStatusCode(HttpStatusCode.OK.getStatusCode());
+    response.setHeader(HttpHeader.CONTENT_TYPE, "application/json");
+    response.setContent(StringUtil.toInputStream("Walter Winter" + CRLF));
+
+    List<ODataResponse> responses = new ArrayList<ODataResponse>(1);
+    responses.add(response);
+    parts.add(new ODataResponsePart(responses, false));
+
+    ODataResponse changeSetResponse = new ODataResponse();
+    changeSetResponse.setStatusCode(HttpStatusCode.NO_CONTENT.getStatusCode());
+    changeSetResponse.setHeader(BatchParserCommon.HTTP_CONTENT_ID, "1");
+    responses = new ArrayList<ODataResponse>(1);
+    responses.add(changeSetResponse);
+    parts.add(new ODataResponsePart(responses, true));
+
+    BatchResponseSerializer writer = new BatchResponseSerializer();
+    ODataResponse batchResponse = new ODataResponse();
+    writer.toODataResponse(parts, batchResponse);
+
+    assertEquals(202, batchResponse.getStatusCode());
+    assertNotNull(batchResponse.getContent());
+    final BufferedReaderIncludingLineEndings reader =
+        new BufferedReaderIncludingLineEndings(new InputStreamReader(batchResponse.getContent()));
+    final List<String> body = reader.toList();
+    reader.close();
+    
+    int line = 0;
+    assertEquals(25, body.size());
+    assertTrue(body.get(line++).contains("--batch_"));
+    assertEquals("Content-Type: application/http" + CRLF, body.get(line++));
+    assertEquals("Content-Transfer-Encoding: binary" + CRLF, body.get(line++));
+    assertEquals(CRLF, body.get(line++));
+    assertEquals("HTTP/1.1 200 OK" + CRLF, body.get(line++));
+    assertEquals("Content-Type: application/json" + CRLF, body.get(line++));
+    assertEquals("Content-Length: 15" + CRLF, body.get(line++));
+    assertEquals(CRLF, body.get(line++));
+    assertEquals("Walter Winter" + CRLF, body.get(line++));
+    assertEquals(CRLF, body.get(line++));
+    assertTrue(body.get(line++).contains("--batch_"));
+    assertTrue(body.get(line++).contains("Content-Type: multipart/mixed; boundary=changeset_"));
+    assertEquals(CRLF, body.get(line++));
+    assertTrue(body.get(line++).contains("--changeset_"));
+    assertEquals("Content-Type: application/http" + CRLF, body.get(line++));
+    assertEquals("Content-Transfer-Encoding: binary" + CRLF, body.get(line++));
+    assertEquals("Content-Id: 1" + CRLF, body.get(line++));
+    assertEquals(CRLF, body.get(line++));
+    assertEquals("HTTP/1.1 204 No Content" + CRLF, body.get(line++));
+    assertEquals("Content-Length: 0" + CRLF, body.get(line++));
+    assertEquals(CRLF, body.get(line++));
+    assertEquals(CRLF, body.get(line++));
+    assertTrue(body.get(line++).contains("--changeset_"));
+    assertEquals(CRLF, body.get(line++));
+    assertTrue(body.get(line++).contains("--batch_"));
+  }
+
+  @Test
+  public void testResponse() throws IOException, BatchException {
+    List<ODataResponsePart> parts = new ArrayList<ODataResponsePart>();
+    ODataResponse response = new ODataResponse();
+    response.setStatusCode(HttpStatusCode.OK.getStatusCode());
+    response.setHeader(HttpHeader.CONTENT_TYPE, "application/json");
+    response.setContent(StringUtil.toInputStream("Walter Winter"));
+
+    List<ODataResponse> responses = new ArrayList<ODataResponse>(1);
+    responses.add(response);
+    parts.add(new ODataResponsePart(responses, false));
+
+    ODataResponse batchResponse = new ODataResponse();
+    new BatchResponseSerializer().toODataResponse(parts, batchResponse);
+
+    assertEquals(202, batchResponse.getStatusCode());
+    assertNotNull(batchResponse.getContent());
+    final BufferedReaderIncludingLineEndings reader =
+        new BufferedReaderIncludingLineEndings(new InputStreamReader(batchResponse.getContent()));
+    final List<String> body = reader.toList();
+    reader.close();
+    
+    int line = 0;
+    assertEquals(10, body.size());
+    assertTrue(body.get(line++).contains("--batch_"));
+    assertEquals("Content-Type: application/http" + CRLF, body.get(line++));
+    assertEquals("Content-Transfer-Encoding: binary" + CRLF, body.get(line++));
+    assertEquals(CRLF, body.get(line++));
+    assertEquals("HTTP/1.1 200 OK" + CRLF, body.get(line++));
+    assertEquals("Content-Type: application/json" + CRLF, body.get(line++));
+    assertEquals("Content-Length: 13" + CRLF, body.get(line++));
+    assertEquals(CRLF, body.get(line++));
+    assertEquals("Walter Winter" + CRLF, body.get(line++));
+    assertTrue(body.get(line++).contains("--batch_"));
+  }
+
+  @Test
+  public void testChangeSetResponse() throws IOException, BatchException {
+    List<ODataResponsePart> parts = new ArrayList<ODataResponsePart>();
+    ODataResponse response = new ODataResponse();
+    response.setHeader(BatchParserCommon.HTTP_CONTENT_ID, "1");
+    response.setStatusCode(HttpStatusCode.NO_CONTENT.getStatusCode());
+
+    List<ODataResponse> responses = new ArrayList<ODataResponse>(1);
+    responses.add(response);
+    parts.add(new ODataResponsePart(responses, true));
+
+    BatchResponseSerializer writer = new BatchResponseSerializer();
+    ODataResponse batchResponse = new ODataResponse();
+    writer.toODataResponse(parts, batchResponse);
+
+    assertEquals(202, batchResponse.getStatusCode());
+    assertNotNull(batchResponse.getContent());
+
+    final BufferedReaderIncludingLineEndings reader =
+        new BufferedReaderIncludingLineEndings(new InputStreamReader(batchResponse.getContent()));
+    final List<String> body = reader.toList();
+    reader.close();
+    
+    int line = 0;
+    assertEquals(15, body.size());
+    assertTrue(body.get(line++).contains("--batch_"));
+    assertTrue(body.get(line++).contains("Content-Type: multipart/mixed; boundary=changeset_"));
+    assertEquals(CRLF, body.get(line++));
+    assertTrue(body.get(line++).contains("--changeset_"));
+    assertEquals("Content-Type: application/http" + CRLF, body.get(line++));
+    assertEquals("Content-Transfer-Encoding: binary" + CRLF, body.get(line++));
+    assertEquals("Content-Id: 1" + CRLF, body.get(line++));
+    assertEquals(CRLF, body.get(line++));
+    assertEquals("HTTP/1.1 204 No Content" + CRLF, body.get(line++));
+    assertEquals("Content-Length: 0" + CRLF, body.get(line++));
+    assertEquals(CRLF, body.get(line++));
+    assertEquals(CRLF, body.get(line++));
+    assertTrue(body.get(line++).contains("--changeset_"));
+    assertEquals(CRLF, body.get(line++));
+    assertTrue(body.get(line++).contains("--batch_"));
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/TechnicalServlet.java
----------------------------------------------------------------------
diff --git a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/TechnicalServlet.java b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/TechnicalServlet.java
index 78c9caa..7fa981b 100644
--- a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/TechnicalServlet.java
+++ b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/TechnicalServlet.java
@@ -18,29 +18,30 @@
  */
 package org.apache.olingo.server.tecsvc;
 
+import java.io.IOException;
+import java.net.URI;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
 import org.apache.olingo.server.api.OData;
 import org.apache.olingo.server.api.ODataHttpHandler;
 import org.apache.olingo.server.api.ServiceMetadata;
 import org.apache.olingo.server.api.edmx.EdmxReference;
 import org.apache.olingo.server.api.edmx.EdmxReferenceInclude;
 import org.apache.olingo.server.tecsvc.data.DataProvider;
+import org.apache.olingo.server.tecsvc.processor.TechnicalBatchProcessor;
 import org.apache.olingo.server.tecsvc.processor.TechnicalEntityProcessor;
 import org.apache.olingo.server.tecsvc.processor.TechnicalPrimitiveComplexProcessor;
 import org.apache.olingo.server.tecsvc.provider.EdmTechProvider;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpSession;
-
-import java.io.IOException;
-import java.net.URI;
-import java.util.Arrays;
-import java.util.List;
-
 public class TechnicalServlet extends HttpServlet {
 
   private static final long serialVersionUID = 1L;
@@ -67,6 +68,7 @@ public class TechnicalServlet extends HttpServlet {
       ODataHttpHandler handler = odata.createHandler(serviceMetadata);
       handler.register(new TechnicalEntityProcessor(dataProvider));
       handler.register(new TechnicalPrimitiveComplexProcessor(dataProvider));
+      handler.register(new TechnicalBatchProcessor(dataProvider));
       handler.process(req, resp);
     } catch (RuntimeException e) {
       LOG.error("Server Error", e);


[14/22] olingo-odata4 git commit: Api Refactoring

Posted by ch...@apache.org.
Api Refactoring

Signed-off-by: Christian Amend <ch...@apache.org>


Project: http://git-wip-us.apache.org/repos/asf/olingo-odata4/repo
Commit: http://git-wip-us.apache.org/repos/asf/olingo-odata4/commit/ad177ac1
Tree: http://git-wip-us.apache.org/repos/asf/olingo-odata4/tree/ad177ac1
Diff: http://git-wip-us.apache.org/repos/asf/olingo-odata4/diff/ad177ac1

Branch: refs/heads/master
Commit: ad177ac11e9cf43087167a9146fa0bcff0f6dc23
Parents: bc46b53
Author: Christian Holzer <c....@sap.com>
Authored: Tue Nov 11 16:50:03 2014 +0100
Committer: Christian Amend <ch...@apache.org>
Committed: Thu Nov 13 17:11:02 2014 +0100

----------------------------------------------------------------------
 .../olingo/server/api/batch/BatchOperation.java |  3 +-
 .../server/api/processor/DefaultProcessor.java  |  2 +-
 .../core/batch/handler/BatchOperationImpl.java  |  9 ++-
 .../core/batch/handler/BatchPartHandler.java    | 59 ++++++++++----------
 .../server/core/batch/parser/BatchParser.java   | 23 ++++----
 .../core/batch/BatchRequestParserTest.java      | 53 +++++++++---------
 .../batch/handler/MockedBatchHandlerTest.java   |  2 +-
 7 files changed, 73 insertions(+), 78 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/ad177ac1/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchOperation.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchOperation.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchOperation.java
index 2d09a94..a9edef0 100644
--- a/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchOperation.java
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchOperation.java
@@ -18,14 +18,13 @@
  */package org.apache.olingo.server.api.batch;
 
 import java.io.IOException;
-import java.io.InputStream;
 import java.util.List;
 
 import org.apache.olingo.server.api.ODataRequest;
 import org.apache.olingo.server.api.ODataResponse;
 
 public interface BatchOperation {
-  public List<BatchRequestPart> parseBatchRequest(InputStream in) throws BatchException;
+  public List<BatchRequestPart> parseBatchRequest(ODataRequest request, boolean isStrict) throws BatchException;
 
   public ODataResponse handleODataRequest(ODataRequest request, BatchRequestPart requestPart) throws BatchException;
 

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/ad177ac1/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/DefaultProcessor.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/DefaultProcessor.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/DefaultProcessor.java
index 25778e3..c4493a1 100644
--- a/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/DefaultProcessor.java
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/DefaultProcessor.java
@@ -107,7 +107,7 @@ public class DefaultProcessor implements MetadataProcessor, ServiceDocumentProce
     boolean continueOnError = shouldContinueOnError(request);
 
     try {
-      final List<BatchRequestPart> parts = operation.parseBatchRequest(request.getBody());
+      final List<BatchRequestPart> parts = operation.parseBatchRequest(request, true);
       final List<ODataResponsePart> responseParts = new ArrayList<ODataResponsePart>();
 
       for (BatchRequestPart part : parts) {

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/ad177ac1/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchOperationImpl.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchOperationImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchOperationImpl.java
index cf7ad7c..b5a4eb4 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchOperationImpl.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchOperationImpl.java
@@ -19,7 +19,6 @@
 package org.apache.olingo.server.core.batch.handler;
 
 import java.io.IOException;
-import java.io.InputStream;
 import java.util.List;
 
 import org.apache.olingo.commons.api.http.HttpHeader;
@@ -43,13 +42,13 @@ public class BatchOperationImpl implements BatchOperation {
       final boolean isStrict) {
     partHandler = new BatchPartHandler(oDataHandler, batchProcessor, this);
     writer = new BatchResponseWriter();
-    parser = new BatchParser(getContentType(request), request.getRawBaseUri(),
-        request.getRawServiceResolutionUri(), isStrict);
+    parser = new BatchParser();
   }
 
   @Override
-  public List<BatchRequestPart> parseBatchRequest(InputStream in) throws BatchException {
-    return parser.parseBatchRequest(in);
+  public List<BatchRequestPart> parseBatchRequest(ODataRequest request, boolean isStrict) throws BatchException {
+    return parser.parseBatchRequest(request.getBody(), getContentType(request), request.getRawBaseUri(),
+        request.getRawServiceResolutionUri(), isStrict);
   }
   
   @Override

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/ad177ac1/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchPartHandler.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchPartHandler.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchPartHandler.java
index 23ba106..2147a2e 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchPartHandler.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchPartHandler.java
@@ -6,9 +6,9 @@
  * to you under the Apache License, Version 2.0 (the
  * "License"); you may not use this file except in compliance
  * with the License. You may obtain a copy of the License at
- *
+ * 
  * http://www.apache.org/licenses/LICENSE-2.0
- *
+ * 
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@@ -18,9 +18,7 @@
  */
 package org.apache.olingo.server.core.batch.handler;
 
-import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
 
 import org.apache.olingo.commons.api.ODataRuntimeException;
@@ -43,7 +41,7 @@ public class BatchPartHandler {
   private BatchProcessor batchProcessor;
   private BatchOperation batchOperation;
   private Map<BatchRequestPart, UriMapping> uriMapping = new HashMap<BatchRequestPart, UriMapping>();
-  
+
   public BatchPartHandler(final ODataHandler oDataHandler, final BatchProcessor processor,
       final BatchOperation batchOperation) {
     this.oDataHandler = oDataHandler;
@@ -53,12 +51,12 @@ public class BatchPartHandler {
 
   public ODataResponse handleODataRequest(ODataRequest request, BatchRequestPart requestPart) throws BatchException {
     final ODataResponse response;
-    
-    if(requestPart.isChangeSet()) {
+
+    if (requestPart.isChangeSet()) {
       final UriMapping mapping = replaceReference(request, requestPart);
 
       response = oDataHandler.process(request);
-      
+
       // Store resource URI
       final String resourceUri = getODataPath(request, response);
       final String contentId = request.getHeader(BatchParserCommon.HTTP_CONTENT_ID);
@@ -67,20 +65,20 @@ public class BatchPartHandler {
     } else {
       response = oDataHandler.process(request);
     }
-    
+
     // Add content id to response
     final String contentId = request.getHeader(BatchParserCommon.HTTP_CONTENT_ID);
-    if(contentId != null) {
+    if (contentId != null) {
       response.setHeader(BatchParserCommon.HTTP_CONTENT_ID, contentId);
     }
-    
-    return  response;
+
+    return response;
   }
 
   private String getODataPath(ODataRequest request, ODataResponse response) throws BatchException {
     String resourceUri = null;
-    
-    if(request.getMethod() == HttpMethod.POST) {
+
+    if (request.getMethod() == HttpMethod.POST) {
       // Create entity
       // The URI of the new resource will be generated by the server and published in the location header
       ODataURI uri = new ODataURI(response.getHeaders().get(HttpHeader.LOCATION), request.getRawBaseUri());
@@ -90,62 +88,61 @@ public class BatchPartHandler {
       // These methods still addresses a given resource, so we use the URI given by the request
       resourceUri = request.getRawODataPath();
     }
-    
+
     return resourceUri;
   }
 
   private UriMapping replaceReference(ODataRequest request, BatchRequestPart requestPart) {
     final UriMapping mapping = getUriMappingOrDefault(requestPart);
     final String reference = BatchChangeSetSorter.getReferenceInURI(request);
-    
-    if(reference != null) {
+
+    if (reference != null) {
       final String replacement = mapping.getUri(reference);
-      
-      if(replacement != null) {
+
+      if (replacement != null) {
         BatchChangeSetSorter.replaceContentIdReference(request, reference, replacement);
       } else {
         throw new ODataRuntimeException("Required Content-Id for reference \"" + reference + "\" not found.");
       }
     }
-    
+
     return mapping;
   }
-  
+
   private UriMapping getUriMappingOrDefault(final BatchRequestPart requestPart) {
     UriMapping mapping = uriMapping.get(requestPart);
-    
-    if(mapping == null) {
+
+    if (mapping == null) {
       mapping = new UriMapping();
     }
     uriMapping.put(requestPart, mapping);
-    
+
     return mapping;
   }
-  
+
   public ODataResponsePart handleBatchRequest(BatchRequestPart request) throws BatchException {
     if (request.isChangeSet()) {
       return handleChangeSet(request);
     } else {
-      final List<ODataResponse> responses = new ArrayList<ODataResponse>();
-      responses.add(handleODataRequest(request.getRequests().get(0), request));
+      final ODataResponse response = handleODataRequest(request.getRequests().get(0), request);
       
-      return new ODataResponsePart(responses, false);
+      return new ODataResponsePart(response, false);
     }
   }
 
   private ODataResponsePart handleChangeSet(BatchRequestPart request) throws BatchException {
     final BatchChangeSetSorter sorter = new BatchChangeSetSorter(request.getRequests());
-    
+
     return batchProcessor.executeChangeSet(batchOperation, sorter.getOrderdRequests(), request);
   }
 
   private static class UriMapping {
     private Map<String, String> uriMapping = new HashMap<String, String>();
-    
+
     public void addMapping(final String contentId, final String uri) {
       uriMapping.put(contentId, uri);
     }
-    
+
     public String getUri(final String contentId) {
       return uriMapping.get(contentId);
     }

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/ad177ac1/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchParser.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchParser.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchParser.java
index 75c0084..3a89440 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchParser.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchParser.java
@@ -6,9 +6,9 @@
  * to you under the Apache License, Version 2.0 (the
  * "License"); you may not use this file except in compliance
  * with the License. You may obtain a copy of the License at
- *
+ * 
  * http://www.apache.org/licenses/LICENSE-2.0
- *
+ * 
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@@ -33,21 +33,18 @@ import org.apache.olingo.server.core.batch.transformator.BatchRequestTransformat
 
 public class BatchParser {
 
-  private final String contentTypeMime;
-  private final String baseUri;
-  private final String rawServiceResolutionUri;
-  private final boolean isStrict;
-  
-  public BatchParser(final String contentType, final String baseUri, final String serviceResolutionUri, 
-      final boolean isStrict) {
+  private String contentTypeMime;
+  private String rawServiceResolutionUri;
+  private boolean isStrict;
+
+  @SuppressWarnings("unchecked")
+  public List<BatchRequestPart> parseBatchRequest(final InputStream in, final String contentType, final String baseUri,
+      final String serviceResolutionUri, final boolean isStrict) throws BatchException {
+    
     contentTypeMime = contentType;
-    this.baseUri = BatchParserCommon.removeEndingSlash(baseUri);
     this.isStrict = isStrict;
     this.rawServiceResolutionUri = serviceResolutionUri;
-  }
 
-  @SuppressWarnings("unchecked")
-  public List<BatchRequestPart> parseBatchRequest(final InputStream in) throws BatchException {
     return (List<BatchRequestPart>) parse(in, new BatchRequestTransformator(baseUri, rawServiceResolutionUri));
   }
 

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/ad177ac1/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/BatchRequestParserTest.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/BatchRequestParserTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/BatchRequestParserTest.java
index 88fdf08..cc249be 100644
--- a/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/BatchRequestParserTest.java
+++ b/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/BatchRequestParserTest.java
@@ -6,9 +6,9 @@
  * to you under the Apache License, Version 2.0 (the
  * "License"); you may not use this file except in compliance
  * with the License. You may obtain a copy of the License at
- *
+ * 
  * http://www.apache.org/licenses/LICENSE-2.0
- *
+ * 
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@@ -196,8 +196,9 @@ public class BatchRequestParserTest {
         + "--batch_1.2+34:2j)0?" + CRLF
         + GET_REQUEST
         + "--batch_1.2+34:2j)0?--";
-    final BatchParser parser = new BatchParser(contentType, SERVICE_ROOT, "", true);
-    final List<BatchRequestPart> batchRequestParts = parser.parseBatchRequest(StringUtil.toInputStream(batch));
+    final BatchParser parser = new BatchParser();
+    final List<BatchRequestPart> batchRequestParts =
+        parser.parseBatchRequest(StringUtil.toInputStream(batch), contentType, SERVICE_ROOT, "", true);
 
     assertNotNull(batchRequestParts);
     assertFalse(batchRequestParts.isEmpty());
@@ -210,10 +211,10 @@ public class BatchRequestParserTest {
         + "--batch_1740-bb84-2f7f" + CRLF
         + GET_REQUEST
         + "--batch_1740-bb84-2f7f--";
-    final BatchParser parser = new BatchParser(invalidContentType, SERVICE_ROOT, "", true);
+    final BatchParser parser = new BatchParser();
 
     try {
-      parser.parseBatchRequest(StringUtil.toInputStream(batch));
+      parser.parseBatchRequest(StringUtil.toInputStream(batch), invalidContentType, SERVICE_ROOT, "", true);
       fail();
     } catch (BatchException e) {
       assertMessageKey(e, BatchException.MessageKeys.INVALID_CONTENT_TYPE);
@@ -224,15 +225,16 @@ public class BatchRequestParserTest {
   public void testContentTypeCharset() throws BatchException {
     final String contentType = "multipart/mixed; charset=UTF-8;boundary=batch_14d1-b293-b99a";
     final String batch = ""
-                    + "--batch_14d1-b293-b99a" + CRLF
-                    + GET_REQUEST
-                    + "--batch_14d1-b293-b99a--";
-    final BatchParser parser = new BatchParser(contentType, SERVICE_ROOT, "", true);
-    final List<BatchRequestPart> parts = parser.parseBatchRequest(StringUtil.toInputStream(batch));
-    
+        + "--batch_14d1-b293-b99a" + CRLF
+        + GET_REQUEST
+        + "--batch_14d1-b293-b99a--";
+    final BatchParser parser = new BatchParser();
+    final List<BatchRequestPart> parts =
+        parser.parseBatchRequest(StringUtil.toInputStream(batch), contentType, SERVICE_ROOT, "", true);
+
     assertEquals(1, parts.size());
   }
-  
+
   @Test
   public void testBatchWithoutBoundaryParameter() throws UnsupportedEncodingException {
     final String invalidContentType = "multipart/mixed";
@@ -240,10 +242,10 @@ public class BatchRequestParserTest {
         + "--batch_1740-bb84-2f7f" + CRLF
         + GET_REQUEST
         + "--batch_1740-bb84-2f7f--";
-    final BatchParser parser = new BatchParser(invalidContentType, SERVICE_ROOT, "", true);
+    final BatchParser parser = new BatchParser();
 
     try {
-      parser.parseBatchRequest(StringUtil.toInputStream(batch));
+      parser.parseBatchRequest(StringUtil.toInputStream(batch), invalidContentType, SERVICE_ROOT, "", true);
       fail();
     } catch (BatchException e) {
       assertMessageKey(e, BatchException.MessageKeys.INVALID_CONTENT_TYPE);
@@ -257,10 +259,10 @@ public class BatchRequestParserTest {
         + "--batch_1740-bb:84-2f7f" + CRLF
         + GET_REQUEST
         + "--batch_1740-bb:84-2f7f--";
-    final BatchParser parser = new BatchParser(invalidContentType, SERVICE_ROOT, "", true);
+    final BatchParser parser = new BatchParser();
 
     try {
-      parser.parseBatchRequest(StringUtil.toInputStream(batch));
+      parser.parseBatchRequest(StringUtil.toInputStream(batch), invalidContentType, SERVICE_ROOT, "", true);
       fail();
     } catch (BatchException e) {
       assertMessageKey(e, BatchException.MessageKeys.INVALID_BOUNDARY);
@@ -453,16 +455,16 @@ public class BatchRequestParserTest {
 
     parseInvalidBatchBody(batch, BatchException.MessageKeys.MISSING_CLOSE_DELIMITER);
   }
-  
+
   @Test
   public void testEmptyRequest() throws BatchException, UnsupportedEncodingException {
     final String batch = ""
         + "--batch_8194-cf13-1f56--";
-    
+
     final List<BatchRequestPart> parts = parse(batch);
     assertEquals(0, parts.size());
   }
-  
+
   @Test
   public void testBadRequest() throws UnsupportedEncodingException {
     final String batch = "This is a bad request. There is no syntax and also no semantic";
@@ -523,7 +525,7 @@ public class BatchRequestParserTest {
 
     final List<BatchRequestPart> parts = parse(batch);
     assertEquals(1, parts.size());
-    
+
     final BatchRequestPart part = parts.get(0);
     assertTrue(part.isChangeSet());
     assertEquals(0, part.getRequests().size());
@@ -1272,8 +1274,9 @@ public class BatchRequestParserTest {
   }
 
   private List<BatchRequestPart> parse(final InputStream in, final boolean isStrict) throws BatchException {
-    final BatchParser parser = new BatchParser(CONTENT_TYPE, SERVICE_ROOT, "", isStrict);
-    final List<BatchRequestPart> batchRequestParts = parser.parseBatchRequest(in);
+    final BatchParser parser = new BatchParser();
+    final List<BatchRequestPart> batchRequestParts =
+        parser.parseBatchRequest(in, CONTENT_TYPE, SERVICE_ROOT, "", isStrict);
 
     assertNotNull(batchRequestParts);
 
@@ -1295,10 +1298,10 @@ public class BatchRequestParserTest {
 
   private void parseInvalidBatchBody(final String batch, final MessageKeys key, final boolean isStrict)
       throws UnsupportedEncodingException {
-    final BatchParser parser = new BatchParser(CONTENT_TYPE, SERVICE_ROOT, "", isStrict);
+    final BatchParser parser = new BatchParser();
 
     try {
-      parser.parseBatchRequest(StringUtil.toInputStream(batch));
+      parser.parseBatchRequest(StringUtil.toInputStream(batch), CONTENT_TYPE, SERVICE_ROOT, "", isStrict);
       fail("No exception thrown. Expect: " + key.toString());
     } catch (BatchException e) {
       assertMessageKey(e, key);

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/ad177ac1/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/MockedBatchHandlerTest.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/MockedBatchHandlerTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/MockedBatchHandlerTest.java
index b81edb8..c68919c 100644
--- a/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/MockedBatchHandlerTest.java
+++ b/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/MockedBatchHandlerTest.java
@@ -593,7 +593,7 @@ public class MockedBatchHandlerTest {
     @Override
     public void executeBatch(BatchOperation operation, ODataRequest request, ODataResponse response) {
       try {
-        final List<BatchRequestPart> parts = operation.parseBatchRequest(request.getBody());
+        final List<BatchRequestPart> parts = operation.parseBatchRequest(request, true);
         final List<ODataResponsePart> responseParts = new ArrayList<ODataResponsePart>();
 
         for (BatchRequestPart part : parts) {


[03/22] olingo-odata4 git commit: [OLINGO-472] BatchParser first draft

Posted by ch...@apache.org.
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/32247295/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/parser/HeaderTest.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/parser/HeaderTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/parser/HeaderTest.java
new file mode 100644
index 0000000..f38ac89
--- /dev/null
+++ b/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/parser/HeaderTest.java
@@ -0,0 +1,179 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.batch.parser;
+
+import static org.junit.Assert.*;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.olingo.commons.api.http.HttpContentType;
+import org.apache.olingo.commons.api.http.HttpHeader;
+import org.apache.olingo.server.core.batch.parser.BatchParserCommon;
+import org.apache.olingo.server.core.batch.parser.Header;
+import org.junit.Test;
+
+public class HeaderTest {
+
+  @Test
+  public void test() {
+    Header header = new Header(1);
+    header.addHeader(HttpHeader.CONTENT_TYPE, HttpContentType.MULTIPART_MIXED, 1);
+
+    assertEquals(HttpContentType.MULTIPART_MIXED, header.getHeader(HttpHeader.CONTENT_TYPE));
+    assertEquals(1, header.getHeaders(HttpHeader.CONTENT_TYPE).size());
+    assertEquals(HttpContentType.MULTIPART_MIXED, header.getHeaders(HttpHeader.CONTENT_TYPE).get(0));
+  }
+
+  @Test
+  public void testNotAvailable() {
+    Header header = new Header(1);
+
+    assertNull(header.getHeader(HttpHeader.CONTENT_TYPE));
+    assertEquals(0, header.getHeaders(HttpHeader.CONTENT_TYPE).size());
+    assertEquals("", header.getHeaderNotNull(HttpHeader.CONTENT_TYPE));
+  }
+
+  @Test
+  public void testCaseInsensitive() {
+    Header header = new Header(1);
+    header.addHeader(HttpHeader.CONTENT_TYPE, HttpContentType.MULTIPART_MIXED, 1);
+
+    assertEquals(HttpContentType.MULTIPART_MIXED, header.getHeader("cOnTenT-TyPE"));
+    assertEquals(1, header.getHeaders("cOnTenT-TyPE").size());
+    assertEquals(HttpContentType.MULTIPART_MIXED, header.getHeaders("cOnTenT-TyPE").get(0));
+  }
+
+  @Test
+  public void testDuplicatedAdd() {
+    Header header = new Header(1);
+    header.addHeader(HttpHeader.CONTENT_TYPE, HttpContentType.MULTIPART_MIXED, 1);
+    header.addHeader(HttpHeader.CONTENT_TYPE, HttpContentType.MULTIPART_MIXED, 2);
+
+    assertEquals(HttpContentType.MULTIPART_MIXED, header.getHeader(HttpHeader.CONTENT_TYPE));
+    assertEquals(1, header.getHeaders(HttpHeader.CONTENT_TYPE).size());
+    assertEquals(HttpContentType.MULTIPART_MIXED, header.getHeaders(HttpHeader.CONTENT_TYPE).get(0));
+  }
+
+  @Test
+  public void testMatcher() {
+    Header header = new Header(1);
+    header.addHeader(HttpHeader.CONTENT_TYPE, HttpContentType.MULTIPART_MIXED + ";boundary=123", 1);
+
+    assertTrue(header.isHeaderMatching(HttpHeader.CONTENT_TYPE, BatchParserCommon.PATTERN_MULTIPART_BOUNDARY));
+  }
+
+  @Test
+  public void testFieldName() {
+    Header header = new Header(0);
+    header.addHeader("MyFieldNamE", "myValue", 1);
+
+    assertEquals("MyFieldNamE", header.getHeaderField("myfieldname").getFieldName());
+    assertEquals("MyFieldNamE", header.toSingleMap().keySet().toArray(new String[0])[0]);
+    assertEquals("MyFieldNamE", header.toMultiMap().keySet().toArray(new String[0])[0]);
+
+    assertEquals("myValue", header.toMultiMap().get("MyFieldNamE").get(0));
+    assertEquals("myValue", header.toSingleMap().get("MyFieldNamE"));
+  }
+
+  @Test
+  public void testDeepCopy() {
+    Header header = new Header(1);
+    header.addHeader(HttpHeader.CONTENT_TYPE, HttpContentType.MULTIPART_MIXED + ";boundary=123", 1);
+
+    Header copy = header.clone();
+    assertEquals(header.getHeaders(HttpHeader.CONTENT_TYPE), copy.getHeaders(HttpHeader.CONTENT_TYPE));
+    assertEquals(header.getHeader(HttpHeader.CONTENT_TYPE), copy.getHeader(HttpHeader.CONTENT_TYPE));
+    assertEquals(header.getHeaderField(HttpHeader.CONTENT_TYPE), copy.getHeaderField(HttpHeader.CONTENT_TYPE));
+
+    assertTrue(header.getHeaders(HttpHeader.CONTENT_TYPE) != copy.getHeaders(HttpHeader.CONTENT_TYPE));
+    assertTrue(header.getHeaderField(HttpHeader.CONTENT_TYPE) != copy.getHeaderField(HttpHeader.CONTENT_TYPE));
+  }
+
+  @Test
+  public void testMatcherNoHeader() {
+    Header header = new Header(1);
+
+    assertFalse(header.isHeaderMatching(HttpHeader.CONTENT_TYPE, BatchParserCommon.PATTERN_MULTIPART_BOUNDARY));
+  }
+
+//  @Test
+//  public void testMatcherFail() {
+//    Header header = new Header(1);
+//    header.addHeader(HttpHeader.CONTENT_TYPE, HttpContentType.MULTIPART_MIXED + ";boundary=123", 1);
+//
+//    assertFalse(header.isHeaderMatching(HttpHeader.CONTENT_TYPE, BatchParserCommon.PATTERN_HEADER_LINE));
+//  }
+
+  @Test
+  public void testDuplicatedAddList() {
+    Header header = new Header(1);
+    header.addHeader(HttpHeader.CONTENT_TYPE, HttpContentType.MULTIPART_MIXED, 1);
+    header.addHeader(HttpHeader.CONTENT_TYPE, Arrays.asList(new String[] { HttpContentType.MULTIPART_MIXED,
+        HttpContentType.APPLICATION_ATOM_SVC }), 2);
+
+    assertEquals(HttpContentType.MULTIPART_MIXED + ", " + HttpContentType.APPLICATION_ATOM_SVC, header
+        .getHeader(HttpHeader.CONTENT_TYPE));
+    assertEquals(2, header.getHeaders(HttpHeader.CONTENT_TYPE).size());
+    assertEquals(HttpContentType.MULTIPART_MIXED, header.getHeaders(HttpHeader.CONTENT_TYPE).get(0));
+    assertEquals(HttpContentType.APPLICATION_ATOM_SVC, header.getHeaders(HttpHeader.CONTENT_TYPE).get(1));
+  }
+
+  @Test
+  public void testRemove() {
+    Header header = new Header(1);
+    header.addHeader(HttpHeader.CONTENT_TYPE, HttpContentType.MULTIPART_MIXED, 1);
+    header.removeHeader(HttpHeader.CONTENT_TYPE);
+
+    assertNull(header.getHeader(HttpHeader.CONTENT_TYPE));
+    assertEquals(0, header.getHeaders(HttpHeader.CONTENT_TYPE).size());
+  }
+
+  @Test
+  public void testMultipleValues() {
+    Header header = new Header(1);
+    header.addHeader(HttpHeader.CONTENT_TYPE, HttpContentType.MULTIPART_MIXED, 1);
+    header.addHeader(HttpHeader.CONTENT_TYPE, HttpContentType.APPLICATION_ATOM_SVC, 2);
+    header.addHeader(HttpHeader.CONTENT_TYPE, HttpContentType.APPLICATION_ATOM_XML, 3);
+
+    final String fullHeaderString =
+        HttpContentType.MULTIPART_MIXED + ", " + HttpContentType.APPLICATION_ATOM_SVC + ", "
+            + HttpContentType.APPLICATION_ATOM_XML;
+
+    assertEquals(fullHeaderString, header.getHeader(HttpHeader.CONTENT_TYPE));
+    assertEquals(3, header.getHeaders(HttpHeader.CONTENT_TYPE).size());
+    assertEquals(HttpContentType.MULTIPART_MIXED, header.getHeaders(HttpHeader.CONTENT_TYPE).get(0));
+    assertEquals(HttpContentType.APPLICATION_ATOM_SVC, header.getHeaders(HttpHeader.CONTENT_TYPE).get(1));
+    assertEquals(HttpContentType.APPLICATION_ATOM_XML, header.getHeaders(HttpHeader.CONTENT_TYPE).get(2));
+  }
+  
+  @Test
+  public void testSplitValues() {
+    final String values = "abc, def,123,77,   99, ysd";
+    List<String> splittedValues = Header.splitValuesByComma(values);
+
+    assertEquals(6, splittedValues.size());
+    assertEquals("abc", splittedValues.get(0));
+    assertEquals("def", splittedValues.get(1));
+    assertEquals("123", splittedValues.get(2));
+    assertEquals("77", splittedValues.get(3));
+    assertEquals("99", splittedValues.get(4));
+    assertEquals("ysd", splittedValues.get(5));
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/32247295/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/writer/BatchResponseWriterTest.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/writer/BatchResponseWriterTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/writer/BatchResponseWriterTest.java
new file mode 100644
index 0000000..ec45a22
--- /dev/null
+++ b/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/writer/BatchResponseWriterTest.java
@@ -0,0 +1,179 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.batch.writer;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.olingo.commons.api.http.HttpHeader;
+import org.apache.olingo.commons.api.http.HttpStatusCode;
+import org.apache.olingo.server.api.ODataResponse;
+import org.apache.olingo.server.api.batch.ODataResponsePart;
+import org.apache.olingo.server.core.batch.BatchException;
+import org.apache.olingo.server.core.batch.StringUtil;
+import org.apache.olingo.server.core.batch.parser.BatchParserCommon;
+import org.apache.olingo.server.core.batch.parser.BufferedReaderIncludingLineEndings;
+import org.junit.Test;
+
+public class BatchResponseWriterTest {
+  private static final String CRLF = "\r\n";
+
+  @Test
+  public void testBatchResponse() throws IOException, BatchException {
+    final List<ODataResponsePart> parts = new ArrayList<ODataResponsePart>();
+    ODataResponse response = new ODataResponse();
+    response.setStatusCode(HttpStatusCode.OK.getStatusCode());
+    response.setHeader(HttpHeader.CONTENT_TYPE, "application/json");
+    response.setContent(StringUtil.toInputStream("Walter Winter" + CRLF));
+
+    List<ODataResponse> responses = new ArrayList<ODataResponse>(1);
+    responses.add(response);
+    parts.add(new ODataResponsePartImpl(responses, false));
+
+    ODataResponse changeSetResponse = new ODataResponse();
+    changeSetResponse.setStatusCode(HttpStatusCode.NO_CONTENT.getStatusCode());
+    changeSetResponse.setHeader(BatchParserCommon.HTTP_CONTENT_ID, "1");
+    responses = new ArrayList<ODataResponse>(1);
+    responses.add(changeSetResponse);
+    parts.add(new ODataResponsePartImpl(responses, true));
+
+    BatchResponseWriter writer = new BatchResponseWriter();
+    ODataResponse batchResponse = new ODataResponse();
+    writer.toODataResponse(parts, batchResponse);
+
+    assertEquals(202, batchResponse.getStatusCode());
+    assertNotNull(batchResponse.getContent());
+    final BufferedReaderIncludingLineEndings reader =
+        new BufferedReaderIncludingLineEndings(new InputStreamReader(batchResponse.getContent()));
+    final List<String> body = reader.toList();
+    reader.close();
+    
+    int line = 0;
+    assertEquals(25, body.size());
+    assertTrue(body.get(line++).contains("--batch_"));
+    assertEquals("Content-Type: application/http" + CRLF, body.get(line++));
+    assertEquals("Content-Transfer-Encoding: binary" + CRLF, body.get(line++));
+    assertEquals(CRLF, body.get(line++));
+    assertEquals("HTTP/1.1 200 OK" + CRLF, body.get(line++));
+    assertEquals("Content-Type: application/json" + CRLF, body.get(line++));
+    assertEquals("Content-Length: 15" + CRLF, body.get(line++));
+    assertEquals(CRLF, body.get(line++));
+    assertEquals("Walter Winter" + CRLF, body.get(line++));
+    assertEquals(CRLF, body.get(line++));
+    assertTrue(body.get(line++).contains("--batch_"));
+    assertTrue(body.get(line++).contains("Content-Type: multipart/mixed; boundary=changeset_"));
+    assertEquals(CRLF, body.get(line++));
+    assertTrue(body.get(line++).contains("--changeset_"));
+    assertEquals("Content-Type: application/http" + CRLF, body.get(line++));
+    assertEquals("Content-Transfer-Encoding: binary" + CRLF, body.get(line++));
+    assertEquals("Content-Id: 1" + CRLF, body.get(line++));
+    assertEquals(CRLF, body.get(line++));
+    assertEquals("HTTP/1.1 204 No Content" + CRLF, body.get(line++));
+    assertEquals("Content-Length: 0" + CRLF, body.get(line++));
+    assertEquals(CRLF, body.get(line++));
+    assertEquals(CRLF, body.get(line++));
+    assertTrue(body.get(line++).contains("--changeset_"));
+    assertEquals(CRLF, body.get(line++));
+    assertTrue(body.get(line++).contains("--batch_"));
+  }
+
+  @Test
+  public void testResponse() throws IOException, BatchException {
+    List<ODataResponsePart> parts = new ArrayList<ODataResponsePart>();
+    ODataResponse response = new ODataResponse();
+    response.setStatusCode(HttpStatusCode.OK.getStatusCode());
+    response.setHeader(HttpHeader.CONTENT_TYPE, "application/json");
+    response.setContent(StringUtil.toInputStream("Walter Winter"));
+
+    List<ODataResponse> responses = new ArrayList<ODataResponse>(1);
+    responses.add(response);
+    parts.add(new ODataResponsePartImpl(responses, false));
+
+    ODataResponse batchResponse = new ODataResponse();
+    new BatchResponseWriter().toODataResponse(parts, batchResponse);
+
+    assertEquals(202, batchResponse.getStatusCode());
+    assertNotNull(batchResponse.getContent());
+    final BufferedReaderIncludingLineEndings reader =
+        new BufferedReaderIncludingLineEndings(new InputStreamReader(batchResponse.getContent()));
+    final List<String> body = reader.toList();
+    reader.close();
+    
+    int line = 0;
+    assertEquals(10, body.size());
+    assertTrue(body.get(line++).contains("--batch_"));
+    assertEquals("Content-Type: application/http" + CRLF, body.get(line++));
+    assertEquals("Content-Transfer-Encoding: binary" + CRLF, body.get(line++));
+    assertEquals(CRLF, body.get(line++));
+    assertEquals("HTTP/1.1 200 OK" + CRLF, body.get(line++));
+    assertEquals("Content-Type: application/json" + CRLF, body.get(line++));
+    assertEquals("Content-Length: 13" + CRLF, body.get(line++));
+    assertEquals(CRLF, body.get(line++));
+    assertEquals("Walter Winter" + CRLF, body.get(line++));
+    assertTrue(body.get(line++).contains("--batch_"));
+  }
+
+  @Test
+  public void testChangeSetResponse() throws IOException, BatchException {
+    List<ODataResponsePart> parts = new ArrayList<ODataResponsePart>();
+    ODataResponse response = new ODataResponse();
+    response.setHeader(BatchParserCommon.HTTP_CONTENT_ID, "1");
+    response.setStatusCode(HttpStatusCode.NO_CONTENT.getStatusCode());
+
+    List<ODataResponse> responses = new ArrayList<ODataResponse>(1);
+    responses.add(response);
+    parts.add(new ODataResponsePartImpl(responses, true));
+
+    BatchResponseWriter writer = new BatchResponseWriter();
+    ODataResponse batchResponse = new ODataResponse();
+    writer.toODataResponse(parts, batchResponse);
+
+    assertEquals(202, batchResponse.getStatusCode());
+    assertNotNull(batchResponse.getContent());
+
+    final BufferedReaderIncludingLineEndings reader =
+        new BufferedReaderIncludingLineEndings(new InputStreamReader(batchResponse.getContent()));
+    final List<String> body = reader.toList();
+    reader.close();
+    
+    int line = 0;
+    assertEquals(15, body.size());
+    assertTrue(body.get(line++).contains("--batch_"));
+    assertTrue(body.get(line++).contains("Content-Type: multipart/mixed; boundary=changeset_"));
+    assertEquals(CRLF, body.get(line++));
+    assertTrue(body.get(line++).contains("--changeset_"));
+    assertEquals("Content-Type: application/http" + CRLF, body.get(line++));
+    assertEquals("Content-Transfer-Encoding: binary" + CRLF, body.get(line++));
+    assertEquals("Content-Id: 1" + CRLF, body.get(line++));
+    assertEquals(CRLF, body.get(line++));
+    assertEquals("HTTP/1.1 204 No Content" + CRLF, body.get(line++));
+    assertEquals("Content-Length: 0" + CRLF, body.get(line++));
+    assertEquals(CRLF, body.get(line++));
+    assertEquals(CRLF, body.get(line++));
+    assertTrue(body.get(line++).contains("--changeset_"));
+    assertEquals(CRLF, body.get(line++));
+    assertTrue(body.get(line++).contains("--batch_"));
+  }
+}


[16/22] olingo-odata4 git commit: [OLINGO-472] Batch Refactoring

Posted by ch...@apache.org.
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalBatchProcessor.java
----------------------------------------------------------------------
diff --git a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalBatchProcessor.java b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalBatchProcessor.java
new file mode 100644
index 0000000..b1dce91
--- /dev/null
+++ b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalBatchProcessor.java
@@ -0,0 +1,123 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.tecsvc.processor;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.olingo.commons.api.ODataRuntimeException;
+import org.apache.olingo.server.api.ODataRequest;
+import org.apache.olingo.server.api.ODataResponse;
+import org.apache.olingo.server.api.batch.BatchException;
+import org.apache.olingo.server.api.batch.BatchFacade;
+import org.apache.olingo.server.api.deserializer.batch.BatchRequestPart;
+import org.apache.olingo.server.api.deserializer.batch.ODataResponsePart;
+import org.apache.olingo.server.api.processor.BatchProcessor;
+import org.apache.olingo.server.api.serializer.SerializerException;
+import org.apache.olingo.server.tecsvc.data.DataProvider;
+
+public class TechnicalBatchProcessor extends TechnicalProcessor implements BatchProcessor {
+
+  // TODO: Move to different location
+  private static final String PREFERENCE_CONTINUE_ON_ERROR = "odata.continue-on-error";
+  private static final String PREFER_HEADER = "Prefer";
+
+  public TechnicalBatchProcessor(DataProvider dataProvider) {
+    super(dataProvider);
+  }
+
+  @Override
+  public void executeBatch(BatchFacade operation, ODataRequest request, ODataResponse response)
+      throws SerializerException, BatchException {
+    boolean continueOnError = shouldContinueOnError(request);
+
+    try {
+
+      final List<BatchRequestPart> parts = odata.createFixedFormatDeserializer().parseBatchRequest(request, true);
+      final List<ODataResponsePart> responseParts = new ArrayList<ODataResponsePart>();
+
+      for (BatchRequestPart part : parts) {
+        final ODataResponsePart responsePart = operation.handleBatchRequest(part);
+        responseParts.add(responsePart); // Also add failed responses
+
+        if (responsePart.getResponses().get(0).getStatusCode() >= 400
+            && !continueOnError) {
+
+          // Perform some additions actions
+          // ...
+
+          break; // Stop processing, but serialize all recent requests
+        }
+      }
+
+      odata.createFixedFormatSerializer().writeResponseParts(responseParts, response); // Serialize responses
+    } catch (IOException e) {
+      throw new ODataRuntimeException(e);
+    }
+  }
+
+  private boolean shouldContinueOnError(ODataRequest request) {
+    final List<String> preferValues = request.getHeaders(PREFER_HEADER);
+
+    if (preferValues != null) {
+      for (final String preference : preferValues) {
+        if (PREFERENCE_CONTINUE_ON_ERROR.equals(preference)) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  @Override
+  public ODataResponsePart executeChangeSet(BatchFacade operation, List<ODataRequest> requests,
+      BatchRequestPart requestPart) {
+    List<ODataResponse> responses = new ArrayList<ODataResponse>();
+
+    for (ODataRequest request : requests) {
+      try {
+        final ODataResponse oDataResponse = operation.handleODataRequest(request, requestPart);
+
+        if (oDataResponse.getStatusCode() < 400) {
+          responses.add(oDataResponse);
+        } else {
+          // Rollback
+          // ...
+
+          // OData Version 4.0 Part 1: Protocol Plus Errata 01
+          // 11.7.4 Responding to a Batch Request
+          //
+          // When a request within a change set fails, the change set response is not represented using
+          // the multipart/mixed media type. Instead, a single response, using the application/http media type
+          // and a Content-Transfer-Encoding header with a value of binary, is returned that applies to all requests
+          // in the change set and MUST be formatted according to the Error Handling defined
+          // for the particular response format.
+
+          return new ODataResponsePart(oDataResponse, false);
+        }
+      } catch (BatchException e) {
+        throw new ODataRuntimeException(e);
+      }
+    }
+
+    return new ODataResponsePart(responses, true);
+  }
+
+}


[06/22] olingo-odata4 git commit: Tech Servlet

Posted by ch...@apache.org.
Tech Servlet

Signed-off-by: Christian Amend <ch...@apache.org>


Project: http://git-wip-us.apache.org/repos/asf/olingo-odata4/repo
Commit: http://git-wip-us.apache.org/repos/asf/olingo-odata4/commit/15bd1526
Tree: http://git-wip-us.apache.org/repos/asf/olingo-odata4/tree/15bd1526
Diff: http://git-wip-us.apache.org/repos/asf/olingo-odata4/diff/15bd1526

Branch: refs/heads/master
Commit: 15bd15267a36ab2318e0824f6fcbb06f3d7a3351
Parents: 3224729
Author: Christian Holzer <c....@sap.com>
Authored: Tue Oct 28 16:55:57 2014 +0100
Committer: Christian Amend <ch...@apache.org>
Committed: Thu Nov 13 17:10:54 2014 +0100

----------------------------------------------------------------------
 .../olingo/server/api/batch/BatchException.java |  65 +++++++++
 .../olingo/server/api/batch/BatchOperation.java |  36 +++++
 .../server/api/processor/BatchProcessor.java    |  31 ++++
 .../apache/olingo/server/core/ODataHandler.java |  12 +-
 .../server/core/batch/BatchException.java       |  65 ---------
 .../batch/handler/BatchChangeSetSorter.java     | 145 +++++++++++++++++++
 .../server/core/batch/handler/BatchHandler.java |  68 +++++++++
 .../core/batch/handler/BatchOperationImpl.java  |  74 ++++++++++
 .../core/batch/handler/BatchPartHandler.java    |  66 +++++++++
 .../batch/handler/ODataResponsePartImpl.java    |  44 ++++++
 .../server/core/batch/parser/BatchBodyPart.java |   2 +-
 .../core/batch/parser/BatchChangeSetPart.java   |   2 +-
 .../server/core/batch/parser/BatchParser.java   |   2 +-
 .../core/batch/parser/BatchParserCommon.java    |   5 +-
 .../core/batch/parser/BatchQueryOperation.java  |   2 +-
 .../BatchRequestTransformator.java              |   8 +-
 .../batch/transformator/BatchTransformator.java |   2 +-
 .../transformator/BatchTransformatorCommon.java |   4 +-
 .../core/batch/writer/BatchResponseWriter.java  |   4 +-
 .../core/batch/BatchRequestParserTest.java      |   3 +-
 .../batch/parser/BatchParserCommonTest.java     |   2 +-
 .../batch/writer/BatchResponseWriterTest.java   |   2 +-
 .../tecsvc/processor/TechnicalProcessor.java    |  39 ++++-
 23 files changed, 598 insertions(+), 85 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/15bd1526/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchException.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchException.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchException.java
new file mode 100644
index 0000000..8da47a8
--- /dev/null
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchException.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.api.batch;
+
+import org.apache.olingo.server.api.ODataTranslatedException;
+
+public class BatchException extends ODataTranslatedException {
+  public static enum MessageKeys implements MessageKey {
+    INVALID_BOUNDARY,
+    INVALID_CHANGESET_METHOD,
+    INVALID_CONTENT,
+    INVALID_CONTENT_LENGTH,
+    INVALID_CONTENT_TRANSFER_ENCODING,
+    INVALID_CONTENT_TYPE,
+    INVALID_HEADER,
+    INVALID_HTTP_VERSION,
+    INVALID_METHOD,
+    INVALID_QUERY_OPERATION_METHOD,
+    INVALID_STATUS_LINE,
+    INVALID_URI,
+    MISSING_BLANK_LINE,
+    MISSING_BOUNDARY_DELIMITER,
+    MISSING_CLOSE_DELIMITER,
+    MISSING_CONTENT_ID,
+    MISSING_CONTENT_TRANSFER_ENCODING,
+    MISSING_CONTENT_TYPE,
+    MISSING_MANDATORY_HEADER, FORBIDDEN_HEADER, INVALID_CONTENT_ID;
+
+    @Override
+    public String getKey() {
+      return name();
+    }
+  }
+
+  private static final long serialVersionUID = -907752788975531134L;
+
+  public BatchException(final String developmentMessage, final MessageKey messageKey, final int lineNumber) {
+    this(developmentMessage, messageKey, "" + lineNumber);
+  }
+
+  public BatchException(final String developmentMessage, final MessageKey messageKey, final String... parameters) {
+    super(developmentMessage, messageKey, parameters);
+  }
+
+  @Override
+  protected String getBundleName() {
+    return DEFAULT_SERVER_BUNDLE_NAME;
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/15bd1526/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchOperation.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchOperation.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchOperation.java
new file mode 100644
index 0000000..8e438cf
--- /dev/null
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchOperation.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */package org.apache.olingo.server.api.batch;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+
+import org.apache.olingo.server.api.ODataRequest;
+import org.apache.olingo.server.api.ODataResponse;
+
+public interface BatchOperation {
+  public List<BatchRequestPart> parseBatchRequest(InputStream in) throws BatchException;
+
+  public ODataResponse handleODataRequest(ODataRequest request);
+
+  public ODataResponsePart handleBatchRequest(BatchRequestPart request);
+
+  public void writeResponseParts(List<ODataResponsePart> batchResponses, ODataResponse response) throws BatchException,
+      IOException;
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/15bd1526/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/BatchProcessor.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/BatchProcessor.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/BatchProcessor.java
new file mode 100644
index 0000000..5a9518d
--- /dev/null
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/BatchProcessor.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.api.processor;
+
+import java.util.List;
+
+import org.apache.olingo.server.api.ODataRequest;
+import org.apache.olingo.server.api.ODataResponse;
+import org.apache.olingo.server.api.batch.BatchOperation;
+
+public interface BatchProcessor extends Processor {
+  void executeBatch(BatchOperation operation, ODataRequest requst, ODataResponse response);
+
+  List<ODataResponse> executeChangeSet(BatchOperation operation, List<ODataRequest> requests);
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/15bd1526/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataHandler.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataHandler.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataHandler.java
index a13ef6f..48d75de 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataHandler.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataHandler.java
@@ -35,6 +35,8 @@ import org.apache.olingo.server.api.ODataRequest;
 import org.apache.olingo.server.api.ODataResponse;
 import org.apache.olingo.server.api.ODataServerError;
 import org.apache.olingo.server.api.ServiceMetadata;
+import org.apache.olingo.server.api.batch.BatchException;
+import org.apache.olingo.server.api.processor.BatchProcessor;
 import org.apache.olingo.server.api.processor.DefaultProcessor;
 import org.apache.olingo.server.api.processor.EntitySetProcessor;
 import org.apache.olingo.server.api.processor.EntityProcessor;
@@ -52,6 +54,7 @@ import org.apache.olingo.server.api.uri.UriResourceKind;
 import org.apache.olingo.server.api.uri.UriResourceNavigation;
 import org.apache.olingo.server.api.uri.UriResourcePartTyped;
 import org.apache.olingo.server.api.uri.UriResourceProperty;
+import org.apache.olingo.server.core.batch.handler.BatchHandler;
 import org.apache.olingo.server.core.uri.parser.Parser;
 import org.apache.olingo.server.core.uri.parser.UriParserException;
 import org.apache.olingo.server.core.uri.parser.UriParserSemanticException;
@@ -115,7 +118,7 @@ public class ODataHandler {
 
   private void processInternal(final ODataRequest request, final ODataResponse response)
       throws ODataHandlerException, UriParserException, UriValidationException, ContentNegotiatorException,
-      ODataApplicationException, SerializerException {
+      ODataApplicationException, SerializerException, BatchException {
     validateODataVersion(request, response);
 
     uriInfo = new Parser().parseUri(request.getRawODataPath(), request.getRawQueryPath(), null,
@@ -158,6 +161,13 @@ public class ODataHandler {
     case resource:
       handleResourceDispatching(request, response);
       break;
+    case batch:
+      BatchProcessor bp = selectProcessor(BatchProcessor.class);
+      
+      final BatchHandler handler = new BatchHandler(this, request, bp, true);
+      handler.process(response);
+      
+      break;
     default:
       throw new ODataHandlerException("not implemented",
           ODataHandlerException.MessageKeys.FUNCTIONALITY_NOT_IMPLEMENTED);

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/15bd1526/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/BatchException.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/BatchException.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/BatchException.java
deleted file mode 100644
index aafe141..0000000
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/BatchException.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.olingo.server.core.batch;
-
-import org.apache.olingo.server.api.ODataTranslatedException;
-
-public class BatchException extends ODataTranslatedException {
-  public static enum MessageKeys implements MessageKey {
-    INVALID_BOUNDARY,
-    INVALID_CHANGESET_METHOD,
-    INVALID_CONTENT,
-    INVALID_CONTENT_LENGTH,
-    INVALID_CONTENT_TRANSFER_ENCODING,
-    INVALID_CONTENT_TYPE,
-    INVALID_HEADER,
-    INVALID_HTTP_VERSION,
-    INVALID_METHOD,
-    INVALID_QUERY_OPERATION_METHOD,
-    INVALID_STATUS_LINE,
-    INVALID_URI,
-    MISSING_BLANK_LINE,
-    MISSING_BOUNDARY_DELIMITER,
-    MISSING_CLOSE_DELIMITER,
-    MISSING_CONTENT_ID,
-    MISSING_CONTENT_TRANSFER_ENCODING,
-    MISSING_CONTENT_TYPE,
-    MISSING_MANDATORY_HEADER, FORBIDDEN_HEADER;
-
-    @Override
-    public String getKey() {
-      return name();
-    }
-  }
-
-  private static final long serialVersionUID = -907752788975531134L;
-
-  public BatchException(final String developmentMessage, final MessageKey messageKey, final int lineNumber) {
-    this(developmentMessage, messageKey, "" + lineNumber);
-  }
-
-  public BatchException(final String developmentMessage, final MessageKey messageKey, final String... parameters) {
-    super(developmentMessage, messageKey, parameters);
-  }
-
-  @Override
-  protected String getBundleName() {
-    return DEFAULT_SERVER_BUNDLE_NAME;
-  }
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/15bd1526/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchChangeSetSorter.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchChangeSetSorter.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchChangeSetSorter.java
new file mode 100644
index 0000000..c348512
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchChangeSetSorter.java
@@ -0,0 +1,145 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.batch.handler;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.olingo.server.api.ODataRequest;
+import org.apache.olingo.server.api.batch.BatchException;
+import org.apache.olingo.server.api.batch.BatchException.MessageKeys;
+import org.apache.olingo.server.core.batch.parser.BatchParserCommon;
+
+public class BatchChangeSetSorter {
+  private static final String REG_EX_REFERENCE = "\\$(.*)(/.*)?";
+
+  final List<ODataRequest> orderdList = new ArrayList<ODataRequest>();
+
+  private static Pattern referencePattern = Pattern.compile(REG_EX_REFERENCE);
+  private Set<String> knownContentId = new HashSet<String>();
+  private Map<String, List<ODataRequest>> requestContentIdMapping = new HashMap<String, List<ODataRequest>>();
+
+  public BatchChangeSetSorter(List<ODataRequest> requests) throws BatchException {
+    sort(requests);
+  }
+
+  public List<ODataRequest> getOrderdRequests() {
+    return orderdList;
+  }
+
+  private List<ODataRequest> sort(final List<ODataRequest> requests) throws BatchException {
+    extractUrlContentId(requests);
+    orderdList.addAll(getRequestsWithoutReferences());
+
+    boolean areRequestsProcessed = true;
+    while (requestsToProcessAvailable() && areRequestsProcessed) {
+      areRequestsProcessed = processRemainingRequests(orderdList);
+    }
+
+    if (requestsToProcessAvailable()) {
+      throw new BatchException("Invalid content id", MessageKeys.INVALID_CONTENT_ID, 0);
+    }
+
+    return orderdList;
+  }
+
+  private boolean requestsToProcessAvailable() {
+    return requestContentIdMapping.keySet().size() != 0;
+  }
+
+  private boolean processRemainingRequests(List<ODataRequest> orderdList) {
+    final List<ODataRequest> addedRequests = getRemainingRequestsWithKownContentId();
+    addRequestsToKnownContentIds(addedRequests);
+    orderdList.addAll(addedRequests);
+
+    return addedRequests.size() != 0;
+  }
+
+  private List<ODataRequest> getRemainingRequestsWithKownContentId() {
+    List<ODataRequest> result = new ArrayList<ODataRequest>();
+
+    for (String contextId : knownContentId) {
+      List<ODataRequest> processedRequests = requestContentIdMapping.get(contextId);
+      if (processedRequests != null && processedRequests.size() != 0) {
+        result.addAll(processedRequests);
+        requestContentIdMapping.remove(contextId);
+      }
+    }
+
+    return result;
+  }
+
+  private List<ODataRequest> getRequestsWithoutReferences() {
+    final List<ODataRequest> requestsWithoutReference = requestContentIdMapping.get(null);
+    requestContentIdMapping.remove(null);
+
+    addRequestsToKnownContentIds(requestsWithoutReference);
+    return requestsWithoutReference;
+  }
+
+  private void addRequestsToKnownContentIds(List<ODataRequest> requestsWithoutReference) {
+    for (ODataRequest request : requestsWithoutReference) {
+      final String contentId = getContentIdFromHeader(request);
+      if (contentId != null) {
+        knownContentId.add(contentId);
+      }
+    }
+  }
+
+  private String getContentIdFromHeader(ODataRequest request) {
+    return request.getHeader(BatchParserCommon.HTTP_CONTENT_ID);
+  }
+
+  private void extractUrlContentId(List<ODataRequest> requests) {
+    for (ODataRequest request : requests) {
+      final String reference = getReferenceInURI(request);
+      addRequestToMapping(reference, request);
+    }
+  }
+
+  private void addRequestToMapping(final String reference, final ODataRequest request) {
+    List<ODataRequest> requestList = requestContentIdMapping.get(reference);
+    requestList = (requestList == null) ? new ArrayList<ODataRequest>() : requestList;
+
+    requestList.add(request);
+    requestContentIdMapping.put(reference, requestList);
+  }
+
+  public static String getReferenceInURI(ODataRequest request) {
+    Matcher matcher = referencePattern.matcher(removeFollingPathSegments(request.getRawODataPath()));
+    return (matcher.matches()) ? matcher.group(1) : null;
+  }
+
+  private static String removeFollingPathSegments(String rawODataPath) {
+    final int indexOfSlash = rawODataPath.indexOf("/");
+    return (indexOfSlash != -1) ? rawODataPath.substring(0, indexOfSlash) : rawODataPath;
+  }
+
+  public static void replaceContentIdReference(ODataRequest request, String contentId, String resourceUri) {
+    final String newUri = request.getRawODataPath().replace("$" + contentId, resourceUri);
+    request.setRawODataPath(newUri);
+    request.setRawRequestUri(request.getRawBaseUri() + "/" + newUri);
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/15bd1526/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchHandler.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchHandler.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchHandler.java
new file mode 100644
index 0000000..01a149e
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchHandler.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.batch.handler;
+
+import org.apache.olingo.commons.api.http.HttpHeader;
+import org.apache.olingo.commons.api.http.HttpMethod;
+import org.apache.olingo.server.api.ODataRequest;
+import org.apache.olingo.server.api.ODataResponse;
+import org.apache.olingo.server.api.batch.BatchException;
+import org.apache.olingo.server.api.batch.BatchOperation;
+import org.apache.olingo.server.api.batch.BatchException.MessageKeys;
+import org.apache.olingo.server.api.processor.BatchProcessor;
+import org.apache.olingo.server.core.ODataHandler;
+import org.apache.olingo.server.core.batch.parser.BatchParserCommon;
+
+public class BatchHandler {
+  private final BatchOperation operation;
+  private final BatchProcessor batchProcessor;
+  private ODataRequest request;
+
+  public BatchHandler(final ODataHandler oDataHandler, final ODataRequest request, final BatchProcessor batchProcessor,
+      final boolean isStrict) {
+    
+    this.request = request;
+    this.batchProcessor = batchProcessor;
+    operation = new BatchOperationImpl(oDataHandler, request, batchProcessor, isStrict);
+  }
+
+  public void process(ODataResponse response) throws BatchException {
+    validateRequest();
+    batchProcessor.executeBatch(operation, request, response);
+  }
+  
+  private void validateRequest() throws BatchException {
+    validateHttpMethod();
+    validateContentType();
+  }
+
+  private void validateContentType() throws BatchException {
+    final String contentType = request.getHeader(HttpHeader.CONTENT_TYPE);
+    
+    if(contentType == null || !BatchParserCommon.PATTERN_MULTIPART_BOUNDARY.matcher(contentType).matches()) {
+      throw new BatchException("Invalid content type", MessageKeys.INVALID_CONTENT_TYPE, 0);
+    }
+  }
+
+  private void validateHttpMethod() throws BatchException {
+    if(request.getMethod() != HttpMethod.POST) {
+      throw new BatchException("Invalid HTTP method", MessageKeys.INVALID_METHOD, 0);
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/15bd1526/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchOperationImpl.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchOperationImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchOperationImpl.java
new file mode 100644
index 0000000..bc148f0
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchOperationImpl.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.batch.handler;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+
+import org.apache.olingo.commons.api.http.HttpHeader;
+import org.apache.olingo.server.api.ODataRequest;
+import org.apache.olingo.server.api.ODataResponse;
+import org.apache.olingo.server.api.batch.BatchException;
+import org.apache.olingo.server.api.batch.BatchOperation;
+import org.apache.olingo.server.api.batch.BatchRequestPart;
+import org.apache.olingo.server.api.batch.ODataResponsePart;
+import org.apache.olingo.server.api.processor.BatchProcessor;
+import org.apache.olingo.server.core.ODataHandler;
+import org.apache.olingo.server.core.batch.parser.BatchParser;
+import org.apache.olingo.server.core.batch.writer.BatchResponseWriter;
+
+public class BatchOperationImpl implements BatchOperation {
+  private final BatchPartHandler partHandler;
+  private final BatchResponseWriter writer;
+  private final BatchParser parser;
+
+  public BatchOperationImpl(ODataHandler oDataHandler, ODataRequest request, BatchProcessor batchProcessor,
+      final boolean isStrict) {
+    partHandler = new BatchPartHandler(oDataHandler, batchProcessor, this);
+    writer = new BatchResponseWriter();
+    parser = new BatchParser(getContentType(request), request.getRawBaseUri(),
+                              request.getRawServiceResolutionUri(), isStrict);
+  }
+
+  @Override
+  public List<BatchRequestPart> parseBatchRequest(InputStream in) throws BatchException {
+    return parser.parseBatchRequest(in);
+  }
+
+  @Override
+  public ODataResponse handleODataRequest(ODataRequest request) {
+    return partHandler.handleODataRequest(request);
+  }
+
+  @Override
+  public ODataResponsePart handleBatchRequest(BatchRequestPart request) {
+    return partHandler.handleBatchRequest(request);
+  }
+
+  @Override
+  public void writeResponseParts(List<ODataResponsePart> batchResponses, ODataResponse response) throws BatchException,
+      IOException {
+    writer.toODataResponse(batchResponses, response);
+  }
+
+  private String getContentType(ODataRequest request) {
+    return request.getHeader(HttpHeader.CONTENT_TYPE);
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/15bd1526/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchPartHandler.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchPartHandler.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchPartHandler.java
new file mode 100644
index 0000000..a78d585
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchPartHandler.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.batch.handler;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.olingo.commons.api.http.HttpHeader;
+import org.apache.olingo.server.api.ODataRequest;
+import org.apache.olingo.server.api.ODataResponse;
+import org.apache.olingo.server.api.batch.BatchOperation;
+import org.apache.olingo.server.api.batch.BatchRequestPart;
+import org.apache.olingo.server.api.batch.ODataResponsePart;
+import org.apache.olingo.server.api.processor.BatchProcessor;
+import org.apache.olingo.server.core.ODataHandler;
+import org.apache.olingo.server.core.batch.parser.BatchParserCommon;
+
+public class BatchPartHandler {
+
+  private ODataHandler oDataHandler;
+  private BatchProcessor batchProcessor;
+  private BatchOperation batchOperation;
+
+  public BatchPartHandler(final ODataHandler oDataHandler, final BatchProcessor processor,
+      final BatchOperation batchOperation) {
+    this.oDataHandler = oDataHandler;
+    this.batchProcessor = processor;
+    this.batchOperation = batchOperation;
+  }
+
+  public ODataResponse handleODataRequest(ODataRequest request) {
+    final ODataResponse response = oDataHandler.process(request);
+    response.setHeader(BatchParserCommon.HTTP_CONTENT_ID, request.getHeader(BatchParserCommon.HTTP_CONTENT_ID));
+    
+    return  response;
+  }
+
+  public ODataResponsePart handleBatchRequest(BatchRequestPart request) {
+    final List<ODataResponse> responses = new ArrayList<ODataResponse>();
+
+    if (request.isChangeSet()) {
+      responses.addAll(batchProcessor.executeChangeSet(batchOperation, request.getRequests()));
+      return new ODataResponsePartImpl(responses, true);
+    } else {
+      responses.add(handleODataRequest(request.getRequests().get(0)));
+      return new ODataResponsePartImpl(responses, false);
+    }
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/15bd1526/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/ODataResponsePartImpl.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/ODataResponsePartImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/ODataResponsePartImpl.java
new file mode 100644
index 0000000..da52a37
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/ODataResponsePartImpl.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.batch.handler;
+
+import java.util.List;
+
+import org.apache.olingo.server.api.ODataResponse;
+import org.apache.olingo.server.api.batch.ODataResponsePart;
+
+public class ODataResponsePartImpl implements ODataResponsePart {
+  private List<ODataResponse> responses;
+  private boolean isChangeSet;
+  
+  public ODataResponsePartImpl(List<ODataResponse> responses, boolean isChangeSet) {
+    this.responses = responses;
+    this.isChangeSet = isChangeSet;
+  }
+
+  @Override
+  public List<ODataResponse> getResponses() {
+    return responses;
+  }
+
+  @Override
+  public boolean isChangeSet() {
+    return isChangeSet;
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/15bd1526/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchBodyPart.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchBodyPart.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchBodyPart.java
index c2d2e0f..2b93783 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchBodyPart.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchBodyPart.java
@@ -22,7 +22,7 @@ import java.util.LinkedList;
 import java.util.List;
 
 import org.apache.olingo.commons.api.http.HttpHeader;
-import org.apache.olingo.server.core.batch.BatchException;
+import org.apache.olingo.server.api.batch.BatchException;
 import org.apache.olingo.server.core.batch.parser.BufferedReaderIncludingLineEndings.Line;
 
 public class BatchBodyPart implements BatchPart {

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/15bd1526/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchChangeSetPart.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchChangeSetPart.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchChangeSetPart.java
index 1d0bd6f..aaa2660 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchChangeSetPart.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchChangeSetPart.java
@@ -20,7 +20,7 @@ package org.apache.olingo.server.core.batch.parser;
 
 import java.util.List;
 
-import org.apache.olingo.server.core.batch.BatchException;
+import org.apache.olingo.server.api.batch.BatchException;
 import org.apache.olingo.server.core.batch.parser.BufferedReaderIncludingLineEndings.Line;
 
 public class BatchChangeSetPart extends BatchQueryOperation {

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/15bd1526/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchParser.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchParser.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchParser.java
index 37b1a9c..75c0084 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchParser.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchParser.java
@@ -25,9 +25,9 @@ import java.util.LinkedList;
 import java.util.List;
 
 import org.apache.olingo.commons.api.ODataRuntimeException;
+import org.apache.olingo.server.api.batch.BatchException;
 import org.apache.olingo.server.api.batch.BatchParserResult;
 import org.apache.olingo.server.api.batch.BatchRequestPart;
-import org.apache.olingo.server.core.batch.BatchException;
 import org.apache.olingo.server.core.batch.parser.BufferedReaderIncludingLineEndings.Line;
 import org.apache.olingo.server.core.batch.transformator.BatchRequestTransformator;
 

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/15bd1526/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchParserCommon.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchParserCommon.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchParserCommon.java
index e2dc5e5..43a09b6 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchParserCommon.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchParserCommon.java
@@ -29,7 +29,7 @@ import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 import org.apache.olingo.commons.api.http.HttpContentType;
-import org.apache.olingo.server.core.batch.BatchException;
+import org.apache.olingo.server.api.batch.BatchException;
 import org.apache.olingo.server.core.batch.parser.BufferedReaderIncludingLineEndings.Line;
 
 public class BatchParserCommon {
@@ -186,7 +186,8 @@ public class BatchParserCommon {
   }
 
   public static void consumeBlankLine(final List<Line> remainingMessage, final boolean isStrict) throws BatchException {
-    if (remainingMessage.size() > 0 && remainingMessage.get(0).toString().matches("\\s*\r\n\\s*")) {
+    //TODO is \r\n to strict?
+    if (remainingMessage.size() > 0 && remainingMessage.get(0).toString().matches("\\s*(\r\n|\n)\\s*")) {
       remainingMessage.remove(0);
     } else {
       if (isStrict) {

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/15bd1526/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchQueryOperation.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchQueryOperation.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchQueryOperation.java
index 5ff7faf..6a5309e 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchQueryOperation.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchQueryOperation.java
@@ -20,7 +20,7 @@ package org.apache.olingo.server.core.batch.parser;
 
 import java.util.List;
 
-import org.apache.olingo.server.core.batch.BatchException;
+import org.apache.olingo.server.api.batch.BatchException;
 import org.apache.olingo.server.core.batch.parser.BufferedReaderIncludingLineEndings.Line;
 
 public class BatchQueryOperation implements BatchPart {

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/15bd1526/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchRequestTransformator.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchRequestTransformator.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchRequestTransformator.java
index 02c8118..94d5226 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchRequestTransformator.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchRequestTransformator.java
@@ -27,9 +27,9 @@ import java.util.List;
 import org.apache.olingo.commons.api.http.HttpHeader;
 import org.apache.olingo.commons.api.http.HttpMethod;
 import org.apache.olingo.server.api.ODataRequest;
+import org.apache.olingo.server.api.batch.BatchException;
 import org.apache.olingo.server.api.batch.BatchParserResult;
-import org.apache.olingo.server.core.batch.BatchException;
-import org.apache.olingo.server.core.batch.BatchException.MessageKeys;
+import org.apache.olingo.server.api.batch.BatchException.MessageKeys;
 import org.apache.olingo.server.core.batch.parser.BatchBodyPart;
 import org.apache.olingo.server.core.batch.parser.BatchChangeSetPart;
 import org.apache.olingo.server.core.batch.parser.BatchParserCommon;
@@ -84,10 +84,10 @@ public class BatchRequestTransformator implements BatchTransformator {
     final HeaderField contentIdRequest = getContentId(request);
 
     if (contentIdChangeRequestPart == null && contentIdRequest == null) {
-      throw new BatchException("Missing content type", MessageKeys.MISSING_CONTENT_ID, changeRequestPart.getHeaders()
+      throw new BatchException("Missing content id", MessageKeys.MISSING_CONTENT_ID, changeRequestPart.getHeaders()
           .getLineNumber());
     } else if (contentIdChangeRequestPart != null) {
-      request.getHeaders().replaceHeaderField(contentIdChangeRequestPart);
+        request.getHeaders().replaceHeaderField(contentIdChangeRequestPart);
     }
   }
 

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/15bd1526/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchTransformator.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchTransformator.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchTransformator.java
index becb6c7..286c0cc 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchTransformator.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchTransformator.java
@@ -20,8 +20,8 @@ package org.apache.olingo.server.core.batch.transformator;
 
 import java.util.List;
 
+import org.apache.olingo.server.api.batch.BatchException;
 import org.apache.olingo.server.api.batch.BatchParserResult;
-import org.apache.olingo.server.core.batch.BatchException;
 import org.apache.olingo.server.core.batch.parser.BatchBodyPart;
 
 public interface BatchTransformator {

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/15bd1526/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchTransformatorCommon.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchTransformatorCommon.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchTransformatorCommon.java
index a351cca..c9da563 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchTransformatorCommon.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchTransformatorCommon.java
@@ -28,8 +28,8 @@ import java.util.regex.Pattern;
 import org.apache.olingo.commons.api.http.HttpContentType;
 import org.apache.olingo.commons.api.http.HttpHeader;
 import org.apache.olingo.commons.api.http.HttpMethod;
-import org.apache.olingo.server.core.batch.BatchException;
-import org.apache.olingo.server.core.batch.BatchException.MessageKeys;
+import org.apache.olingo.server.api.batch.BatchException;
+import org.apache.olingo.server.api.batch.BatchException.MessageKeys;
 import org.apache.olingo.server.core.batch.parser.BatchParserCommon;
 import org.apache.olingo.server.core.batch.parser.BufferedReaderIncludingLineEndings.Line;
 import org.apache.olingo.server.core.batch.parser.Header;

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/15bd1526/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/writer/BatchResponseWriter.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/writer/BatchResponseWriter.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/writer/BatchResponseWriter.java
index a063747..812ea8b 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/writer/BatchResponseWriter.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/writer/BatchResponseWriter.java
@@ -31,9 +31,9 @@ import org.apache.olingo.commons.api.http.HttpContentType;
 import org.apache.olingo.commons.api.http.HttpHeader;
 import org.apache.olingo.commons.api.http.HttpStatusCode;
 import org.apache.olingo.server.api.ODataResponse;
+import org.apache.olingo.server.api.batch.BatchException;
 import org.apache.olingo.server.api.batch.ODataResponsePart;
-import org.apache.olingo.server.core.batch.BatchException;
-import org.apache.olingo.server.core.batch.BatchException.MessageKeys;
+import org.apache.olingo.server.api.batch.BatchException.MessageKeys;
 import org.apache.olingo.server.core.batch.parser.BatchParserCommon;
 import org.apache.olingo.server.core.serializer.utils.CircleStreamBuffer;
 

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/15bd1526/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/BatchRequestParserTest.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/BatchRequestParserTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/BatchRequestParserTest.java
index 1d68307..c9778e3 100644
--- a/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/BatchRequestParserTest.java
+++ b/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/BatchRequestParserTest.java
@@ -33,8 +33,9 @@ import java.util.List;
 import org.apache.olingo.commons.api.http.HttpHeader;
 import org.apache.olingo.commons.api.http.HttpMethod;
 import org.apache.olingo.server.api.ODataRequest;
+import org.apache.olingo.server.api.batch.BatchException;
 import org.apache.olingo.server.api.batch.BatchRequestPart;
-import org.apache.olingo.server.core.batch.BatchException.MessageKeys;
+import org.apache.olingo.server.api.batch.BatchException.MessageKeys;
 import org.apache.olingo.server.core.batch.parser.BatchParser;
 import org.apache.olingo.server.core.batch.parser.BatchParserCommon;
 import org.junit.Test;

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/15bd1526/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/parser/BatchParserCommonTest.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/parser/BatchParserCommonTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/parser/BatchParserCommonTest.java
index 6fb0796..e3a1acc 100644
--- a/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/parser/BatchParserCommonTest.java
+++ b/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/parser/BatchParserCommonTest.java
@@ -24,7 +24,7 @@ import java.util.ArrayList;
 import java.util.List;
 
 import org.apache.olingo.commons.api.http.HttpHeader;
-import org.apache.olingo.server.core.batch.BatchException;
+import org.apache.olingo.server.api.batch.BatchException;
 import org.apache.olingo.server.core.batch.parser.BatchParserCommon;
 import org.apache.olingo.server.core.batch.parser.BufferedReaderIncludingLineEndings.Line;
 import org.apache.olingo.server.core.batch.parser.Header;

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/15bd1526/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/writer/BatchResponseWriterTest.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/writer/BatchResponseWriterTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/writer/BatchResponseWriterTest.java
index ec45a22..b7169b9 100644
--- a/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/writer/BatchResponseWriterTest.java
+++ b/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/writer/BatchResponseWriterTest.java
@@ -30,8 +30,8 @@ import java.util.List;
 import org.apache.olingo.commons.api.http.HttpHeader;
 import org.apache.olingo.commons.api.http.HttpStatusCode;
 import org.apache.olingo.server.api.ODataResponse;
+import org.apache.olingo.server.api.batch.BatchException;
 import org.apache.olingo.server.api.batch.ODataResponsePart;
-import org.apache.olingo.server.core.batch.BatchException;
 import org.apache.olingo.server.core.batch.StringUtil;
 import org.apache.olingo.server.core.batch.parser.BatchParserCommon;
 import org.apache.olingo.server.core.batch.parser.BufferedReaderIncludingLineEndings;

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/15bd1526/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalProcessor.java
----------------------------------------------------------------------
diff --git a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalProcessor.java b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalProcessor.java
index d88a02f..b59c92e 100644
--- a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalProcessor.java
+++ b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalProcessor.java
@@ -19,11 +19,14 @@
 package org.apache.olingo.server.tecsvc.processor;
 
 import java.io.ByteArrayInputStream;
+import java.io.IOException;
 import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Locale;
 
+import org.apache.olingo.commons.api.ODataRuntimeException;
 import org.apache.olingo.commons.api.data.ContextURL;
 import org.apache.olingo.commons.api.data.ContextURL.Suffix;
 import org.apache.olingo.commons.api.data.Entity;
@@ -46,6 +49,11 @@ import org.apache.olingo.server.api.ODataApplicationException;
 import org.apache.olingo.server.api.ODataRequest;
 import org.apache.olingo.server.api.ODataResponse;
 import org.apache.olingo.server.api.ServiceMetadata;
+import org.apache.olingo.server.api.batch.BatchException;
+import org.apache.olingo.server.api.batch.BatchOperation;
+import org.apache.olingo.server.api.batch.BatchRequestPart;
+import org.apache.olingo.server.api.batch.ODataResponsePart;
+import org.apache.olingo.server.api.processor.BatchProcessor;
 import org.apache.olingo.server.api.processor.EntityProcessor;
 import org.apache.olingo.server.api.processor.EntitySetProcessor;
 import org.apache.olingo.server.api.processor.PropertyProcessor;
@@ -66,7 +74,7 @@ import org.apache.olingo.server.tecsvc.data.DataProvider;
 /**
  * Technical Processor which provides currently implemented processor functionality.
  */
-public class TechnicalProcessor implements EntitySetProcessor, EntityProcessor, PropertyProcessor {
+public class TechnicalProcessor implements EntitySetProcessor, EntityProcessor, PropertyProcessor, BatchProcessor {
 
   private OData odata;
   private DataProvider dataProvider;
@@ -326,4 +334,33 @@ public class TechnicalProcessor implements EntitySetProcessor, EntityProcessor,
       }
     }
   }
+
+  @Override
+  public void executeBatch(BatchOperation operation, ODataRequest requst, ODataResponse response) {
+    try {
+      final List<BatchRequestPart> parts = operation.parseBatchRequest(requst.getBody());
+      final List<ODataResponsePart> responseParts = new ArrayList<ODataResponsePart>();
+      
+      for(BatchRequestPart part : parts) {
+        responseParts.add(operation.handleBatchRequest(part));
+      }
+      
+      operation.writeResponseParts(responseParts, response);
+    } catch (BatchException e) {
+      throw new ODataRuntimeException(e);
+    } catch (IOException e) {
+      throw new ODataRuntimeException(e);
+    }
+  }
+
+  @Override
+  public List<ODataResponse> executeChangeSet(BatchOperation operation, List<ODataRequest> requests) {
+    List<ODataResponse> responses = new ArrayList<ODataResponse>();
+    
+    for(ODataRequest request : requests) {
+      responses.add(operation.handleODataRequest(request));
+    }
+    
+    return responses;
+  }
 }


[18/22] olingo-odata4 git commit: [OLINGO-472] Batch Refactoring

Posted by ch...@apache.org.
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/parser/BatchParserCommonTest.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/parser/BatchParserCommonTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/parser/BatchParserCommonTest.java
deleted file mode 100644
index e3a1acc..0000000
--- a/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/parser/BatchParserCommonTest.java
+++ /dev/null
@@ -1,230 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.olingo.server.core.batch.parser;
-
-import static org.junit.Assert.*;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import org.apache.olingo.commons.api.http.HttpHeader;
-import org.apache.olingo.server.api.batch.BatchException;
-import org.apache.olingo.server.core.batch.parser.BatchParserCommon;
-import org.apache.olingo.server.core.batch.parser.BufferedReaderIncludingLineEndings.Line;
-import org.apache.olingo.server.core.batch.parser.Header;
-import org.junit.Test;
-
-public class BatchParserCommonTest {
-
-  private static final String CRLF = "\r\n";
-
-  @Test
-  public void testMultipleHeader() throws BatchException {
-    String[] messageRaw = new String[] {
-        "Content-Id: 1" + CRLF,
-        "Content-Id: 2" + CRLF,
-        "content-type: Application/http" + CRLF,
-        "content-transfer-encoding: Binary" + CRLF
-      };
-    List<Line> message = toLineList(messageRaw);
-    
-    final Header header = BatchParserCommon.consumeHeaders(message);
-    assertNotNull(header);
-    
-    final List<String> contentIdHeaders = header.getHeaders(BatchParserCommon.HTTP_CONTENT_ID);
-    assertNotNull(contentIdHeaders);
-    assertEquals(2, contentIdHeaders.size());
-    assertEquals("1", contentIdHeaders.get(0));
-    assertEquals("2", contentIdHeaders.get(1));
-  }
-  
-  @Test
-  public void testMultipleHeaderSameValue() throws BatchException {
-    String[] messageRaw = new String[] {
-        "Content-Id: 1" + CRLF,
-        "Content-Id: 1" + CRLF,
-        "content-type: Application/http" + CRLF,
-        "content-transfer-encoding: Binary" + CRLF
-      };
-    List<Line> message = toLineList(messageRaw);
-    
-    final Header header = BatchParserCommon.consumeHeaders(message);
-    assertNotNull(header);
-    
-    final List<String> contentIdHeaders = header.getHeaders(BatchParserCommon.HTTP_CONTENT_ID);
-    assertNotNull(contentIdHeaders);
-    assertEquals(1, contentIdHeaders.size());
-    assertEquals("1", contentIdHeaders.get(0));
-  }
-  
-  @Test
-  public void testHeaderSperatedByComma() throws BatchException {
-    String[] messageRaw = new String[] {
-        "Content-Id: 1" + CRLF,
-        "Upgrade: HTTP/2.0, SHTTP/1.3, IRC/6.9, RTA/x11" + CRLF,
-        "content-type: Application/http" + CRLF,
-        "content-transfer-encoding: Binary" + CRLF
-      };
-    List<Line> message = toLineList(messageRaw);
-    
-    final Header header = BatchParserCommon.consumeHeaders(message);
-    assertNotNull(header);
-    
-    final List<String> upgradeHeader = header.getHeaders("upgrade");
-    assertNotNull(upgradeHeader);
-    assertEquals(4, upgradeHeader.size());
-    assertEquals("HTTP/2.0", upgradeHeader.get(0));
-    assertEquals("SHTTP/1.3", upgradeHeader.get(1));
-    assertEquals("IRC/6.9", upgradeHeader.get(2));
-    assertEquals("RTA/x11", upgradeHeader.get(3));
-  }
-  
-  @Test
-  public void testMultipleAcceptHeader() throws BatchException {
-    String[] messageRaw = new String[] {
-        "Accept: application/atomsvc+xml;q=0.8, application/json;odata=verbose;q=0.5, */*;q=0.1" + CRLF,
-        "Accept: text/plain;q=0.3" + CRLF,
-        "Accept-Language:en-US,en;q=0.7,en-UK;q=0.9" + CRLF,
-        "content-type: Application/http" + CRLF,
-        "content-transfer-encoding: Binary" + CRLF
-      };
-    List<Line> message = toLineList(messageRaw);
-    
-    final Header header = BatchParserCommon.consumeHeaders(message);
-    assertNotNull(header);
-    
-    final List<String> acceptHeader = header.getHeaders(HttpHeader.ACCEPT);
-    assertNotNull(acceptHeader);
-    assertEquals(4, acceptHeader.size());
-  }
-  
-  @Test
-  public void testMultipleAcceptHeaderSameValue() throws BatchException {
-    String[] messageRaw = new String[] {
-        "Accept: application/atomsvc+xml;q=0.8, application/json;odata=verbose;q=0.5, */*;q=0.1" + CRLF,
-        "Accept: application/atomsvc+xml;q=0.8" + CRLF,
-        "Accept-Language:en-US,en;q=0.7,en-UK;q=0.9" + CRLF,
-        "content-type: Application/http" + CRLF,
-        "content-transfer-encoding: Binary" + CRLF
-      };
-    List<Line> message = toLineList(messageRaw);
-    
-    final Header header = BatchParserCommon.consumeHeaders(message);
-    assertNotNull(header);
-    
-    final List<String> acceptHeader = header.getHeaders(HttpHeader.ACCEPT);
-    assertNotNull(acceptHeader);
-    assertEquals(3, acceptHeader.size());
-  }
-  
-  @Test
-  public void testMultipleAccepLanguagetHeader() throws BatchException {
-    String[] messageRaw = new String[] {
-        "Accept-Language:en-US,en;q=0.7,en-UK;q=0.9" + CRLF,
-        "Accept-Language: de-DE;q=0.3" + CRLF,
-        "content-type: Application/http" + CRLF,
-        "content-transfer-encoding: Binary" + CRLF
-      };
-    List<Line> message = toLineList(messageRaw);
-    
-    final Header header = BatchParserCommon.consumeHeaders(message);
-    assertNotNull(header);
-    
-    final List<String> acceptLanguageHeader = header.getHeaders(HttpHeader.ACCEPT_LANGUAGE);
-    assertNotNull(acceptLanguageHeader);
-    assertEquals(4, acceptLanguageHeader.size());
-  }
-  
-  @Test
-  public void testMultipleAccepLanguagetHeaderSameValue() throws BatchException {
-    String[] messageRaw = new String[] {
-        "Accept-Language:en-US,en;q=0.7,en-UK;q=0.9" + CRLF,
-        "Accept-Language:en-US,en;q=0.7" + CRLF,
-        "content-type: Application/http" + CRLF,
-        "content-transfer-encoding: Binary" + CRLF
-      };
-    List<Line> message = toLineList(messageRaw);
-    
-    final Header header = BatchParserCommon.consumeHeaders(message);
-    assertNotNull(header);
-    
-    final List<String> acceptLanguageHeader = header.getHeaders(HttpHeader.ACCEPT_LANGUAGE);
-    assertNotNull(acceptLanguageHeader);
-    assertEquals(3, acceptLanguageHeader.size());
-  }
-  
-  @Test
-  public void testRemoveEndingCRLF() {
-    String line = "Test\r\n";
-    assertEquals("Test", BatchParserCommon.removeEndingCRLF(new Line(line,1)).toString());
-  }
-
-  @Test
-  public void testRemoveLastEndingCRLF() {
-    String line = "Test\r\n\r\n";
-    assertEquals("Test\r\n", BatchParserCommon.removeEndingCRLF(new Line(line,1)).toString());
-  }
-
-  @Test
-  public void testRemoveEndingCRLFWithWS() {
-    String line = "Test\r\n            ";
-    assertEquals("Test", BatchParserCommon.removeEndingCRLF(new Line(line,1)).toString());
-  }
-
-  @Test
-  public void testRemoveEndingCRLFNothingToRemove() {
-    String line = "Hallo\r\nBla";
-    assertEquals("Hallo\r\nBla", BatchParserCommon.removeEndingCRLF(new Line(line,1)).toString());
-  }
-
-  @Test
-  public void testRemoveEndingCRLFAll() {
-    String line = "\r\n";
-    assertEquals("", BatchParserCommon.removeEndingCRLF(new Line(line,1)).toString());
-  }
-
-  @Test
-  public void testRemoveEndingCRLFSpace() {
-    String line = "\r\n                      ";
-    assertEquals("", BatchParserCommon.removeEndingCRLF(new Line(line,1)).toString());
-  }
-
-  @Test
-  public void testRemoveLastEndingCRLFWithWS() {
-    String line = "Test            \r\n";
-    assertEquals("Test            ", BatchParserCommon.removeEndingCRLF(new Line(line,1)).toString());
-  }
-
-  @Test
-  public void testRemoveLastEndingCRLFWithWSLong() {
-    String line = "Test            \r\nTest2    \r\n";
-    assertEquals("Test            \r\nTest2    ", BatchParserCommon.removeEndingCRLF(new Line(line,1)).toString());
-  }
-  
-  private List<Line> toLineList(String[] messageRaw) {
-    final List<Line> lineList = new ArrayList<Line>();
-    int counter = 1;
-    
-    for(final String currentLine : messageRaw) {
-      lineList.add(new Line(currentLine, counter++));
-    }
-    
-    return lineList;
-  }
-}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/parser/BufferedReaderIncludingLineEndingsTest.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/parser/BufferedReaderIncludingLineEndingsTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/parser/BufferedReaderIncludingLineEndingsTest.java
deleted file mode 100644
index eac9dff..0000000
--- a/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/parser/BufferedReaderIncludingLineEndingsTest.java
+++ /dev/null
@@ -1,484 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.olingo.server.core.batch.parser;
-
-import static org.junit.Assert.*;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.UnsupportedEncodingException;
-import java.util.List;
-
-import org.apache.olingo.server.core.batch.parser.BufferedReaderIncludingLineEndings;
-import org.apache.olingo.server.core.batch.parser.BufferedReaderIncludingLineEndings.Line;
-import org.junit.Test;
-
-public class BufferedReaderIncludingLineEndingsTest {
-
-
-  private static final String TEXT_COMBINED = "Test\r" +
-      "Test2\r\n" +
-      "Test3\n" +
-      "Test4\r" +
-      "\r" +
-      "\r\n" +
-      "\r\n" +
-      "Test5\n" +
-      "Test6\r\n" +
-      "Test7\n" +
-      "\n";
-
-  private static final String TEXT_SMALL = "Test\r" +
-      "123";
-  private static final String TEXT_EMPTY = "";
-
-  @Test
-  public void testSimpleText() throws IOException {
-    final String TEXT = "Test";
-    BufferedReaderIncludingLineEndings reader = create(TEXT);
-
-    assertEquals(TEXT, reader.readLine());
-    assertNull(reader.readLine());
-    assertNull(reader.readLine());
-    reader.close();
-  }
-
-  @Test
-  public void testNoText() throws IOException {
-    final String TEXT = "";
-    BufferedReaderIncludingLineEndings reader = create(TEXT);
-
-    assertNull(reader.readLine());
-    assertNull(reader.readLine());
-    reader.close();
-  }
-
-  @Test
-  public void testNoBytes() throws IOException {
-    BufferedReaderIncludingLineEndings reader =
-        new BufferedReaderIncludingLineEndings(new InputStreamReader(new ByteArrayInputStream(new byte[0])));
-
-    assertNull(reader.readLine());
-    assertNull(reader.readLine());
-    reader.close();
-  }
-
-  @Test
-  public void testCRLF() throws IOException {
-    final String TEXT = "Test\r\n" +
-        "Test2";
-
-    BufferedReaderIncludingLineEndings reader = create(TEXT);
-
-    assertEquals("Test\r\n", reader.readLine());
-    assertEquals("Test2", reader.readLine());
-    assertNull(reader.readLine());
-    assertNull(reader.readLine());
-    reader.close();
-  }
-
-  @Test
-  public void testLF() throws IOException {
-    final String TEXT = "Test\n" +
-        "Test2";
-
-    BufferedReaderIncludingLineEndings reader = create(TEXT);
-
-    assertEquals("Test\n", reader.readLine());
-    assertEquals("Test2", reader.readLine());
-    assertNull(reader.readLine());
-    assertNull(reader.readLine());
-    reader.close();
-  }
-
-  @Test
-  public void testCR() throws IOException {
-    final String TEXT = "Test\r" +
-        "Test2";
-
-    BufferedReaderIncludingLineEndings reader = create(TEXT);
-
-    assertEquals("Test\r", reader.readLine());
-    assertEquals("Test2", reader.readLine());
-    assertNull(reader.readLine());
-    assertNull(reader.readLine());
-    reader.close();
-  }
-
-  @Test
-  public void testCombined() throws IOException {
-    BufferedReaderIncludingLineEndings reader = create(TEXT_COMBINED);
-
-    assertEquals("Test\r", reader.readLine());
-    assertEquals("Test2\r\n", reader.readLine());
-    assertEquals("Test3\n", reader.readLine());
-    assertEquals("Test4\r", reader.readLine());
-    assertEquals("\r", reader.readLine());
-    assertEquals("\r\n", reader.readLine());
-    assertEquals("\r\n", reader.readLine());
-    assertEquals("Test5\n", reader.readLine());
-    assertEquals("Test6\r\n", reader.readLine());
-    assertEquals("Test7\n", reader.readLine());
-    assertEquals("\n", reader.readLine());
-    assertNull(reader.readLine());
-    assertNull(reader.readLine());
-    reader.close();
-  }
-
-  @Test
-  public void testCombinedBufferSizeTwo() throws IOException {
-    BufferedReaderIncludingLineEndings reader = create(TEXT_COMBINED, 2);
-
-    assertEquals("Test\r", reader.readLine());
-    assertEquals("Test2\r\n", reader.readLine());
-    assertEquals("Test3\n", reader.readLine());
-    assertEquals("Test4\r", reader.readLine());
-    assertEquals("\r", reader.readLine());
-    assertEquals("\r\n", reader.readLine());
-    assertEquals("\r\n", reader.readLine());
-    assertEquals("Test5\n", reader.readLine());
-    assertEquals("Test6\r\n", reader.readLine());
-    assertEquals("Test7\n", reader.readLine());
-    assertEquals("\n", reader.readLine());
-    assertNull(reader.readLine());
-    assertNull(reader.readLine());
-    reader.close();
-  }
-
-  @Test
-  public void testCombinedBufferSizeOne() throws IOException {
-    final String TEXT = "Test\r" +
-        "Test2\r\n" +
-        "Test3\n" +
-        "Test4\r" +
-        "\r" +
-        "\r\n" +
-        "\r\n" +
-        "Test5\n" +
-        "Test6\r\n" +
-        "Test7\n" +
-        "\r\n";
-
-    BufferedReaderIncludingLineEndings reader = create(TEXT, 1);
-
-    assertEquals("Test\r", reader.readLine());
-    assertEquals("Test2\r\n", reader.readLine());
-    assertEquals("Test3\n", reader.readLine());
-    assertEquals("Test4\r", reader.readLine());
-    assertEquals("\r", reader.readLine());
-    assertEquals("\r\n", reader.readLine());
-    assertEquals("\r\n", reader.readLine());
-    assertEquals("Test5\n", reader.readLine());
-    assertEquals("Test6\r\n", reader.readLine());
-    assertEquals("Test7\n", reader.readLine());
-    assertEquals("\r\n", reader.readLine());
-    assertNull(reader.readLine());
-    assertNull(reader.readLine());
-
-    reader.close();
-  }
-
-  @Test
-  public void testDoubleLF() throws IOException {
-    final String TEXT = "Test\r" +
-        "\r";
-
-    BufferedReaderIncludingLineEndings reader = create(TEXT, 1);
-
-    assertEquals("Test\r", reader.readLine());
-    assertEquals("\r", reader.readLine());
-    reader.close();
-  }
-
-  @Test
-  public void testSkipSimple() throws IOException {
-    BufferedReaderIncludingLineEndings reader = create(TEXT_SMALL);
-
-    assertEquals(5, reader.skip(5)); // Test\r
-    assertEquals("123", reader.readLine());
-    assertNull(reader.readLine());
-    assertNull(reader.readLine());
-    reader.close();
-  }
-
-  @Test
-  public void testSkipBufferOne() throws IOException {
-    BufferedReaderIncludingLineEndings reader = create(TEXT_SMALL, 1);
-
-    assertEquals(5, reader.skip(5)); // Test\r
-    assertEquals("123", reader.readLine());
-    assertNull(reader.readLine());
-    assertNull(reader.readLine());
-    reader.close();
-  }
-
-  @Test
-  public void testReadThanSkip() throws IOException {
-    final String TEXT = "Test\r" +
-        "\r" +
-        "123";
-
-    BufferedReaderIncludingLineEndings reader = create(TEXT);
-
-    assertEquals("Test\r", reader.readLine());
-    assertEquals(1, reader.skip(1)); // Test\r
-    assertEquals("123", reader.readLine());
-    assertNull(reader.readLine());
-    assertNull(reader.readLine());
-    reader.close();
-  }
-
-  @Test
-  public void testReadMoreBufferCapacityThanCharacterAvailable() throws IOException {
-    final String TEXT = "Foo";
-    char[] buffer = new char[20];
-
-    BufferedReaderIncludingLineEndings reader = create(TEXT);
-    assertEquals(3, reader.read(buffer, 0, 20));
-    assertEquals(-1, reader.read(buffer, 0, 20));
-    reader.close();
-
-    BufferedReaderIncludingLineEndings readerBufferOne = create(TEXT, 1);
-    assertEquals(3, readerBufferOne.read(buffer, 0, 20));
-    assertEquals(-1, readerBufferOne.read(buffer, 0, 20));
-    readerBufferOne.close();
-  }
-
-  @Test
-  public void testSkipZero() throws IOException {
-    final String TEXT = "Test\r" +
-        "123\r\n";
-
-    BufferedReaderIncludingLineEndings reader = create(TEXT);
-
-    assertEquals(0, reader.skip(0)); // Test\r
-    assertEquals("Test\r", reader.readLine());
-    assertEquals("123\r\n", reader.readLine());
-    assertNull(reader.readLine());
-    assertNull(reader.readLine());
-    reader.close();
-  }
-
-  @Test
-  public void testSkipToMuch() throws IOException {
-    BufferedReaderIncludingLineEndings reader = create(TEXT_SMALL);
-
-    assertEquals(8, reader.skip(10)); // Test\r
-    assertEquals(null, reader.readLine());
-    reader.close();
-  }
-
-  @Test
-  public void testReadBufferOne() throws IOException {
-    BufferedReaderIncludingLineEndings reader = create(TEXT_SMALL, 1);
-
-    assertEquals('T', reader.read());
-    assertEquals('e', reader.read());
-    assertEquals('s', reader.read());
-    assertEquals('t', reader.read());
-    assertEquals('\r', reader.read());
-    assertEquals('1', reader.read());
-    assertEquals('2', reader.read());
-    assertEquals('3', reader.read());
-    assertEquals(-1, reader.read());
-    assertEquals(-1, reader.read());
-  }
-
-  @Test
-  public void testReadZeroBytes() throws IOException {
-    BufferedReaderIncludingLineEndings reader = create(TEXT_SMALL, 1);
-
-    char[] buffer = new char[3];
-    assertEquals(0, reader.read(buffer, 0, 0));
-    assertEquals('T', reader.read());
-    assertEquals(0, reader.read(buffer, 0, 0));
-    assertEquals("est\r", reader.readLine());
-    assertEquals("123", reader.readLine());
-
-    reader.close();
-  }
-
-  @Test
-  public void testRead() throws IOException {
-    BufferedReaderIncludingLineEndings reader = create(TEXT_SMALL);
-
-    assertEquals('T', reader.read());
-    assertEquals('e', reader.read());
-    assertEquals('s', reader.read());
-    assertEquals('t', reader.read());
-    assertEquals('\r', reader.read());
-    assertEquals('1', reader.read());
-    assertEquals('2', reader.read());
-    assertEquals('3', reader.read());
-    assertEquals(-1, reader.read());
-    assertEquals(-1, reader.read());
-  }
-
-  @Test(expected = IndexOutOfBoundsException.class)
-  public void testFailReadBufferAndOffsetBiggerThanBuffer() throws IOException {
-    BufferedReaderIncludingLineEndings reader = create("");
-
-    final char[] buffer = new char[3];
-    reader.read(buffer, 1, 3);
-  }
-
-  @Test(expected = IndexOutOfBoundsException.class)
-  public void testFailLengthNegative() throws IOException {
-    final char[] buffer = new char[3];
-    BufferedReaderIncludingLineEndings reader = create("123");
-
-    reader.read(buffer, 1, -2);
-    reader.close();
-  }
-
-  @Test(expected = IndexOutOfBoundsException.class)
-  public void testFailOffsetNegative() throws IOException {
-    final char[] buffer = new char[3];
-    BufferedReaderIncludingLineEndings reader = create("123");
-
-    reader.read(buffer, -1, 2);
-    reader.close();
-  }
-
-  @Test
-  public void testReadAndReadLine() throws IOException {
-    final String TEXT = "Test\r" +
-        "bar\n" +
-        "123\r\n" +
-        "foo";
-
-    BufferedReaderIncludingLineEndings reader = create(TEXT);
-
-    assertEquals('T', reader.read());
-    assertEquals('e', reader.read());
-    assertEquals('s', reader.read());
-    assertEquals('t', reader.read());
-    assertEquals("\r", reader.readLine());
-    assertEquals("bar\n", reader.readLine());
-    assertEquals('1', reader.read());
-    assertEquals('2', reader.read());
-    assertEquals("3\r\n", reader.readLine());
-    assertEquals("foo", reader.readLine());
-    assertEquals(null, reader.readLine());
-    assertEquals(-1, reader.read());
-  }
-  
-  @Test
-  public void testLineEqualsAndHashCode() {
-    Line l1 = new Line("The first line", 1);
-    Line l2 = new Line("The first line", 1);
-    Line l3 = new Line("The second line", 2);
-    
-    assertEquals(l1, l2);
-    assertFalse(l1.equals(l3));
-    assertTrue(l1.hashCode() != l3.hashCode());
-  }
-  
-  @Test(expected = IllegalArgumentException.class)
-  public void testSkipNegative() throws IOException {
-    BufferedReaderIncludingLineEndings reader = create("123");
-    reader.skip(-1);
-  }
-
-  @Test(expected = IllegalArgumentException.class)
-  public void testFailBufferSizeZero() throws IOException {
-    BufferedReaderIncludingLineEndings reader = create(TEXT_EMPTY, 0);
-    reader.close();
-  }
-
-  @Test(expected = NullPointerException.class)
-  public void testInputStreamIsNull() throws IOException {
-    // Same behaviour like BufferedReader
-    BufferedReaderIncludingLineEndings reader = new BufferedReaderIncludingLineEndings(null);
-    reader.close();
-  }
-
-  @Test(expected = IllegalArgumentException.class)
-  public void testFailBufferSizeNegative() throws IOException {
-    BufferedReaderIncludingLineEndings reader = create(TEXT_EMPTY, -1);
-    reader.close();
-  }
-
-  @Test
-  public void testMarkSupoorted() throws IOException {
-    BufferedReaderIncludingLineEndings reader = create(TEXT_EMPTY);
-
-    assertEquals(false, reader.markSupported());
-    reader.close();
-  }
-
-  @Test(expected = IOException.class)
-  public void testFailMark() throws IOException {
-    BufferedReaderIncludingLineEndings reader = create("123");
-
-    reader.mark(1);
-  }
-
-  @Test(expected = IOException.class)
-  public void testFailReset() throws IOException {
-    BufferedReaderIncludingLineEndings reader = create("123");
-
-    reader.reset();
-  }
-
-  @Test
-  public void testReady() throws IOException {
-    BufferedReaderIncludingLineEndings reader = create("123\r123");
-    assertEquals(false, reader.ready());
-    assertEquals("123\r", reader.readLine());
-    assertEquals(true, reader.ready());
-    assertEquals("123", reader.readLine());
-    assertEquals(false, reader.ready());
-
-    reader.close();
-  }
-
-  @Test
-  public void testToList() throws IOException {
-    BufferedReaderIncludingLineEndings reader = create(TEXT_COMBINED);
-    List<Line> stringList = reader.toLineList();
-
-    assertEquals(11, stringList.size());
-    assertEquals("Test\r", stringList.get(0).toString());
-    assertEquals("Test2\r\n", stringList.get(1).toString());
-    assertEquals("Test3\n", stringList.get(2).toString());
-    assertEquals("Test4\r", stringList.get(3).toString());
-    assertEquals("\r", stringList.get(4).toString());
-    assertEquals("\r\n", stringList.get(5).toString());
-    assertEquals("\r\n", stringList.get(6).toString());
-    assertEquals("Test5\n", stringList.get(7).toString());
-    assertEquals("Test6\r\n", stringList.get(8).toString());
-    assertEquals("Test7\n", stringList.get(9).toString());
-    assertEquals("\n", stringList.get(10).toString());
-    reader.close();
-  }
-
-  private BufferedReaderIncludingLineEndings create(final String inputString) throws UnsupportedEncodingException {
-    return new BufferedReaderIncludingLineEndings(new InputStreamReader(new ByteArrayInputStream(inputString
-        .getBytes("UTF-8"))));
-  }
-
-  private BufferedReaderIncludingLineEndings create(final String inputString, int bufferSize)
-      throws UnsupportedEncodingException {
-    return new BufferedReaderIncludingLineEndings(new InputStreamReader(new ByteArrayInputStream(inputString
-        .getBytes("UTF-8"))), bufferSize);
-  }
-
-}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/parser/HeaderTest.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/parser/HeaderTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/parser/HeaderTest.java
deleted file mode 100644
index f38ac89..0000000
--- a/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/parser/HeaderTest.java
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.olingo.server.core.batch.parser;
-
-import static org.junit.Assert.*;
-
-import java.util.Arrays;
-import java.util.List;
-
-import org.apache.olingo.commons.api.http.HttpContentType;
-import org.apache.olingo.commons.api.http.HttpHeader;
-import org.apache.olingo.server.core.batch.parser.BatchParserCommon;
-import org.apache.olingo.server.core.batch.parser.Header;
-import org.junit.Test;
-
-public class HeaderTest {
-
-  @Test
-  public void test() {
-    Header header = new Header(1);
-    header.addHeader(HttpHeader.CONTENT_TYPE, HttpContentType.MULTIPART_MIXED, 1);
-
-    assertEquals(HttpContentType.MULTIPART_MIXED, header.getHeader(HttpHeader.CONTENT_TYPE));
-    assertEquals(1, header.getHeaders(HttpHeader.CONTENT_TYPE).size());
-    assertEquals(HttpContentType.MULTIPART_MIXED, header.getHeaders(HttpHeader.CONTENT_TYPE).get(0));
-  }
-
-  @Test
-  public void testNotAvailable() {
-    Header header = new Header(1);
-
-    assertNull(header.getHeader(HttpHeader.CONTENT_TYPE));
-    assertEquals(0, header.getHeaders(HttpHeader.CONTENT_TYPE).size());
-    assertEquals("", header.getHeaderNotNull(HttpHeader.CONTENT_TYPE));
-  }
-
-  @Test
-  public void testCaseInsensitive() {
-    Header header = new Header(1);
-    header.addHeader(HttpHeader.CONTENT_TYPE, HttpContentType.MULTIPART_MIXED, 1);
-
-    assertEquals(HttpContentType.MULTIPART_MIXED, header.getHeader("cOnTenT-TyPE"));
-    assertEquals(1, header.getHeaders("cOnTenT-TyPE").size());
-    assertEquals(HttpContentType.MULTIPART_MIXED, header.getHeaders("cOnTenT-TyPE").get(0));
-  }
-
-  @Test
-  public void testDuplicatedAdd() {
-    Header header = new Header(1);
-    header.addHeader(HttpHeader.CONTENT_TYPE, HttpContentType.MULTIPART_MIXED, 1);
-    header.addHeader(HttpHeader.CONTENT_TYPE, HttpContentType.MULTIPART_MIXED, 2);
-
-    assertEquals(HttpContentType.MULTIPART_MIXED, header.getHeader(HttpHeader.CONTENT_TYPE));
-    assertEquals(1, header.getHeaders(HttpHeader.CONTENT_TYPE).size());
-    assertEquals(HttpContentType.MULTIPART_MIXED, header.getHeaders(HttpHeader.CONTENT_TYPE).get(0));
-  }
-
-  @Test
-  public void testMatcher() {
-    Header header = new Header(1);
-    header.addHeader(HttpHeader.CONTENT_TYPE, HttpContentType.MULTIPART_MIXED + ";boundary=123", 1);
-
-    assertTrue(header.isHeaderMatching(HttpHeader.CONTENT_TYPE, BatchParserCommon.PATTERN_MULTIPART_BOUNDARY));
-  }
-
-  @Test
-  public void testFieldName() {
-    Header header = new Header(0);
-    header.addHeader("MyFieldNamE", "myValue", 1);
-
-    assertEquals("MyFieldNamE", header.getHeaderField("myfieldname").getFieldName());
-    assertEquals("MyFieldNamE", header.toSingleMap().keySet().toArray(new String[0])[0]);
-    assertEquals("MyFieldNamE", header.toMultiMap().keySet().toArray(new String[0])[0]);
-
-    assertEquals("myValue", header.toMultiMap().get("MyFieldNamE").get(0));
-    assertEquals("myValue", header.toSingleMap().get("MyFieldNamE"));
-  }
-
-  @Test
-  public void testDeepCopy() {
-    Header header = new Header(1);
-    header.addHeader(HttpHeader.CONTENT_TYPE, HttpContentType.MULTIPART_MIXED + ";boundary=123", 1);
-
-    Header copy = header.clone();
-    assertEquals(header.getHeaders(HttpHeader.CONTENT_TYPE), copy.getHeaders(HttpHeader.CONTENT_TYPE));
-    assertEquals(header.getHeader(HttpHeader.CONTENT_TYPE), copy.getHeader(HttpHeader.CONTENT_TYPE));
-    assertEquals(header.getHeaderField(HttpHeader.CONTENT_TYPE), copy.getHeaderField(HttpHeader.CONTENT_TYPE));
-
-    assertTrue(header.getHeaders(HttpHeader.CONTENT_TYPE) != copy.getHeaders(HttpHeader.CONTENT_TYPE));
-    assertTrue(header.getHeaderField(HttpHeader.CONTENT_TYPE) != copy.getHeaderField(HttpHeader.CONTENT_TYPE));
-  }
-
-  @Test
-  public void testMatcherNoHeader() {
-    Header header = new Header(1);
-
-    assertFalse(header.isHeaderMatching(HttpHeader.CONTENT_TYPE, BatchParserCommon.PATTERN_MULTIPART_BOUNDARY));
-  }
-
-//  @Test
-//  public void testMatcherFail() {
-//    Header header = new Header(1);
-//    header.addHeader(HttpHeader.CONTENT_TYPE, HttpContentType.MULTIPART_MIXED + ";boundary=123", 1);
-//
-//    assertFalse(header.isHeaderMatching(HttpHeader.CONTENT_TYPE, BatchParserCommon.PATTERN_HEADER_LINE));
-//  }
-
-  @Test
-  public void testDuplicatedAddList() {
-    Header header = new Header(1);
-    header.addHeader(HttpHeader.CONTENT_TYPE, HttpContentType.MULTIPART_MIXED, 1);
-    header.addHeader(HttpHeader.CONTENT_TYPE, Arrays.asList(new String[] { HttpContentType.MULTIPART_MIXED,
-        HttpContentType.APPLICATION_ATOM_SVC }), 2);
-
-    assertEquals(HttpContentType.MULTIPART_MIXED + ", " + HttpContentType.APPLICATION_ATOM_SVC, header
-        .getHeader(HttpHeader.CONTENT_TYPE));
-    assertEquals(2, header.getHeaders(HttpHeader.CONTENT_TYPE).size());
-    assertEquals(HttpContentType.MULTIPART_MIXED, header.getHeaders(HttpHeader.CONTENT_TYPE).get(0));
-    assertEquals(HttpContentType.APPLICATION_ATOM_SVC, header.getHeaders(HttpHeader.CONTENT_TYPE).get(1));
-  }
-
-  @Test
-  public void testRemove() {
-    Header header = new Header(1);
-    header.addHeader(HttpHeader.CONTENT_TYPE, HttpContentType.MULTIPART_MIXED, 1);
-    header.removeHeader(HttpHeader.CONTENT_TYPE);
-
-    assertNull(header.getHeader(HttpHeader.CONTENT_TYPE));
-    assertEquals(0, header.getHeaders(HttpHeader.CONTENT_TYPE).size());
-  }
-
-  @Test
-  public void testMultipleValues() {
-    Header header = new Header(1);
-    header.addHeader(HttpHeader.CONTENT_TYPE, HttpContentType.MULTIPART_MIXED, 1);
-    header.addHeader(HttpHeader.CONTENT_TYPE, HttpContentType.APPLICATION_ATOM_SVC, 2);
-    header.addHeader(HttpHeader.CONTENT_TYPE, HttpContentType.APPLICATION_ATOM_XML, 3);
-
-    final String fullHeaderString =
-        HttpContentType.MULTIPART_MIXED + ", " + HttpContentType.APPLICATION_ATOM_SVC + ", "
-            + HttpContentType.APPLICATION_ATOM_XML;
-
-    assertEquals(fullHeaderString, header.getHeader(HttpHeader.CONTENT_TYPE));
-    assertEquals(3, header.getHeaders(HttpHeader.CONTENT_TYPE).size());
-    assertEquals(HttpContentType.MULTIPART_MIXED, header.getHeaders(HttpHeader.CONTENT_TYPE).get(0));
-    assertEquals(HttpContentType.APPLICATION_ATOM_SVC, header.getHeaders(HttpHeader.CONTENT_TYPE).get(1));
-    assertEquals(HttpContentType.APPLICATION_ATOM_XML, header.getHeaders(HttpHeader.CONTENT_TYPE).get(2));
-  }
-  
-  @Test
-  public void testSplitValues() {
-    final String values = "abc, def,123,77,   99, ysd";
-    List<String> splittedValues = Header.splitValuesByComma(values);
-
-    assertEquals(6, splittedValues.size());
-    assertEquals("abc", splittedValues.get(0));
-    assertEquals("def", splittedValues.get(1));
-    assertEquals("123", splittedValues.get(2));
-    assertEquals("77", splittedValues.get(3));
-    assertEquals("99", splittedValues.get(4));
-    assertEquals("ysd", splittedValues.get(5));
-  }
-}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/writer/BatchResponseWriterTest.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/writer/BatchResponseWriterTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/writer/BatchResponseWriterTest.java
deleted file mode 100644
index ea05bfb..0000000
--- a/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/writer/BatchResponseWriterTest.java
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.olingo.server.core.batch.writer;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.apache.olingo.commons.api.http.HttpHeader;
-import org.apache.olingo.commons.api.http.HttpStatusCode;
-import org.apache.olingo.server.api.ODataResponse;
-import org.apache.olingo.server.api.batch.BatchException;
-import org.apache.olingo.server.api.batch.ODataResponsePart;
-import org.apache.olingo.server.core.batch.StringUtil;
-import org.apache.olingo.server.core.batch.parser.BatchParserCommon;
-import org.apache.olingo.server.core.batch.parser.BufferedReaderIncludingLineEndings;
-import org.junit.Test;
-
-public class BatchResponseWriterTest {
-  private static final String CRLF = "\r\n";
-
-  @Test
-  public void testBatchResponse() throws IOException, BatchException {
-    final List<ODataResponsePart> parts = new ArrayList<ODataResponsePart>();
-    ODataResponse response = new ODataResponse();
-    response.setStatusCode(HttpStatusCode.OK.getStatusCode());
-    response.setHeader(HttpHeader.CONTENT_TYPE, "application/json");
-    response.setContent(StringUtil.toInputStream("Walter Winter" + CRLF));
-
-    List<ODataResponse> responses = new ArrayList<ODataResponse>(1);
-    responses.add(response);
-    parts.add(new ODataResponsePart(responses, false));
-
-    ODataResponse changeSetResponse = new ODataResponse();
-    changeSetResponse.setStatusCode(HttpStatusCode.NO_CONTENT.getStatusCode());
-    changeSetResponse.setHeader(BatchParserCommon.HTTP_CONTENT_ID, "1");
-    responses = new ArrayList<ODataResponse>(1);
-    responses.add(changeSetResponse);
-    parts.add(new ODataResponsePart(responses, true));
-
-    BatchResponseWriter writer = new BatchResponseWriter();
-    ODataResponse batchResponse = new ODataResponse();
-    writer.toODataResponse(parts, batchResponse);
-
-    assertEquals(202, batchResponse.getStatusCode());
-    assertNotNull(batchResponse.getContent());
-    final BufferedReaderIncludingLineEndings reader =
-        new BufferedReaderIncludingLineEndings(new InputStreamReader(batchResponse.getContent()));
-    final List<String> body = reader.toList();
-    reader.close();
-    
-    int line = 0;
-    assertEquals(25, body.size());
-    assertTrue(body.get(line++).contains("--batch_"));
-    assertEquals("Content-Type: application/http" + CRLF, body.get(line++));
-    assertEquals("Content-Transfer-Encoding: binary" + CRLF, body.get(line++));
-    assertEquals(CRLF, body.get(line++));
-    assertEquals("HTTP/1.1 200 OK" + CRLF, body.get(line++));
-    assertEquals("Content-Type: application/json" + CRLF, body.get(line++));
-    assertEquals("Content-Length: 15" + CRLF, body.get(line++));
-    assertEquals(CRLF, body.get(line++));
-    assertEquals("Walter Winter" + CRLF, body.get(line++));
-    assertEquals(CRLF, body.get(line++));
-    assertTrue(body.get(line++).contains("--batch_"));
-    assertTrue(body.get(line++).contains("Content-Type: multipart/mixed; boundary=changeset_"));
-    assertEquals(CRLF, body.get(line++));
-    assertTrue(body.get(line++).contains("--changeset_"));
-    assertEquals("Content-Type: application/http" + CRLF, body.get(line++));
-    assertEquals("Content-Transfer-Encoding: binary" + CRLF, body.get(line++));
-    assertEquals("Content-Id: 1" + CRLF, body.get(line++));
-    assertEquals(CRLF, body.get(line++));
-    assertEquals("HTTP/1.1 204 No Content" + CRLF, body.get(line++));
-    assertEquals("Content-Length: 0" + CRLF, body.get(line++));
-    assertEquals(CRLF, body.get(line++));
-    assertEquals(CRLF, body.get(line++));
-    assertTrue(body.get(line++).contains("--changeset_"));
-    assertEquals(CRLF, body.get(line++));
-    assertTrue(body.get(line++).contains("--batch_"));
-  }
-
-  @Test
-  public void testResponse() throws IOException, BatchException {
-    List<ODataResponsePart> parts = new ArrayList<ODataResponsePart>();
-    ODataResponse response = new ODataResponse();
-    response.setStatusCode(HttpStatusCode.OK.getStatusCode());
-    response.setHeader(HttpHeader.CONTENT_TYPE, "application/json");
-    response.setContent(StringUtil.toInputStream("Walter Winter"));
-
-    List<ODataResponse> responses = new ArrayList<ODataResponse>(1);
-    responses.add(response);
-    parts.add(new ODataResponsePart(responses, false));
-
-    ODataResponse batchResponse = new ODataResponse();
-    new BatchResponseWriter().toODataResponse(parts, batchResponse);
-
-    assertEquals(202, batchResponse.getStatusCode());
-    assertNotNull(batchResponse.getContent());
-    final BufferedReaderIncludingLineEndings reader =
-        new BufferedReaderIncludingLineEndings(new InputStreamReader(batchResponse.getContent()));
-    final List<String> body = reader.toList();
-    reader.close();
-    
-    int line = 0;
-    assertEquals(10, body.size());
-    assertTrue(body.get(line++).contains("--batch_"));
-    assertEquals("Content-Type: application/http" + CRLF, body.get(line++));
-    assertEquals("Content-Transfer-Encoding: binary" + CRLF, body.get(line++));
-    assertEquals(CRLF, body.get(line++));
-    assertEquals("HTTP/1.1 200 OK" + CRLF, body.get(line++));
-    assertEquals("Content-Type: application/json" + CRLF, body.get(line++));
-    assertEquals("Content-Length: 13" + CRLF, body.get(line++));
-    assertEquals(CRLF, body.get(line++));
-    assertEquals("Walter Winter" + CRLF, body.get(line++));
-    assertTrue(body.get(line++).contains("--batch_"));
-  }
-
-  @Test
-  public void testChangeSetResponse() throws IOException, BatchException {
-    List<ODataResponsePart> parts = new ArrayList<ODataResponsePart>();
-    ODataResponse response = new ODataResponse();
-    response.setHeader(BatchParserCommon.HTTP_CONTENT_ID, "1");
-    response.setStatusCode(HttpStatusCode.NO_CONTENT.getStatusCode());
-
-    List<ODataResponse> responses = new ArrayList<ODataResponse>(1);
-    responses.add(response);
-    parts.add(new ODataResponsePart(responses, true));
-
-    BatchResponseWriter writer = new BatchResponseWriter();
-    ODataResponse batchResponse = new ODataResponse();
-    writer.toODataResponse(parts, batchResponse);
-
-    assertEquals(202, batchResponse.getStatusCode());
-    assertNotNull(batchResponse.getContent());
-
-    final BufferedReaderIncludingLineEndings reader =
-        new BufferedReaderIncludingLineEndings(new InputStreamReader(batchResponse.getContent()));
-    final List<String> body = reader.toList();
-    reader.close();
-    
-    int line = 0;
-    assertEquals(15, body.size());
-    assertTrue(body.get(line++).contains("--batch_"));
-    assertTrue(body.get(line++).contains("Content-Type: multipart/mixed; boundary=changeset_"));
-    assertEquals(CRLF, body.get(line++));
-    assertTrue(body.get(line++).contains("--changeset_"));
-    assertEquals("Content-Type: application/http" + CRLF, body.get(line++));
-    assertEquals("Content-Transfer-Encoding: binary" + CRLF, body.get(line++));
-    assertEquals("Content-Id: 1" + CRLF, body.get(line++));
-    assertEquals(CRLF, body.get(line++));
-    assertEquals("HTTP/1.1 204 No Content" + CRLF, body.get(line++));
-    assertEquals("Content-Length: 0" + CRLF, body.get(line++));
-    assertEquals(CRLF, body.get(line++));
-    assertEquals(CRLF, body.get(line++));
-    assertTrue(body.get(line++).contains("--changeset_"));
-    assertEquals(CRLF, body.get(line++));
-    assertTrue(body.get(line++).contains("--batch_"));
-  }
-}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/test/java/org/apache/olingo/server/core/batchhandler/BatchChangeSetSorterTest.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/batchhandler/BatchChangeSetSorterTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/batchhandler/BatchChangeSetSorterTest.java
new file mode 100644
index 0000000..7b73e43
--- /dev/null
+++ b/lib/server-core/src/test/java/org/apache/olingo/server/core/batchhandler/BatchChangeSetSorterTest.java
@@ -0,0 +1,165 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.batchhandler;
+
+import static org.junit.Assert.*;
+
+import java.io.ByteArrayInputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.olingo.commons.api.http.HttpMethod;
+import org.apache.olingo.server.api.ODataRequest;
+import org.apache.olingo.server.api.batch.BatchException;
+import org.apache.olingo.server.core.batchhandler.BatchChangeSetSorter;
+import org.apache.olingo.server.core.deserializer.batch.BatchParserCommon;
+import org.junit.Test;
+
+public class BatchChangeSetSorterTest {
+
+  private static final String BASE_URI = "http://localhost/odata.src";
+  
+  @Test
+  public void test() throws BatchException {
+    final List<ODataRequest> changeSet = new ArrayList<ODataRequest>();
+    changeSet.add(createRequest(HttpMethod.POST, BASE_URI, "$1/Adress", "2"));
+    changeSet.add(createRequest(HttpMethod.POST, BASE_URI, "/Employees", "1"));
+    
+   BatchChangeSetSorter sorter = new BatchChangeSetSorter(changeSet);
+   final List<ODataRequest> sortedChangeSet = sorter.getOrderdRequests();
+   
+   assertEquals(2, sortedChangeSet.size());
+   assertEquals("1", getContentId(sortedChangeSet.get(0)));
+   assertEquals("2", getContentId(sortedChangeSet.get(1)));
+  }
+  
+  private String getContentId(ODataRequest request) {
+    return request.getHeader(BatchParserCommon.HTTP_CONTENT_ID);
+  }
+  
+  @Test
+  public void testNoContentId() throws BatchException {
+    final List<ODataRequest> changeSet = new ArrayList<ODataRequest>();
+    changeSet.add(createRequest(HttpMethod.POST, BASE_URI, "$1/Department", "2"));
+    changeSet.add(createRequest(HttpMethod.POST, BASE_URI, "Employees", "1"));
+    changeSet.add(createRequest(HttpMethod.POST, BASE_URI, "Employee('2')/Address"));
+    changeSet.add(createRequest(HttpMethod.POST, BASE_URI, "Employee('3')/Address"));
+    changeSet.add(createRequest(HttpMethod.POST, BASE_URI, "$2/Manager", "3"));
+    
+    BatchChangeSetSorter sorter = new BatchChangeSetSorter(changeSet);
+   final List<ODataRequest> sortedChangeSet = sorter.getOrderdRequests();
+   
+   assertEquals(5, sortedChangeSet.size());
+   assertEquals("1", getContentId(sortedChangeSet.get(0)));
+   assertEquals(null, getContentId(sortedChangeSet.get(1)));
+   assertEquals(null, getContentId(sortedChangeSet.get(2)));
+   assertEquals("2", getContentId(sortedChangeSet.get(3)));
+   assertEquals("3", getContentId(sortedChangeSet.get(4)));
+  }
+  
+  @SuppressWarnings("unused")
+  @Test(expected=BatchException.class)
+  public void testContentIdNotAvailable() throws BatchException {
+    final List<ODataRequest> changeSet = new ArrayList<ODataRequest>();
+    changeSet.add(createRequest(HttpMethod.POST, BASE_URI, "$1/Department", "2"));
+    changeSet.add(createRequest(HttpMethod.POST, BASE_URI, "Employees", "1"));
+    changeSet.add(createRequest(HttpMethod.POST, BASE_URI, "Employee('2')/Address"));
+    changeSet.add(createRequest(HttpMethod.POST, BASE_URI, "Employee('3')/Address"));
+    changeSet.add(createRequest(HttpMethod.POST, BASE_URI, "$4/Manager", "3")); //4 is not available
+    
+   BatchChangeSetSorter sorter = new BatchChangeSetSorter(changeSet);
+   final List<ODataRequest> sortedChangeSet = sorter.getOrderdRequests();
+  }
+  
+  @Test
+  public void testStringAsContentId() throws BatchException {
+    final List<ODataRequest> changeSet = new ArrayList<ODataRequest>();
+    changeSet.add(createRequest(HttpMethod.POST, BASE_URI, "$One/Department", "Two"));
+    changeSet.add(createRequest(HttpMethod.POST, BASE_URI, "Employees", "One"));
+    changeSet.add(createRequest(HttpMethod.POST, BASE_URI, "Employee('2')/Address"));
+    changeSet.add(createRequest(HttpMethod.POST, BASE_URI, "Employee('3')/Address"));
+    changeSet.add(createRequest(HttpMethod.POST, BASE_URI, "$Two/Manager", "Three"));
+    
+   BatchChangeSetSorter sorter = new BatchChangeSetSorter(changeSet);
+   final List<ODataRequest> sortedChangeSet = sorter.getOrderdRequests();
+   
+   assertEquals(5, sortedChangeSet.size());
+   assertEquals("One", getContentId(sortedChangeSet.get(0)));
+   assertEquals(null, getContentId(sortedChangeSet.get(1)));
+   assertEquals(null, getContentId(sortedChangeSet.get(2)));
+   assertEquals("Two", getContentId(sortedChangeSet.get(3)));
+   assertEquals("Three", getContentId(sortedChangeSet.get(4)));
+  }
+  
+  @Test
+  public void testRewriting() {
+    final String CONTENT_ID = "1";
+    final String ODATA_PATH ="/$" + CONTENT_ID + "/Address";
+    final String RESOURCE_URI = "Employee('1')";
+    final ODataRequest request = createRequest(HttpMethod.POST, BASE_URI, ODATA_PATH);
+    
+    BatchChangeSetSorter.replaceContentIdReference(request, CONTENT_ID, RESOURCE_URI);
+    assertEquals(BASE_URI + "/" + "Employee('1')/Address", request.getRawRequestUri());
+    assertEquals("Employee('1')/Address", request.getRawODataPath());
+  }
+  
+  @Test
+  public void testRewritingNoContentId() {
+    final String CONTENT_ID = "1";
+    final String ODATA_PATH = /* "$" + CONTENT_ID + */ "Address";
+    final String RESOURCE_URI = "Employee('1')";
+    final ODataRequest request = createRequest(HttpMethod.POST, BASE_URI, ODATA_PATH);
+    
+    BatchChangeSetSorter.replaceContentIdReference(request, CONTENT_ID, RESOURCE_URI);
+    assertEquals(BASE_URI + "/" + "Address", request.getRawRequestUri());
+    assertEquals("Address", request.getRawODataPath());
+  }
+  
+  @Test
+  public void testWrongRewriting() {
+    final String CONTENT_ID = "1";
+    final String ODATA_PATH = /*"$1" */ "$2" + "/Address";
+    final String RESOURCE_URI = "Employee('1')";
+    final ODataRequest request = createRequest(HttpMethod.POST, BASE_URI, ODATA_PATH);
+    
+    BatchChangeSetSorter.replaceContentIdReference(request, CONTENT_ID, RESOURCE_URI);
+    assertEquals(BASE_URI + "/" + "$2/Address", request.getRawRequestUri());
+    assertEquals("$2/Address", request.getRawODataPath());
+  }
+  
+  private ODataRequest createRequest(HttpMethod method, String baseUrl, String oDataPath) {
+    return createRequest(method, baseUrl, oDataPath, null);
+  }
+  
+  private ODataRequest createRequest(HttpMethod method, String baseUrl, String oDataPath, String contentId) {
+    final ODataRequest request = new ODataRequest();
+    request.setBody(new ByteArrayInputStream(new byte[0]));
+    request.setMethod(HttpMethod.GET);
+    request.setRawBaseUri(baseUrl);
+    request.setRawODataPath(oDataPath);
+    request.setRawRequestUri(baseUrl + "/" + oDataPath);
+    request.setRawQueryPath("");
+    
+    if(contentId != null) {
+      request.addHeader(BatchParserCommon.HTTP_CONTENT_ID, Arrays.asList(new String[] { contentId }));
+    }
+    return request;
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/test/java/org/apache/olingo/server/core/batchhandler/MockedBatchHandlerTest.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/batchhandler/MockedBatchHandlerTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/batchhandler/MockedBatchHandlerTest.java
new file mode 100644
index 0000000..c814184
--- /dev/null
+++ b/lib/server-core/src/test/java/org/apache/olingo/server/core/batchhandler/MockedBatchHandlerTest.java
@@ -0,0 +1,671 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.batchhandler;
+
+import static org.junit.Assert.*;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.olingo.commons.api.ODataRuntimeException;
+import org.apache.olingo.commons.api.http.HttpHeader;
+import org.apache.olingo.commons.api.http.HttpMethod;
+import org.apache.olingo.commons.api.http.HttpStatusCode;
+import org.apache.olingo.server.api.OData;
+import org.apache.olingo.server.api.ODataRequest;
+import org.apache.olingo.server.api.ODataResponse;
+import org.apache.olingo.server.api.ServiceMetadata;
+import org.apache.olingo.server.api.batch.BatchException;
+import org.apache.olingo.server.api.batch.BatchFacade;
+import org.apache.olingo.server.api.deserializer.batch.BatchRequestPart;
+import org.apache.olingo.server.api.deserializer.batch.ODataResponsePart;
+import org.apache.olingo.server.api.processor.BatchProcessor;
+import org.apache.olingo.server.api.serializer.SerializerException;
+import org.apache.olingo.server.core.ODataHandler;
+import org.apache.olingo.server.core.batchhandler.BatchHandler;
+import org.apache.olingo.server.core.deserializer.batch.BatchParserCommon;
+import org.apache.olingo.server.core.deserializer.batch.BufferedReaderIncludingLineEndings;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class MockedBatchHandlerTest {
+
+  private static final String BATCH_CONTENT_TYPE = "multipart/mixed;boundary=batch_12345";
+  private static final String BATCH_ODATA_PATH = "/$batch";
+  private static final String BATCH_REQUEST_URI = "http://localhost:8080/odata/$batch";
+  private static final String BASE_URI = "http://localhost:8080/odata";
+  private static final String CRLF = "\r\n";
+  private ODataHandler oDataHandler;
+  private BatchHandler batchHandler;
+  private int entityCounter = 1;
+
+  @Before
+  public void setup() {
+    final BatchProcessor batchProcessor = new BatchTestProcessorImpl();
+    batchProcessor.init(OData.newInstance(), null);
+
+    entityCounter = 1;
+    oDataHandler = mock(ODataHandler.class);
+    batchHandler = new BatchHandler(oDataHandler, batchProcessor);
+  }
+
+  @Test
+  public void test() throws Exception {
+    final String content = "--batch_12345" + CRLF
+        + "Content-Type: multipart/mixed; boundary=changeset_12345" + CRLF
+        + CRLF
+        + "--changeset_12345" + CRLF
+        + "Content-Type: application/http" + CRLF
+        + "Content-Transfer-Encoding: binary" + CRLF
+        + "Content-Id: 4" + CRLF
+        + CRLF
+        + "PUT /$3/PropertyInt32 HTTP/1.1" + CRLF // Absolute URI with separate Host header and ref.
+        + "Host: http://localhost:8080/odata" + CRLF
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + CRLF
+        + CRLF
+        + "--changeset_12345" + CRLF
+        + "Content-Type: application/http" + CRLF
+        + "Content-Transfer-Encoding: binary" + CRLF
+        + "Content-Id: 5" + CRLF
+        + CRLF
+        + "POST http://localhost:8080/odata/$1/NavPropertyETTwoPrimMany HTTP/1.1" + CRLF // Absolute URI with ref.
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + CRLF
+        + CRLF
+        + "--changeset_12345" + CRLF
+        + "Content-Type: application/http" + CRLF
+        + "Content-Transfer-Encoding: binary" + CRLF
+        + "Content-Id: 2" + CRLF
+        + CRLF
+        + "POST $1/NavPropertyETTwoPrimMany HTTP/1.1" + CRLF // Relative URI with ref.
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + CRLF
+        + CRLF
+        + "--changeset_12345" + CRLF
+        + "Content-Type: application/http" + CRLF
+        + "Content-Transfer-Encoding: binary" + CRLF
+        + "Content-Id: 1" + CRLF
+        + CRLF
+        + "POST http://localhost:8080/odata/ESAllPrim HTTP/1.1" + CRLF // Absolute URI
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + CRLF
+        + CRLF
+        + "--changeset_12345" + CRLF
+        + "Content-Type: application/http" + CRLF
+        + "Content-Transfer-Encoding: binary" + CRLF
+        + "Content-Id: 3" + CRLF
+        + CRLF
+        + "PUT ESAllPrim(1) HTTP/1.1" + CRLF // Relative URI
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + CRLF
+        + CRLF
+        + "--changeset_12345" + CRLF
+        + "Content-Type: application/http" + CRLF
+        + "Content-Transfer-Encoding: binary" + CRLF
+        + "Content-Id: 6" + CRLF
+        + CRLF
+        + "PUT /ESAllPrim(1) HTTP/1.1" + CRLF // Absolute URI with separate Host header
+        + "Host: http://localhost:8080/odata"
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + CRLF
+        + CRLF
+        + "--changeset_12345--" + CRLF
+        + CRLF
+        + "--batch_12345--";
+    final Map<String, List<String>> header = getMimeHeader();
+    final ODataResponse response = new ODataResponse();
+    final ODataRequest request = buildODataRequest(content, header);
+
+    batchHandler.process(request, response, true);
+
+    BufferedReaderIncludingLineEndings reader =
+        new BufferedReaderIncludingLineEndings(new InputStreamReader(response.getContent()));
+
+    final List<String> responseContent = reader.toList();
+    reader.close();
+
+    int line = 0;
+    assertEquals(63, responseContent.size());
+
+    // Check change set
+    assertTrue(responseContent.get(line++).contains("--batch_"));
+    assertTrue(responseContent.get(line++).contains("Content-Type: multipart/mixed; boundary=changeset_"));
+
+    for (int i = 0; i < 6; i++) {
+      String contentId = checkChangeSetPartHeader(responseContent, line);
+      line += 6;
+
+      if ("1".equals(contentId)) {
+        assertEquals("HTTP/1.1 201 Created" + CRLF, responseContent.get(line++));
+        assertEquals("Location: " + BASE_URI + "/ESAllPrim(1)" + CRLF, responseContent.get(line++));
+        assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
+      } else if ("2".equals(contentId)) {
+        assertEquals("HTTP/1.1 201 Created" + CRLF, responseContent.get(line++));
+        assertEquals("Location: " + BASE_URI + "/ESTwoPrim(3)" + CRLF, responseContent.get(line++));
+        assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
+      } else if ("3".equals(contentId)) {
+        assertEquals("HTTP/1.1 200 OK" + CRLF, responseContent.get(line++));
+        assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
+      } else if ("4".equals(contentId)) {
+        assertEquals("HTTP/1.1 200 OK" + CRLF, responseContent.get(line++));
+        assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
+      } else if ("5".equals(contentId)) {
+        assertEquals("HTTP/1.1 201 Created" + CRLF, responseContent.get(line++));
+        assertEquals("Location: " + BASE_URI + "/ESTwoPrim(2)" + CRLF, responseContent.get(line++));
+        assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
+      } else if ("6".equals(contentId)) {
+        assertEquals("HTTP/1.1 200 OK" + CRLF, responseContent.get(line++));
+        assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
+      } else {
+        fail();
+      }
+
+      assertEquals(CRLF, responseContent.get(line++));
+    }
+
+    // Close body part (change set)
+    assertEquals(CRLF, responseContent.get(line++));
+    assertTrue(responseContent.get(line++).contains("--changeset_"));
+
+    // Close batch
+    assertEquals(CRLF, responseContent.get(line++));
+    assertTrue(responseContent.get(line++).contains("--batch_"));
+    assertEquals(63, line);
+  }
+
+  @Test
+  public void testGetRequest() throws Exception {
+    final String content = ""
+        + "--batch_12345" + CRLF
+        + "Content-Type: application/http" + CRLF
+        + "Content-Transfer-Encoding: binary" + CRLF
+        + CRLF
+        + "GET ESAllPrim(0) HTTP/1.1" + CRLF
+        + CRLF
+        + CRLF
+        + "--batch_12345--";
+
+    final Map<String, List<String>> header = getMimeHeader();
+    final ODataResponse response = new ODataResponse();
+    final ODataRequest request = buildODataRequest(content, header);
+
+    batchHandler.process(request, response, true);
+
+    BufferedReaderIncludingLineEndings reader =
+        new BufferedReaderIncludingLineEndings(new InputStreamReader(response.getContent()));
+
+    final List<String> responseContent = reader.toList();
+    int line = 0;
+
+    assertEquals(9, responseContent.size());
+    assertTrue(responseContent.get(line++).contains("--batch_"));
+    assertEquals("Content-Type: application/http" + CRLF, responseContent.get(line++));
+    assertEquals("Content-Transfer-Encoding: binary" + CRLF, responseContent.get(line++));
+    assertEquals(CRLF, responseContent.get(line++));
+    assertEquals("HTTP/1.1 200 OK" + CRLF, responseContent.get(line++));
+    assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
+    assertEquals(CRLF, responseContent.get(line++));
+    assertEquals(CRLF, responseContent.get(line++));
+    assertTrue(responseContent.get(line++).contains("--batch_"));
+
+    assertEquals(9, line);
+
+    reader.close();
+  }
+
+  @Test
+  public void testMultipleChangeSets() throws Exception {
+    final String content = ""
+        + "--batch_12345" + CRLF
+        + "Content-Type: multipart/mixed; boundary=changeset_12345" + CRLF
+        + CRLF
+        + "--changeset_12345" + CRLF
+        + "Content-Type: application/http" + CRLF
+        + "Content-Transfer-Encoding: binary" + CRLF
+        + "Content-Id: 2" + CRLF
+        + CRLF
+        + "POST /$1/NavPropertyETTwoPrimMany HTTP/1.1" + CRLF
+        + "Host: http://localhost:8080/odata" + CRLF
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + CRLF
+        + CRLF
+        + "--changeset_12345" + CRLF
+        + "Content-Type: application/http" + CRLF
+        + "Content-Transfer-Encoding: binary" + CRLF
+        + "Content-Id: 1" + CRLF
+        + CRLF
+        + "PUT ESAllPrim(1) HTTP/1.1" + CRLF
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + CRLF
+        + CRLF
+        + "--changeset_12345--" + CRLF
+
+        + "--batch_12345" + CRLF
+        + "Content-Type: multipart/mixed; boundary=changeset_54321" + CRLF
+        + CRLF
+        + "--changeset_54321" + CRLF
+        + "Content-Type: application/http" + CRLF
+        + "Content-Transfer-Encoding: binary" + CRLF
+        + "Content-Id: 2" + CRLF
+        + CRLF
+        + "POST /$1/NavPropertyETTwoPrimMany HTTP/1.1" + CRLF
+        + "Host: http://localhost:8080/odata" + CRLF
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + CRLF
+        + CRLF
+        + "--changeset_54321" + CRLF
+        + "Content-Type: application/http" + CRLF
+        + "Content-Transfer-Encoding: binary" + CRLF
+        + "Content-Id: 1" + CRLF
+        + CRLF
+        + "PUT ESAllPrim(2) HTTP/1.1" + CRLF
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + CRLF
+        + CRLF
+        + "--changeset_54321--" + CRLF
+
+        + CRLF
+        + "--batch_12345--";
+    final Map<String, List<String>> header = getMimeHeader();
+    final ODataResponse response = new ODataResponse();
+    final ODataRequest request = buildODataRequest(content, header);
+
+    batchHandler.process(request, response, true);
+
+    BufferedReaderIncludingLineEndings reader =
+        new BufferedReaderIncludingLineEndings(new InputStreamReader(response.getContent()));
+
+    final List<String> responseContent = reader.toList();
+    reader.close();
+
+    int line = 0;
+    assertEquals(49, responseContent.size());
+
+    // Check first change set
+    assertTrue(responseContent.get(line++).contains("--batch_"));
+    assertTrue(responseContent.get(line++).contains("Content-Type: multipart/mixed; boundary=changeset_"));
+
+    for (int i = 0; i < 2; i++) {
+      String contentId = checkChangeSetPartHeader(responseContent, line);
+      line += 6;
+
+      if ("1".equals(contentId)) {
+        assertEquals("HTTP/1.1 200 OK" + CRLF, responseContent.get(line++));
+        assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
+      } else if ("2".equals(contentId)) {
+        assertEquals("HTTP/1.1 201 Created" + CRLF, responseContent.get(line++));
+        assertEquals("Location: " + BASE_URI + "/ESTwoPrim(1)" + CRLF, responseContent.get(line++));
+        assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
+      } else {
+        fail();
+      }
+
+      assertEquals(CRLF, responseContent.get(line++));
+    }
+    // Close body part (1st change set)
+    assertEquals(CRLF, responseContent.get(line++));
+    assertTrue(responseContent.get(line++).contains("--changeset_"));
+
+    // Check second change set
+    assertEquals(CRLF, responseContent.get(line++));
+    assertTrue(responseContent.get(line++).contains("--batch_"));
+    assertTrue(responseContent.get(line++).contains("Content-Type: multipart/mixed; boundary=changeset_"));
+
+    for (int i = 0; i < 2; i++) {
+      String contentId = checkChangeSetPartHeader(responseContent, line);
+      line += 6;
+
+      if ("1".equals(contentId)) {
+        assertEquals("HTTP/1.1 200 OK" + CRLF, responseContent.get(line++));
+        assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
+      } else if ("2".equals(contentId)) {
+        assertEquals("HTTP/1.1 201 Created" + CRLF, responseContent.get(line++));
+        assertEquals("Location: " + BASE_URI + "/ESTwoPrim(2)" + CRLF, responseContent.get(line++));
+        assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
+      } else {
+        fail();
+      }
+
+      assertEquals(CRLF, responseContent.get(line++));
+    }
+    // Close body part (2nd change set)
+    assertEquals(CRLF, responseContent.get(line++));
+    assertTrue(responseContent.get(line++).contains("--changeset_"));
+
+    // Close batch
+    assertEquals(CRLF, responseContent.get(line++));
+    assertTrue(responseContent.get(line++).contains("--batch_"));
+
+    assertEquals(49, line);
+  }
+
+  @Test
+  public void testMineBodyPartTransitiv() throws Exception {
+    final String content = ""
+        + "--batch_12345" + CRLF
+        + "Content-Type: multipart/mixed; boundary=changeset_12345" + CRLF
+        + CRLF
+        + "--changeset_12345" + CRLF
+        + "Content-Type: application/http" + CRLF
+        + "Content-Transfer-Encoding: binary" + CRLF
+        + "Content-Id: 4" + CRLF
+        + CRLF
+        + "POST $3/NavPropertyETTwoPrimOne HTTP/1.1" + CRLF
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + CRLF
+        + CRLF
+        + "--changeset_12345" + CRLF
+        + "Content-Type: application/http" + CRLF
+        + "Content-Transfer-Encoding: binary" + CRLF
+        + "Content-Id: 2" + CRLF
+        + CRLF
+        + "POST /$1/NavPropertyETTwoPrimMany HTTP/1.1" + CRLF
+        + "Host: http://localhost:8080/odata" + CRLF
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + CRLF
+        + CRLF
+        + "--changeset_12345" + CRLF
+        + "Content-Type: application/http" + CRLF
+        + "Content-Transfer-Encoding: binary" + CRLF
+        + "Content-Id: 1" + CRLF
+        + CRLF
+        + "PUT ESAllPrim(1) HTTP/1.1" + CRLF
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + CRLF
+        + CRLF
+        + "--changeset_12345" + CRLF
+        + "Content-Type: application/http" + CRLF
+        + "Content-Transfer-Encoding: binary" + CRLF
+        + "Content-Id: 3" + CRLF
+        + CRLF
+        + "POST $2/NavPropertyETAllPrimMany HTTP/1.1" + CRLF
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + CRLF
+        + CRLF
+        + "--changeset_12345--" + CRLF
+
+        + CRLF
+        + "--batch_12345--";
+
+    final Map<String, List<String>> header = getMimeHeader();
+    final ODataResponse response = new ODataResponse();
+    final ODataRequest request = buildODataRequest(content, header);
+
+    batchHandler.process(request, response, true);
+
+    BufferedReaderIncludingLineEndings reader =
+        new BufferedReaderIncludingLineEndings(new InputStreamReader(response.getContent()));
+
+    final List<String> responseContent = reader.toList();
+    reader.close();
+
+    int line = 0;
+    assertEquals(45, responseContent.size());
+
+    // Check change set
+    assertTrue(responseContent.get(line++).contains("--batch_"));
+    assertTrue(responseContent.get(line++).contains("Content-Type: multipart/mixed; boundary=changeset_"));
+
+    for (int i = 0; i < 4; i++) {
+      String contentId = checkChangeSetPartHeader(responseContent, line);
+      line += 6;
+
+      if ("1".equals(contentId)) {
+        assertEquals("HTTP/1.1 200 OK" + CRLF, responseContent.get(line++));
+        assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
+      } else if ("2".equals(contentId)) {
+        assertEquals("HTTP/1.1 201 Created" + CRLF, responseContent.get(line++));
+        assertEquals("Location: " + BASE_URI + "/ESTwoPrim(1)" + CRLF, responseContent.get(line++));
+        assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
+      } else if ("3".equals(contentId)) {
+        assertEquals("HTTP/1.1 201 Created" + CRLF, responseContent.get(line++));
+        assertEquals("Location: " + BASE_URI + "/ESAllPrim(2)" + CRLF, responseContent.get(line++));
+        assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
+      } else if ("4".equals(contentId)) {
+        assertEquals("HTTP/1.1 201 Created" + CRLF, responseContent.get(line++));
+        assertEquals("Location: " + BASE_URI + "/ESTwoPrim(3)" + CRLF, responseContent.get(line++));
+        assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
+      } else {
+        fail();
+      }
+
+      assertEquals(CRLF, responseContent.get(line++));
+    }
+
+    // Close body part (change set)
+    assertEquals(CRLF, responseContent.get(line++));
+    assertTrue(responseContent.get(line++).contains("--changeset_"));
+
+    // Close batch
+    assertEquals(CRLF, responseContent.get(line++));
+    assertTrue(responseContent.get(line++).contains("--batch_"));
+    assertEquals(45, line);
+  }
+
+  private String checkChangeSetPartHeader(final List<String> response, int line) {
+    assertEquals(CRLF, response.get(line++));
+    assertTrue(response.get(line++).contains("--changeset_"));
+    assertEquals("Content-Type: application/http" + CRLF, response.get(line++));
+    assertEquals("Content-Transfer-Encoding: binary" + CRLF, response.get(line++));
+
+    assertTrue(response.get(line).contains("Content-Id:"));
+    String contentId = response.get(line).split(":")[1].trim();
+    line++;
+
+    assertEquals(CRLF, response.get(line++));
+
+    return contentId;
+  }
+
+  @Test(expected = BatchException.class)
+  public void testInvalidMethod() throws Exception {
+    final String content = ""
+        + "--batch_12345" + CRLF
+        + "Content-Type: multipart/mixed; boundary=changeset_12345" + CRLF
+        + CRLF
+        + "--changeset_12345" + CRLF
+        + "Content-Type: application/http" + CRLF
+        + "Content-Transfer-Encoding: binary" + CRLF
+        + "Content-Id: 1" + CRLF
+        + CRLF
+        + "PUT ESAllPrim(1) HTTP/1.1" + CRLF
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + CRLF
+        + CRLF
+        + "--changeset_12345--" + CRLF
+        + CRLF
+        + "--batch_12345--";
+
+    final Map<String, List<String>> header = getMimeHeader();
+    final ODataResponse response = new ODataResponse();
+    final ODataRequest request = buildODataRequest(content, header);
+    request.setMethod(HttpMethod.GET);
+
+    batchHandler.process(request, response, true);
+  }
+
+  @Test(expected = BatchException.class)
+  public void testInvalidContentType() throws Exception {
+    final String content = ""
+        + "--batch_12345" + CRLF
+        + "Content-Type: multipart/mixed; boundary=changeset_12345" + CRLF
+        + CRLF
+        + "--changeset_12345" + CRLF
+        + "Content-Type: application/http" + CRLF
+        + "Content-Transfer-Encoding: binary" + CRLF
+        + "Content-Id: 1" + CRLF
+        + CRLF
+        + "PUT ESAllPrim(1) HTTP/1.1" + CRLF
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + CRLF
+        + CRLF
+        + "--changeset_12345--" + CRLF
+        + CRLF
+        + "--batch_12345--";
+
+    final Map<String, List<String>> header = new HashMap<String, List<String>>();
+    header.put(HttpHeader.CONTENT_TYPE, Arrays.asList(new String[] { "application/http" }));
+    final ODataResponse response = new ODataResponse();
+    final ODataRequest request = buildODataRequest(content, header);
+
+    batchHandler.process(request, response, true);
+  }
+
+  /*
+   * Helper methods
+   */
+  private Map<String, List<String>> getMimeHeader() {
+    final Map<String, List<String>> header = new HashMap<String, List<String>>();
+    header.put(HttpHeader.CONTENT_TYPE, Arrays.asList(new String[] { BATCH_CONTENT_TYPE }));
+
+    return header;
+  }
+
+  private ODataRequest buildODataRequest(final String content, final Map<String, List<String>> header)
+      throws UnsupportedEncodingException {
+    final ODataRequest request = new ODataRequest();
+
+    for (final String key : header.keySet()) {
+      request.addHeader(key, header.get(key));
+    }
+
+    request.setMethod(HttpMethod.POST);
+    request.setRawBaseUri(BASE_URI);
+    request.setRawODataPath(BATCH_ODATA_PATH);
+    request.setRawQueryPath("");
+    request.setRawRequestUri(BATCH_REQUEST_URI);
+    request.setRawServiceResolutionUri("");
+
+    request.setBody(new ByteArrayInputStream(content.getBytes("UTF-8")));
+
+    return request;
+  }
+
+  /**
+   * Batch processor
+   */
+  private class BatchTestProcessorImpl implements BatchProcessor {
+
+    private OData odata;
+
+    @Override
+    public void init(OData odata, ServiceMetadata serviceMetadata) {
+      this.odata = odata;
+    }
+
+    @Override
+    public ODataResponsePart executeChangeSet(BatchFacade operation, List<ODataRequest> requests,
+        BatchRequestPart requestPart) {
+      List<ODataResponse> responses = new ArrayList<ODataResponse>();
+
+      for (ODataRequest request : requests) {
+        try {
+          responses.add(operation.handleODataRequest(request, requestPart));
+        } catch (BatchException e) {
+          fail();
+        }
+      }
+
+      return new ODataResponsePart(responses, true);
+    }
+
+    @Override
+    public void executeBatch(BatchFacade operation, ODataRequest request, ODataResponse response)
+        throws SerializerException, BatchException {
+      try {
+        final List<BatchRequestPart> parts = odata.createFixedFormatDeserializer().parseBatchRequest(request, true);
+        final List<ODataResponsePart> responseParts = new ArrayList<ODataResponsePart>();
+
+        for (BatchRequestPart part : parts) {
+          for (final ODataRequest oDataRequest : part.getRequests()) {
+            // Mock the processor for a given requests
+            when(oDataHandler.process(oDataRequest)).then(new Answer<ODataResponse>() {
+              @Override
+              public ODataResponse answer(InvocationOnMock invocation) throws Throwable {
+                Object[] arguments = invocation.getArguments();
+
+                return buildResponse((ODataRequest) arguments[0]);
+              }
+            });
+          }
+
+          responseParts.add(operation.handleBatchRequest(part));
+        }
+
+        odata.createFixedFormatSerializer().writeResponseParts(responseParts, response);
+      } catch (IOException e) {
+        throw new ODataRuntimeException(e);
+      }
+    }
+  }
+
+  private ODataResponse buildResponse(ODataRequest request) {
+    final ODataResponse oDataResponse = new ODataResponse();
+
+    if (request.getMethod() == HttpMethod.POST) {
+      oDataResponse.setStatusCode(HttpStatusCode.CREATED.getStatusCode());
+      oDataResponse.setHeader(HttpHeader.LOCATION, createResourceUri(request));
+    } else {
+      oDataResponse.setStatusCode(HttpStatusCode.OK.getStatusCode());
+    }
+
+    final String contentId = request.getHeader(BatchParserCommon.HTTP_CONTENT_ID);
+    if (contentId != null) {
+      oDataResponse.setHeader(BatchParserCommon.HTTP_CONTENT_ID, contentId);
+    }
+
+    return oDataResponse;
+  }
+
+  private String createResourceUri(final ODataRequest request) {
+    final String parts[] = request.getRawODataPath().split("/");
+    String oDataPath = "";
+
+    if (parts.length == 2) {
+      // Entity Collection
+      oDataPath = parts[1];
+    } else {
+      // Navigation property
+
+      final String navProperty = parts[parts.length - 1];
+      if (navProperty.equals("NavPropertyETTwoPrimMany")) {
+        oDataPath = "ESTwoPrim";
+      } else if (navProperty.equals("NavPropertyETAllPrimMany")) {
+        oDataPath = "ESAllPrim";
+      } else if (navProperty.equals("NavPropertyETTwoPrimOne")) {
+        oDataPath = "ESTwoPrim";
+      }
+    }
+
+    return BASE_URI + "/" + oDataPath + "(" + entityCounter++ + ")";
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/test/java/org/apache/olingo/server/core/deserializer/BatchParserCommonTest.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/deserializer/BatchParserCommonTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/deserializer/BatchParserCommonTest.java
new file mode 100644
index 0000000..2191555
--- /dev/null
+++ b/lib/server-core/src/test/java/org/apache/olingo/server/core/deserializer/BatchParserCommonTest.java
@@ -0,0 +1,230 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.deserializer;
+
+import static org.junit.Assert.*;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.olingo.commons.api.http.HttpHeader;
+import org.apache.olingo.server.api.batch.BatchException;
+import org.apache.olingo.server.core.deserializer.batch.BatchParserCommon;
+import org.apache.olingo.server.core.deserializer.batch.Header;
+import org.apache.olingo.server.core.deserializer.batch.BufferedReaderIncludingLineEndings.Line;
+import org.junit.Test;
+
+public class BatchParserCommonTest {
+
+  private static final String CRLF = "\r\n";
+
+  @Test
+  public void testMultipleHeader() throws BatchException {
+    String[] messageRaw = new String[] {
+        "Content-Id: 1" + CRLF,
+        "Content-Id: 2" + CRLF,
+        "content-type: Application/http" + CRLF,
+        "content-transfer-encoding: Binary" + CRLF
+      };
+    List<Line> message = toLineList(messageRaw);
+    
+    final Header header = BatchParserCommon.consumeHeaders(message);
+    assertNotNull(header);
+    
+    final List<String> contentIdHeaders = header.getHeaders(BatchParserCommon.HTTP_CONTENT_ID);
+    assertNotNull(contentIdHeaders);
+    assertEquals(2, contentIdHeaders.size());
+    assertEquals("1", contentIdHeaders.get(0));
+    assertEquals("2", contentIdHeaders.get(1));
+  }
+  
+  @Test
+  public void testMultipleHeaderSameValue() throws BatchException {
+    String[] messageRaw = new String[] {
+        "Content-Id: 1" + CRLF,
+        "Content-Id: 1" + CRLF,
+        "content-type: Application/http" + CRLF,
+        "content-transfer-encoding: Binary" + CRLF
+      };
+    List<Line> message = toLineList(messageRaw);
+    
+    final Header header = BatchParserCommon.consumeHeaders(message);
+    assertNotNull(header);
+    
+    final List<String> contentIdHeaders = header.getHeaders(BatchParserCommon.HTTP_CONTENT_ID);
+    assertNotNull(contentIdHeaders);
+    assertEquals(1, contentIdHeaders.size());
+    assertEquals("1", contentIdHeaders.get(0));
+  }
+  
+  @Test
+  public void testHeaderSperatedByComma() throws BatchException {
+    String[] messageRaw = new String[] {
+        "Content-Id: 1" + CRLF,
+        "Upgrade: HTTP/2.0, SHTTP/1.3, IRC/6.9, RTA/x11" + CRLF,
+        "content-type: Application/http" + CRLF,
+        "content-transfer-encoding: Binary" + CRLF
+      };
+    List<Line> message = toLineList(messageRaw);
+    
+    final Header header = BatchParserCommon.consumeHeaders(message);
+    assertNotNull(header);
+    
+    final List<String> upgradeHeader = header.getHeaders("upgrade");
+    assertNotNull(upgradeHeader);
+    assertEquals(4, upgradeHeader.size());
+    assertEquals("HTTP/2.0", upgradeHeader.get(0));
+    assertEquals("SHTTP/1.3", upgradeHeader.get(1));
+    assertEquals("IRC/6.9", upgradeHeader.get(2));
+    assertEquals("RTA/x11", upgradeHeader.get(3));
+  }
+  
+  @Test
+  public void testMultipleAcceptHeader() throws BatchException {
+    String[] messageRaw = new String[] {
+        "Accept: application/atomsvc+xml;q=0.8, application/json;odata=verbose;q=0.5, */*;q=0.1" + CRLF,
+        "Accept: text/plain;q=0.3" + CRLF,
+        "Accept-Language:en-US,en;q=0.7,en-UK;q=0.9" + CRLF,
+        "content-type: Application/http" + CRLF,
+        "content-transfer-encoding: Binary" + CRLF
+      };
+    List<Line> message = toLineList(messageRaw);
+    
+    final Header header = BatchParserCommon.consumeHeaders(message);
+    assertNotNull(header);
+    
+    final List<String> acceptHeader = header.getHeaders(HttpHeader.ACCEPT);
+    assertNotNull(acceptHeader);
+    assertEquals(4, acceptHeader.size());
+  }
+  
+  @Test
+  public void testMultipleAcceptHeaderSameValue() throws BatchException {
+    String[] messageRaw = new String[] {
+        "Accept: application/atomsvc+xml;q=0.8, application/json;odata=verbose;q=0.5, */*;q=0.1" + CRLF,
+        "Accept: application/atomsvc+xml;q=0.8" + CRLF,
+        "Accept-Language:en-US,en;q=0.7,en-UK;q=0.9" + CRLF,
+        "content-type: Application/http" + CRLF,
+        "content-transfer-encoding: Binary" + CRLF
+      };
+    List<Line> message = toLineList(messageRaw);
+    
+    final Header header = BatchParserCommon.consumeHeaders(message);
+    assertNotNull(header);
+    
+    final List<String> acceptHeader = header.getHeaders(HttpHeader.ACCEPT);
+    assertNotNull(acceptHeader);
+    assertEquals(3, acceptHeader.size());
+  }
+  
+  @Test
+  public void testMultipleAccepLanguagetHeader() throws BatchException {
+    String[] messageRaw = new String[] {
+        "Accept-Language:en-US,en;q=0.7,en-UK;q=0.9" + CRLF,
+        "Accept-Language: de-DE;q=0.3" + CRLF,
+        "content-type: Application/http" + CRLF,
+        "content-transfer-encoding: Binary" + CRLF
+      };
+    List<Line> message = toLineList(messageRaw);
+    
+    final Header header = BatchParserCommon.consumeHeaders(message);
+    assertNotNull(header);
+    
+    final List<String> acceptLanguageHeader = header.getHeaders(HttpHeader.ACCEPT_LANGUAGE);
+    assertNotNull(acceptLanguageHeader);
+    assertEquals(4, acceptLanguageHeader.size());
+  }
+  
+  @Test
+  public void testMultipleAccepLanguagetHeaderSameValue() throws BatchException {
+    String[] messageRaw = new String[] {
+        "Accept-Language:en-US,en;q=0.7,en-UK;q=0.9" + CRLF,
+        "Accept-Language:en-US,en;q=0.7" + CRLF,
+        "content-type: Application/http" + CRLF,
+        "content-transfer-encoding: Binary" + CRLF
+      };
+    List<Line> message = toLineList(messageRaw);
+    
+    final Header header = BatchParserCommon.consumeHeaders(message);
+    assertNotNull(header);
+    
+    final List<String> acceptLanguageHeader = header.getHeaders(HttpHeader.ACCEPT_LANGUAGE);
+    assertNotNull(acceptLanguageHeader);
+    assertEquals(3, acceptLanguageHeader.size());
+  }
+  
+  @Test
+  public void testRemoveEndingCRLF() {
+    String line = "Test\r\n";
+    assertEquals("Test", BatchParserCommon.removeEndingCRLF(new Line(line,1)).toString());
+  }
+
+  @Test
+  public void testRemoveLastEndingCRLF() {
+    String line = "Test\r\n\r\n";
+    assertEquals("Test\r\n", BatchParserCommon.removeEndingCRLF(new Line(line,1)).toString());
+  }
+
+  @Test
+  public void testRemoveEndingCRLFWithWS() {
+    String line = "Test\r\n            ";
+    assertEquals("Test", BatchParserCommon.removeEndingCRLF(new Line(line,1)).toString());
+  }
+
+  @Test
+  public void testRemoveEndingCRLFNothingToRemove() {
+    String line = "Hallo\r\nBla";
+    assertEquals("Hallo\r\nBla", BatchParserCommon.removeEndingCRLF(new Line(line,1)).toString());
+  }
+
+  @Test
+  public void testRemoveEndingCRLFAll() {
+    String line = "\r\n";
+    assertEquals("", BatchParserCommon.removeEndingCRLF(new Line(line,1)).toString());
+  }
+
+  @Test
+  public void testRemoveEndingCRLFSpace() {
+    String line = "\r\n                      ";
+    assertEquals("", BatchParserCommon.removeEndingCRLF(new Line(line,1)).toString());
+  }
+
+  @Test
+  public void testRemoveLastEndingCRLFWithWS() {
+    String line = "Test            \r\n";
+    assertEquals("Test            ", BatchParserCommon.removeEndingCRLF(new Line(line,1)).toString());
+  }
+
+  @Test
+  public void testRemoveLastEndingCRLFWithWSLong() {
+    String line = "Test            \r\nTest2    \r\n";
+    assertEquals("Test            \r\nTest2    ", BatchParserCommon.removeEndingCRLF(new Line(line,1)).toString());
+  }
+  
+  private List<Line> toLineList(String[] messageRaw) {
+    final List<Line> lineList = new ArrayList<Line>();
+    int counter = 1;
+    
+    for(final String currentLine : messageRaw) {
+      lineList.add(new Line(currentLine, counter++));
+    }
+    
+    return lineList;
+  }
+}


[07/22] olingo-odata4 git commit: Changeset Sorter

Posted by ch...@apache.org.
Changeset Sorter

Signed-off-by: Christian Amend <ch...@apache.org>


Project: http://git-wip-us.apache.org/repos/asf/olingo-odata4/repo
Commit: http://git-wip-us.apache.org/repos/asf/olingo-odata4/commit/ee2451cb
Tree: http://git-wip-us.apache.org/repos/asf/olingo-odata4/tree/ee2451cb
Diff: http://git-wip-us.apache.org/repos/asf/olingo-odata4/diff/ee2451cb

Branch: refs/heads/master
Commit: ee2451cbd064e3a5b4585f37192c983f32fe5c55
Parents: 15bd152
Author: Christian Holzer <c....@sap.com>
Authored: Tue Oct 28 18:18:21 2014 +0100
Committer: Christian Amend <ch...@apache.org>
Committed: Thu Nov 13 17:10:55 2014 +0100

----------------------------------------------------------------------
 .../olingo/server/api/batch/BatchOperation.java |   4 +-
 .../server/api/processor/BatchProcessor.java    |  10 +-
 .../olingo/server/core/batch/StringUtil.java    |  54 ------
 .../core/batch/handler/BatchOperationImpl.java  |  16 +-
 .../core/batch/handler/BatchPartHandler.java    |  65 ++++++--
 .../server/core/batch/handler/UriMapping.java   |  34 ++++
 .../core/batch/parser/BatchRequestPartImpl.java |   4 +
 .../olingo/server/core/batch/StringUtil.java    |  54 ++++++
 .../batch/handler/BatchChangeSetSorterTest.java | 164 +++++++++++++++++++
 .../tecsvc/processor/TechnicalProcessor.java    |  44 +----
 10 files changed, 333 insertions(+), 116 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/ee2451cb/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchOperation.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchOperation.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchOperation.java
index 8e438cf..af7d9c1 100644
--- a/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchOperation.java
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchOperation.java
@@ -27,9 +27,9 @@ import org.apache.olingo.server.api.ODataResponse;
 public interface BatchOperation {
   public List<BatchRequestPart> parseBatchRequest(InputStream in) throws BatchException;
 
-  public ODataResponse handleODataRequest(ODataRequest request);
+  public ODataResponse handleODataRequest(ODataRequest request, BatchRequestPart requestPart);
 
-  public ODataResponsePart handleBatchRequest(BatchRequestPart request);
+  public ODataResponsePart handleBatchRequest(BatchRequestPart request) throws BatchException;
 
   public void writeResponseParts(List<ODataResponsePart> batchResponses, ODataResponse response) throws BatchException,
       IOException;

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/ee2451cb/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/BatchProcessor.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/BatchProcessor.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/BatchProcessor.java
index 5a9518d..221b38a 100644
--- a/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/BatchProcessor.java
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/BatchProcessor.java
@@ -6,9 +6,9 @@
  * to you under the Apache License, Version 2.0 (the
  * "License"); you may not use this file except in compliance
  * with the License. You may obtain a copy of the License at
- *
+ * 
  * http://www.apache.org/licenses/LICENSE-2.0
- *
+ * 
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@@ -23,9 +23,11 @@ import java.util.List;
 import org.apache.olingo.server.api.ODataRequest;
 import org.apache.olingo.server.api.ODataResponse;
 import org.apache.olingo.server.api.batch.BatchOperation;
+import org.apache.olingo.server.api.batch.BatchRequestPart;
 
 public interface BatchProcessor extends Processor {
-  void executeBatch(BatchOperation operation, ODataRequest requst, ODataResponse response);
+  void executeBatch(BatchOperation operation, ODataRequest requests, ODataResponse response);
 
-  List<ODataResponse> executeChangeSet(BatchOperation operation, List<ODataRequest> requests);
+  List<ODataResponse> executeChangeSet(BatchOperation operation, List<ODataRequest> requests,
+      BatchRequestPart requestPart);
 }

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/ee2451cb/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/StringUtil.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/StringUtil.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/StringUtil.java
deleted file mode 100644
index 2602852..0000000
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/StringUtil.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.olingo.server.core.batch;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.UnsupportedEncodingException;
-
-import org.apache.olingo.commons.api.ODataRuntimeException;
-import org.apache.olingo.server.core.batch.parser.BufferedReaderIncludingLineEndings;
-
-public class StringUtil {
-  
-  
-  public static String toString(final InputStream in) throws IOException {
-    final StringBuilder builder = new StringBuilder();
-    final BufferedReaderIncludingLineEndings reader = new BufferedReaderIncludingLineEndings(new InputStreamReader(in));
-    String currentLine;
-    
-    while((currentLine = reader.readLine()) != null) {
-      builder.append(currentLine);
-    }
-    
-    reader.close();
-    
-    return builder.toString();
-  }
-
-  public static InputStream toInputStream(final String string) {
-    try {
-      return new ByteArrayInputStream(string.getBytes("UTF-8"));
-    } catch (UnsupportedEncodingException e) {
-      throw new ODataRuntimeException("Charset UTF-8 not found");
-    }
-  }
-}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/ee2451cb/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchOperationImpl.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchOperationImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchOperationImpl.java
index bc148f0..1f5e243 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchOperationImpl.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchOperationImpl.java
@@ -6,9 +6,9 @@
  * to you under the Apache License, Version 2.0 (the
  * "License"); you may not use this file except in compliance
  * with the License. You may obtain a copy of the License at
- *
+ * 
  * http://www.apache.org/licenses/LICENSE-2.0
- *
+ * 
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@@ -44,21 +44,21 @@ public class BatchOperationImpl implements BatchOperation {
     partHandler = new BatchPartHandler(oDataHandler, batchProcessor, this);
     writer = new BatchResponseWriter();
     parser = new BatchParser(getContentType(request), request.getRawBaseUri(),
-                              request.getRawServiceResolutionUri(), isStrict);
+        request.getRawServiceResolutionUri(), isStrict);
   }
 
   @Override
   public List<BatchRequestPart> parseBatchRequest(InputStream in) throws BatchException {
     return parser.parseBatchRequest(in);
   }
-
+  
   @Override
-  public ODataResponse handleODataRequest(ODataRequest request) {
-    return partHandler.handleODataRequest(request);
+  public ODataResponse handleODataRequest(ODataRequest request, BatchRequestPart requestPart) {
+    return partHandler.handleODataRequest(request, requestPart);
   }
-
+  
   @Override
-  public ODataResponsePart handleBatchRequest(BatchRequestPart request) {
+  public ODataResponsePart handleBatchRequest(BatchRequestPart request) throws BatchException {
     return partHandler.handleBatchRequest(request);
   }
 

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/ee2451cb/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchPartHandler.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchPartHandler.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchPartHandler.java
index a78d585..df23c55 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchPartHandler.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchPartHandler.java
@@ -19,11 +19,14 @@
 package org.apache.olingo.server.core.batch.handler;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 import org.apache.olingo.commons.api.http.HttpHeader;
 import org.apache.olingo.server.api.ODataRequest;
 import org.apache.olingo.server.api.ODataResponse;
+import org.apache.olingo.server.api.batch.BatchException;
 import org.apache.olingo.server.api.batch.BatchOperation;
 import org.apache.olingo.server.api.batch.BatchRequestPart;
 import org.apache.olingo.server.api.batch.ODataResponsePart;
@@ -36,7 +39,8 @@ public class BatchPartHandler {
   private ODataHandler oDataHandler;
   private BatchProcessor batchProcessor;
   private BatchOperation batchOperation;
-
+  private Map<BatchRequestPart, UriMapping> uriMapping = new HashMap<BatchRequestPart, UriMapping>();
+  
   public BatchPartHandler(final ODataHandler oDataHandler, final BatchProcessor processor,
       final BatchOperation batchOperation) {
     this.oDataHandler = oDataHandler;
@@ -44,23 +48,62 @@ public class BatchPartHandler {
     this.batchOperation = batchOperation;
   }
 
-  public ODataResponse handleODataRequest(ODataRequest request) {
-    final ODataResponse response = oDataHandler.process(request);
-    response.setHeader(BatchParserCommon.HTTP_CONTENT_ID, request.getHeader(BatchParserCommon.HTTP_CONTENT_ID));
+  public ODataResponse handleODataRequest(ODataRequest request, BatchRequestPart requestPart) {
+    final ODataResponse response;
+    
+    if(requestPart.isChangeSet()) {
+      final UriMapping mapping = getUriMappingOrDefault(requestPart);
+      final String reference = BatchChangeSetSorter.getReferenceInURI(request);
+      if(reference != null) {
+        BatchChangeSetSorter.replaceContentIdReference(request, reference, mapping.getUri(reference));
+      }
+      
+       response = oDataHandler.process(request);
+       
+      final String contentId = request.getHeader(BatchParserCommon.HTTP_CONTENT_ID);
+      final String locationHeader = request.getHeader(HttpHeader.LOCATION);
+      mapping.addMapping(contentId, locationHeader);
+    } else {
+      response = oDataHandler.process(request);
+    }
+    
+    final String contentId = request.getHeader(BatchParserCommon.HTTP_CONTENT_ID);
+    if(contentId != null) {
+      response.setHeader(BatchParserCommon.HTTP_CONTENT_ID, contentId);
+    }
     
     return  response;
   }
-
-  public ODataResponsePart handleBatchRequest(BatchRequestPart request) {
-    final List<ODataResponse> responses = new ArrayList<ODataResponse>();
-
+  
+  private UriMapping getUriMappingOrDefault(final BatchRequestPart requestPart) {
+    UriMapping mapping = uriMapping.get(requestPart);
+    
+    if(uriMapping == null) {
+      mapping = new UriMapping();
+    }
+    uriMapping.put(requestPart, mapping);
+    
+    return mapping;
+  }
+  
+  public ODataResponsePart handleBatchRequest(BatchRequestPart request) throws BatchException {
     if (request.isChangeSet()) {
-      responses.addAll(batchProcessor.executeChangeSet(batchOperation, request.getRequests()));
-      return new ODataResponsePartImpl(responses, true);
+      return handleChangeSet(request);
     } else {
-      responses.add(handleODataRequest(request.getRequests().get(0)));
+      final List<ODataResponse> responses = new ArrayList<ODataResponse>();
+      responses.add(handleODataRequest(request.getRequests().get(0), request));
+      
       return new ODataResponsePartImpl(responses, false);
     }
   }
 
+  private ODataResponsePart handleChangeSet(BatchRequestPart request) throws BatchException {
+    final List<ODataResponse> responses = new ArrayList<ODataResponse>();
+    final BatchChangeSetSorter sorter = new BatchChangeSetSorter(request.getRequests());
+
+    responses.addAll(batchProcessor.executeChangeSet(batchOperation, sorter.getOrderdRequests(), request));
+    
+    return new ODataResponsePartImpl(responses, true);
+  }
+
 }

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/ee2451cb/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/UriMapping.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/UriMapping.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/UriMapping.java
new file mode 100644
index 0000000..0123320
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/UriMapping.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.batch.handler;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class UriMapping {
+  private Map<String, String> uriMapping = new HashMap<String, String>();
+  
+  public void addMapping(final String contentId, final String uri) {
+    uriMapping.put(contentId, uri);
+  }
+  
+  public String getUri(final String contentId) {
+    return uriMapping.get(contentId);
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/ee2451cb/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchRequestPartImpl.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchRequestPartImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchRequestPartImpl.java
index 6c80216..9765b37 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchRequestPartImpl.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchRequestPartImpl.java
@@ -25,6 +25,10 @@ import java.util.List;
 import org.apache.olingo.server.api.ODataRequest;
 import org.apache.olingo.server.api.batch.BatchRequestPart;
 
+/**
+ * Has to be immutable!
+ *
+ */
 public class BatchRequestPartImpl implements BatchRequestPart {
 
   private List<ODataRequest> requests = new ArrayList<ODataRequest>();

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/ee2451cb/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/StringUtil.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/StringUtil.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/StringUtil.java
new file mode 100644
index 0000000..2602852
--- /dev/null
+++ b/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/StringUtil.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.batch;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+
+import org.apache.olingo.commons.api.ODataRuntimeException;
+import org.apache.olingo.server.core.batch.parser.BufferedReaderIncludingLineEndings;
+
+public class StringUtil {
+  
+  
+  public static String toString(final InputStream in) throws IOException {
+    final StringBuilder builder = new StringBuilder();
+    final BufferedReaderIncludingLineEndings reader = new BufferedReaderIncludingLineEndings(new InputStreamReader(in));
+    String currentLine;
+    
+    while((currentLine = reader.readLine()) != null) {
+      builder.append(currentLine);
+    }
+    
+    reader.close();
+    
+    return builder.toString();
+  }
+
+  public static InputStream toInputStream(final String string) {
+    try {
+      return new ByteArrayInputStream(string.getBytes("UTF-8"));
+    } catch (UnsupportedEncodingException e) {
+      throw new ODataRuntimeException("Charset UTF-8 not found");
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/ee2451cb/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/BatchChangeSetSorterTest.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/BatchChangeSetSorterTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/BatchChangeSetSorterTest.java
new file mode 100644
index 0000000..9196514
--- /dev/null
+++ b/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/BatchChangeSetSorterTest.java
@@ -0,0 +1,164 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.batch.handler;
+
+import static org.junit.Assert.*;
+
+import java.io.ByteArrayInputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.olingo.commons.api.http.HttpMethod;
+import org.apache.olingo.server.api.ODataRequest;
+import org.apache.olingo.server.api.batch.BatchException;
+import org.apache.olingo.server.core.batch.parser.BatchParserCommon;
+import org.junit.Test;
+
+public class BatchChangeSetSorterTest {
+
+  private static final String BASE_URI = "http://localhost/odata.src";
+  
+  @Test
+  public void test() throws BatchException {
+    final List<ODataRequest> changeSet = new ArrayList<ODataRequest>();
+    changeSet.add(createRequest(HttpMethod.POST, BASE_URI, "$1/Adress", "2"));
+    changeSet.add(createRequest(HttpMethod.POST, BASE_URI, "Employees", "1"));
+    
+   BatchChangeSetSorter sorter = new BatchChangeSetSorter(changeSet);
+   final List<ODataRequest> sortedChangeSet = sorter.getOrderdRequests();
+   
+   assertEquals(2, sortedChangeSet.size());
+   assertEquals("1", getContentId(sortedChangeSet.get(0)));
+   assertEquals("2", getContentId(sortedChangeSet.get(1)));
+  }
+  
+  private String getContentId(ODataRequest request) {
+    return request.getHeader(BatchParserCommon.HTTP_CONTENT_ID);
+  }
+  
+  @Test
+  public void testNoContentId() throws BatchException {
+    final List<ODataRequest> changeSet = new ArrayList<ODataRequest>();
+    changeSet.add(createRequest(HttpMethod.POST, BASE_URI, "$1/Department", "2"));
+    changeSet.add(createRequest(HttpMethod.POST, BASE_URI, "Employees", "1"));
+    changeSet.add(createRequest(HttpMethod.POST, BASE_URI, "Employee('2')/Address"));
+    changeSet.add(createRequest(HttpMethod.POST, BASE_URI, "Employee('3')/Address"));
+    changeSet.add(createRequest(HttpMethod.POST, BASE_URI, "$2/Manager", "3"));
+    
+    BatchChangeSetSorter sorter = new BatchChangeSetSorter(changeSet);
+   final List<ODataRequest> sortedChangeSet = sorter.getOrderdRequests();
+   
+   assertEquals(5, sortedChangeSet.size());
+   assertEquals("1", getContentId(sortedChangeSet.get(0)));
+   assertEquals(null, getContentId(sortedChangeSet.get(1)));
+   assertEquals(null, getContentId(sortedChangeSet.get(2)));
+   assertEquals("2", getContentId(sortedChangeSet.get(3)));
+   assertEquals("3", getContentId(sortedChangeSet.get(4)));
+  }
+  
+  @SuppressWarnings("unused")
+  @Test(expected=BatchException.class)
+  public void testContentIdNotAvailable() throws BatchException {
+    final List<ODataRequest> changeSet = new ArrayList<ODataRequest>();
+    changeSet.add(createRequest(HttpMethod.POST, BASE_URI, "$1/Department", "2"));
+    changeSet.add(createRequest(HttpMethod.POST, BASE_URI, "Employees", "1"));
+    changeSet.add(createRequest(HttpMethod.POST, BASE_URI, "Employee('2')/Address"));
+    changeSet.add(createRequest(HttpMethod.POST, BASE_URI, "Employee('3')/Address"));
+    changeSet.add(createRequest(HttpMethod.POST, BASE_URI, "$4/Manager", "3")); //4 is not available
+    
+   BatchChangeSetSorter sorter = new BatchChangeSetSorter(changeSet);
+   final List<ODataRequest> sortedChangeSet = sorter.getOrderdRequests();
+  }
+  
+  @Test
+  public void testStringAsContentId() throws BatchException {
+    final List<ODataRequest> changeSet = new ArrayList<ODataRequest>();
+    changeSet.add(createRequest(HttpMethod.POST, BASE_URI, "$One/Department", "Two"));
+    changeSet.add(createRequest(HttpMethod.POST, BASE_URI, "Employees", "One"));
+    changeSet.add(createRequest(HttpMethod.POST, BASE_URI, "Employee('2')/Address"));
+    changeSet.add(createRequest(HttpMethod.POST, BASE_URI, "Employee('3')/Address"));
+    changeSet.add(createRequest(HttpMethod.POST, BASE_URI, "$Two/Manager", "Three"));
+    
+   BatchChangeSetSorter sorter = new BatchChangeSetSorter(changeSet);
+   final List<ODataRequest> sortedChangeSet = sorter.getOrderdRequests();
+   
+   assertEquals(5, sortedChangeSet.size());
+   assertEquals("One", getContentId(sortedChangeSet.get(0)));
+   assertEquals(null, getContentId(sortedChangeSet.get(1)));
+   assertEquals(null, getContentId(sortedChangeSet.get(2)));
+   assertEquals("Two", getContentId(sortedChangeSet.get(3)));
+   assertEquals("Three", getContentId(sortedChangeSet.get(4)));
+  }
+  
+  @Test
+  public void testRewriting() {
+    final String CONTENT_ID = "1";
+    final String ODATA_PATH ="$" + CONTENT_ID + "/Address";
+    final String RESOURCE_URI = "Employee('1')";
+    final ODataRequest request = createRequest(HttpMethod.POST, BASE_URI, ODATA_PATH);
+    
+    BatchChangeSetSorter.replaceContentIdReference(request, CONTENT_ID, RESOURCE_URI);
+    assertEquals(BASE_URI + "/" + "Employee('1')/Address", request.getRawRequestUri());
+    assertEquals("Employee('1')/Address", request.getRawODataPath());
+  }
+  
+  @Test
+  public void testRewritingNoContentId() {
+    final String CONTENT_ID = "1";
+    final String ODATA_PATH = /* "$" + CONTENT_ID + */ "Address";
+    final String RESOURCE_URI = "Employee('1')";
+    final ODataRequest request = createRequest(HttpMethod.POST, BASE_URI, ODATA_PATH);
+    
+    BatchChangeSetSorter.replaceContentIdReference(request, CONTENT_ID, RESOURCE_URI);
+    assertEquals(BASE_URI + "/" + "Address", request.getRawRequestUri());
+    assertEquals("Address", request.getRawODataPath());
+  }
+  
+  @Test
+  public void testWrongRewriting() {
+    final String CONTENT_ID = "1";
+    final String ODATA_PATH = /*"$1" */ "$2" + "/Address";
+    final String RESOURCE_URI = "Employee('1')";
+    final ODataRequest request = createRequest(HttpMethod.POST, BASE_URI, ODATA_PATH);
+    
+    BatchChangeSetSorter.replaceContentIdReference(request, CONTENT_ID, RESOURCE_URI);
+    assertEquals(BASE_URI + "/" + "$2/Address", request.getRawRequestUri());
+    assertEquals("$2/Address", request.getRawODataPath());
+  }
+  
+  private ODataRequest createRequest(HttpMethod method, String baseUrl, String oDataPath) {
+    return createRequest(method, baseUrl, oDataPath, null);
+  }
+  
+  private ODataRequest createRequest(HttpMethod method, String baseUrl, String oDataPath, String contentId) {
+    final ODataRequest request = new ODataRequest();
+    request.setBody(new ByteArrayInputStream(new byte[0]));
+    request.setMethod(HttpMethod.GET);
+    request.setRawBaseUri(baseUrl);
+    request.setRawODataPath(oDataPath);
+    request.setRawRequestUri(baseUrl + "/" + oDataPath);
+    request.setRawQueryPath("");
+    
+    if(contentId != null) {
+      request.addHeader(BatchParserCommon.HTTP_CONTENT_ID, Arrays.asList(new String[] { contentId }));
+    }
+    return request;
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/ee2451cb/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalProcessor.java
----------------------------------------------------------------------
diff --git a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalProcessor.java b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalProcessor.java
index b59c92e..e5a0d44 100644
--- a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalProcessor.java
+++ b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalProcessor.java
@@ -21,7 +21,6 @@ package org.apache.olingo.server.tecsvc.processor;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
-import java.util.ArrayList;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Locale;
@@ -74,7 +73,7 @@ import org.apache.olingo.server.tecsvc.data.DataProvider;
 /**
  * Technical Processor which provides currently implemented processor functionality.
  */
-public class TechnicalProcessor implements EntitySetProcessor, EntityProcessor, PropertyProcessor, BatchProcessor {
+public class TechnicalProcessor implements EntitySetProcessor, EntityProcessor, PropertyProcessor {
 
   private OData odata;
   private DataProvider dataProvider;
@@ -198,12 +197,12 @@ public class TechnicalProcessor implements EntitySetProcessor, EntityProcessor,
     List<UriResource> subResPaths = resourcePaths.subList(1, resourcePaths.size());
     for (UriResource subResPath : subResPaths) {
       UriResourceKind kind = subResPath.getKind();
-      if(kind != UriResourceKind.primitiveProperty
-              && kind != UriResourceKind.complexProperty
-              && kind != UriResourceKind.count
-              && kind != UriResourceKind.value) {
+      if (kind != UriResourceKind.primitiveProperty
+          && kind != UriResourceKind.complexProperty
+          && kind != UriResourceKind.count
+          && kind != UriResourceKind.value) {
         throw new ODataApplicationException("Invalid resource type.",
-                HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), Locale.ROOT);
+            HttpStatusCode.NOT_IMPLEMENTED.getStatusCode(), Locale.ROOT);
       }
     }
 
@@ -219,6 +218,7 @@ public class TechnicalProcessor implements EntitySetProcessor, EntityProcessor,
       final EdmEntitySet entitySet, final boolean isSingleEntity,
       final ExpandOption expand, final SelectOption select,
       final List<UriParameter> keys, final String propertyPath) throws SerializerException {
+
     return ContextURL.with().entitySet(entitySet)
         .selectList(serializer.buildContextURLSelectList(entitySet, expand, select))
         .suffix(isSingleEntity && propertyPath == null ? Suffix.ENTITY : null)
@@ -226,7 +226,6 @@ public class TechnicalProcessor implements EntitySetProcessor, EntityProcessor,
         .navOrPropertyPath(propertyPath)
         .build();
   }
-
   @Override
   public void readProperty(final ODataRequest request, ODataResponse response, final UriInfo uriInfo,
       final ContentType contentType) throws ODataApplicationException, SerializerException {
@@ -334,33 +333,4 @@ public class TechnicalProcessor implements EntitySetProcessor, EntityProcessor,
       }
     }
   }
-
-  @Override
-  public void executeBatch(BatchOperation operation, ODataRequest requst, ODataResponse response) {
-    try {
-      final List<BatchRequestPart> parts = operation.parseBatchRequest(requst.getBody());
-      final List<ODataResponsePart> responseParts = new ArrayList<ODataResponsePart>();
-      
-      for(BatchRequestPart part : parts) {
-        responseParts.add(operation.handleBatchRequest(part));
-      }
-      
-      operation.writeResponseParts(responseParts, response);
-    } catch (BatchException e) {
-      throw new ODataRuntimeException(e);
-    } catch (IOException e) {
-      throw new ODataRuntimeException(e);
-    }
-  }
-
-  @Override
-  public List<ODataResponse> executeChangeSet(BatchOperation operation, List<ODataRequest> requests) {
-    List<ODataResponse> responses = new ArrayList<ODataResponse>();
-    
-    for(ODataRequest request : requests) {
-      responses.add(operation.handleODataRequest(request));
-    }
-    
-    return responses;
-  }
 }


[13/22] olingo-odata4 git commit: Test added to MockedBatchHandlerTest

Posted by ch...@apache.org.
Test added to MockedBatchHandlerTest

Signed-off-by: Christian Amend <ch...@apache.org>


Project: http://git-wip-us.apache.org/repos/asf/olingo-odata4/repo
Commit: http://git-wip-us.apache.org/repos/asf/olingo-odata4/commit/bc46b535
Tree: http://git-wip-us.apache.org/repos/asf/olingo-odata4/tree/bc46b535
Diff: http://git-wip-us.apache.org/repos/asf/olingo-odata4/diff/bc46b535

Branch: refs/heads/master
Commit: bc46b5352e9cb9dae3e46478dab34287e2ea62bc
Parents: 4ff5fb9
Author: Christian Holzer <c....@sap.com>
Authored: Tue Nov 11 16:17:51 2014 +0100
Committer: Christian Amend <ch...@apache.org>
Committed: Thu Nov 13 17:11:01 2014 +0100

----------------------------------------------------------------------
 .../batch/handler/MockedBatchHandlerTest.java   | 84 +++++++++++++++-----
 1 file changed, 63 insertions(+), 21 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/bc46b535/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/MockedBatchHandlerTest.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/MockedBatchHandlerTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/MockedBatchHandlerTest.java
index 7a85ffa..b81edb8 100644
--- a/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/MockedBatchHandlerTest.java
+++ b/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/MockedBatchHandlerTest.java
@@ -200,6 +200,46 @@ public class MockedBatchHandlerTest {
   }
 
   @Test
+  public void testGetRequest() throws BatchException, IOException {
+    final String content = ""
+        + "--batch_12345" + CRLF
+        + "Content-Type: application/http" + CRLF
+        + "Content-Transfer-Encoding: binary" + CRLF
+        + CRLF
+        + "GET ESAllPrim(0) HTTP/1.1" + CRLF
+        + CRLF
+        + CRLF
+        + "--batch_12345--";
+
+    final Map<String, List<String>> header = getMimeHeader();
+    final ODataResponse response = new ODataResponse();
+    final ODataRequest request = buildODataRequest(content, header);
+
+    batchHandler.process(request, response, true);
+
+    BufferedReaderIncludingLineEndings reader =
+        new BufferedReaderIncludingLineEndings(new InputStreamReader(response.getContent()));
+
+    final List<String> responseContent = reader.toList();
+    int line = 0;
+
+    assertEquals(9, responseContent.size());
+    assertTrue(responseContent.get(line++).contains("--batch_"));
+    assertEquals("Content-Type: application/http" + CRLF, responseContent.get(line++));
+    assertEquals("Content-Transfer-Encoding: binary" + CRLF, responseContent.get(line++));
+    assertEquals(CRLF, responseContent.get(line++));
+    assertEquals("HTTP/1.1 200 OK" + CRLF, responseContent.get(line++));
+    assertEquals("Content-Length: 0" + CRLF, responseContent.get(line++));
+    assertEquals(CRLF, responseContent.get(line++));
+    assertEquals(CRLF, responseContent.get(line++));
+    assertTrue(responseContent.get(line++).contains("--batch_"));
+
+    assertEquals(9, line);
+
+    reader.close();
+  }
+
+  @Test
   public void testMultipleChangeSets() throws BatchException, IOException {
     final String content = ""
         + "--batch_12345" + CRLF
@@ -442,8 +482,8 @@ public class MockedBatchHandlerTest {
 
     return contentId;
   }
-  
-  @Test(expected=BatchException.class)
+
+  @Test(expected = BatchException.class)
   public void testInvalidMethod() throws UnsupportedEncodingException, BatchException {
     final String content = ""
         + "--batch_12345" + CRLF
@@ -454,23 +494,23 @@ public class MockedBatchHandlerTest {
         + "Content-Transfer-Encoding: binary" + CRLF
         + "Content-Id: 1" + CRLF
         + CRLF
-        + "PUT ESAllPrim(1) HTTP/1.1" + CRLF 
+        + "PUT ESAllPrim(1) HTTP/1.1" + CRLF
         + "Content-Type: application/json;odata=verbose" + CRLF
         + CRLF
         + CRLF
         + "--changeset_12345--" + CRLF
         + CRLF
         + "--batch_12345--";
-    
+
     final Map<String, List<String>> header = getMimeHeader();
     final ODataResponse response = new ODataResponse();
     final ODataRequest request = buildODataRequest(content, header);
     request.setMethod(HttpMethod.GET);
-    
+
     batchHandler.process(request, response, true);
   }
-  
-  @Test(expected=BatchException.class)
+
+  @Test(expected = BatchException.class)
   public void testInvalidContentType() throws UnsupportedEncodingException, BatchException {
     final String content = ""
         + "--batch_12345" + CRLF
@@ -481,22 +521,22 @@ public class MockedBatchHandlerTest {
         + "Content-Transfer-Encoding: binary" + CRLF
         + "Content-Id: 1" + CRLF
         + CRLF
-        + "PUT ESAllPrim(1) HTTP/1.1" + CRLF 
+        + "PUT ESAllPrim(1) HTTP/1.1" + CRLF
         + "Content-Type: application/json;odata=verbose" + CRLF
         + CRLF
         + CRLF
         + "--changeset_12345--" + CRLF
         + CRLF
         + "--batch_12345--";
-    
+
     final Map<String, List<String>> header = new HashMap<String, List<String>>();
     header.put(HttpHeader.CONTENT_TYPE, Arrays.asList(new String[] { "application/http" }));
     final ODataResponse response = new ODataResponse();
     final ODataRequest request = buildODataRequest(content, header);
-    
+
     batchHandler.process(request, response, true);
   }
-  
+
   /*
    * Helper methods
    */
@@ -540,16 +580,6 @@ public class MockedBatchHandlerTest {
       List<ODataResponse> responses = new ArrayList<ODataResponse>();
 
       for (ODataRequest request : requests) {
-        // Mock the processor for a given requests
-        when(oDataHandler.process(request)).then(new Answer<ODataResponse>() {
-          @Override
-          public ODataResponse answer(InvocationOnMock invocation) throws Throwable {
-            Object[] arguments = invocation.getArguments();
-
-            return buildResponse((ODataRequest) arguments[0]);
-          }
-        });
-
         try {
           responses.add(operation.handleODataRequest(request, requestPart));
         } catch (BatchException e) {
@@ -567,6 +597,18 @@ public class MockedBatchHandlerTest {
         final List<ODataResponsePart> responseParts = new ArrayList<ODataResponsePart>();
 
         for (BatchRequestPart part : parts) {
+          for (final ODataRequest oDataRequest : part.getRequests()) {
+            // Mock the processor for a given requests
+            when(oDataHandler.process(oDataRequest)).then(new Answer<ODataResponse>() {
+              @Override
+              public ODataResponse answer(InvocationOnMock invocation) throws Throwable {
+                Object[] arguments = invocation.getArguments();
+
+                return buildResponse((ODataRequest) arguments[0]);
+              }
+            });
+          }
+
           responseParts.add(operation.handleBatchRequest(part));
         }
 


[04/22] olingo-odata4 git commit: [OLINGO-472] BatchParser first draft

Posted by ch...@apache.org.
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/32247295/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/writer/BatchResponseWriter.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/writer/BatchResponseWriter.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/writer/BatchResponseWriter.java
new file mode 100644
index 0000000..a063747
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/writer/BatchResponseWriter.java
@@ -0,0 +1,228 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.batch.writer;
+
+import java.io.BufferedWriter;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStreamWriter;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import org.apache.olingo.commons.api.http.HttpContentType;
+import org.apache.olingo.commons.api.http.HttpHeader;
+import org.apache.olingo.commons.api.http.HttpStatusCode;
+import org.apache.olingo.server.api.ODataResponse;
+import org.apache.olingo.server.api.batch.ODataResponsePart;
+import org.apache.olingo.server.core.batch.BatchException;
+import org.apache.olingo.server.core.batch.BatchException.MessageKeys;
+import org.apache.olingo.server.core.batch.parser.BatchParserCommon;
+import org.apache.olingo.server.core.serializer.utils.CircleStreamBuffer;
+
+public class BatchResponseWriter {
+  private static final int BUFFER_SIZE = 4096;
+  private static final String DOUBLE_DASH = "--";
+  private static final String COLON = ":";
+  private static final String SP = " ";
+  private static final String CRLF = "\r\n";
+
+  public void toODataResponse(final List<ODataResponsePart> batchResponse, final ODataResponse response)
+      throws IOException, BatchException {
+    final String boundary = generateBoundary("batch");
+
+    setStatusCode(response);
+    ResponseWriter writer = createBody(batchResponse, boundary);
+
+    response.setContent(writer.toInputStream());
+    setHttpHeader(response, writer, boundary);
+  }
+
+  private ResponseWriter createBody(final List<ODataResponsePart> batchResponses, final String boundary)
+      throws IOException, BatchException {
+    final ResponseWriter writer = new ResponseWriter();
+
+    for (final ODataResponsePart part : batchResponses) {
+      writer.append(getDashBoundary(boundary));
+
+      if (part.isChangeSet()) {
+        appendChangeSet(part, writer);
+      } else {
+        appendBodyPart(part.getResponses().get(0), writer, false);
+      }
+    }
+    writer.append(getCloseDelimiter(boundary));
+
+    return writer;
+  }
+
+  private void appendChangeSet(ODataResponsePart part, ResponseWriter writer) throws IOException, BatchException {
+    final String changeSetBoundary = generateBoundary("changeset");
+
+    appendChangeSetHeader(writer, changeSetBoundary);
+    writer.append(CRLF);
+
+    for (final ODataResponse response : part.getResponses()) {
+      writer.append(getDashBoundary(changeSetBoundary));
+      appendBodyPart(response, writer, true);
+    }
+
+    writer.append(getCloseDelimiter(changeSetBoundary));
+    writer.append(CRLF);
+  }
+
+  private void appendBodyPart(ODataResponse response, ResponseWriter writer, boolean isChangeSet) throws IOException,
+      BatchException {
+    byte[] body = getBody(response);
+    
+    appendBodyPartHeader(response, writer, isChangeSet);
+    writer.append(CRLF);
+
+    appendStatusLine(response, writer);
+    appendResponseHeader(response, body.length, writer);
+    writer.append(CRLF);
+
+    writer.append(body);
+    writer.append(CRLF);
+  }
+
+  private byte[] getBody(final ODataResponse response) throws IOException {
+    final InputStream content = response.getContent();
+    final ByteArrayOutputStream out = new ByteArrayOutputStream();
+    
+    if (content != null) {
+      byte[] buffer = new byte[BUFFER_SIZE];
+      int n;
+
+      while ((n = content.read(buffer, 0, buffer.length)) != -1) {
+        out.write(buffer, 0, n);
+      }
+      out.flush();
+      
+      return out.toByteArray();
+    } else {
+      return new byte[0];
+    }
+  }
+
+  private void appendChangeSetHeader(ResponseWriter writer, final String changeSetBoundary) throws IOException {
+    appendHeader(HttpHeader.CONTENT_TYPE, HttpContentType.MULTIPART_MIXED.toString() + "; boundary="
+        + changeSetBoundary, writer);
+  }
+
+  private void appendHeader(String name, String value, ResponseWriter writer) throws IOException {
+    writer.append(name)
+        .append(COLON)
+        .append(SP)
+        .append(value)
+        .append(CRLF);
+  }
+
+  private void appendStatusLine(ODataResponse response, ResponseWriter writer) throws IOException {
+    writer.append("HTTP/1.1")
+        .append(SP)
+        .append("" + response.getStatusCode())
+        .append(SP)
+        .append(HttpStatusCode.fromStatusCode(response.getStatusCode()).toString())
+        .append(CRLF);
+  }
+
+  private void appendResponseHeader(ODataResponse response, int contentLength, ResponseWriter writer)
+      throws IOException {
+    final Map<String, String> header = response.getHeaders();
+
+    for (final String key : header.keySet()) {
+      // Requests do never have content id header
+      if (!key.equalsIgnoreCase(BatchParserCommon.HTTP_CONTENT_ID)) {
+        appendHeader(key, header.get(key), writer);
+      }
+    }
+
+    appendHeader(HttpHeader.CONTENT_LENGTH, "" + contentLength, writer);
+  }
+
+  private void appendBodyPartHeader(ODataResponse response, ResponseWriter writer, boolean isChangeSet)
+      throws BatchException, IOException {
+    appendHeader(HttpHeader.CONTENT_TYPE, HttpContentType.APPLICATION_HTTP, writer);
+    appendHeader(BatchParserCommon.HTTP_CONTENT_TRANSFER_ENCODING, BatchParserCommon.BINARY_ENCODING, writer);
+
+    if (isChangeSet) {
+      if (response.getHeaders().get(BatchParserCommon.HTTP_CONTENT_ID) != null) {
+        appendHeader(BatchParserCommon.HTTP_CONTENT_ID, response.getHeaders().get(BatchParserCommon.HTTP_CONTENT_ID),
+            writer);
+      } else {
+        throw new BatchException("Missing content id", MessageKeys.MISSING_CONTENT_ID, "");
+      }
+    }
+  }
+
+  private void setHttpHeader(ODataResponse response, ResponseWriter writer, final String boundary) {
+    response.setHeader(HttpHeader.CONTENT_TYPE, HttpContentType.MULTIPART_MIXED.toString() + "; boundary=" + boundary);
+    response.setHeader(HttpHeader.CONTENT_LENGTH, "" + writer.length());
+  }
+
+  private void setStatusCode(final ODataResponse response) {
+    response.setStatusCode(HttpStatusCode.ACCEPTED.getStatusCode());
+  }
+
+  private String getDashBoundary(String boundary) {
+    return DOUBLE_DASH + boundary + CRLF;
+  }
+
+  private String getCloseDelimiter(final String boundary) {
+    return DOUBLE_DASH + boundary + DOUBLE_DASH + CRLF;
+  }
+
+  private String generateBoundary(final String value) {
+    return value + "_" + UUID.randomUUID().toString();
+  }
+
+  private static class ResponseWriter {
+    private CircleStreamBuffer buffer = new CircleStreamBuffer();
+    private BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(buffer.getOutputStream()));
+    private int length = 0;
+
+    public ResponseWriter append(final String content) throws IOException {
+      length += content.length();
+      writer.write(content);
+      
+      return this;
+    }
+
+    public ResponseWriter append(final byte[] content) throws IOException {
+      length += content.length;
+      writer.flush();
+      buffer.getOutputStream().write(content, 0, content.length);
+      
+      return this;
+    }
+
+    public int length() {
+      return length;
+    }
+
+    public InputStream toInputStream() throws IOException {
+      writer.flush();
+      writer.close();
+
+      return buffer.getInputStream();
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/32247295/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/writer/ODataResponsePartImpl.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/writer/ODataResponsePartImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/writer/ODataResponsePartImpl.java
new file mode 100644
index 0000000..a9711c9
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/writer/ODataResponsePartImpl.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.batch.writer;
+
+import java.util.List;
+
+import org.apache.olingo.server.api.ODataResponse;
+import org.apache.olingo.server.api.batch.ODataResponsePart;
+
+public class ODataResponsePartImpl implements ODataResponsePart {
+  private final List<ODataResponse> responses;
+  private final boolean isChangeSet;
+
+  public ODataResponsePartImpl(final List<ODataResponse> responses, final boolean isChangeSet) {
+    this.responses = responses;
+    this.isChangeSet = isChangeSet;
+  }
+
+  @Override
+  public List<ODataResponse> getResponses() {
+    return responses;
+  }
+
+  @Override
+  public boolean isChangeSet() {
+    return isChangeSet;
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/32247295/lib/server-core/src/main/resources/server-core-exceptions-i18n.properties
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/resources/server-core-exceptions-i18n.properties b/lib/server-core/src/main/resources/server-core-exceptions-i18n.properties
index 15f4ab8..7e58fc6 100644
--- a/lib/server-core/src/main/resources/server-core-exceptions-i18n.properties
+++ b/lib/server-core/src/main/resources/server-core-exceptions-i18n.properties
@@ -94,3 +94,24 @@ SerializerException.UNSUPPORTED_PROPERTY_TYPE=The type of the property '%1$s' is
 SerializerException.INCONSISTENT_PROPERTY_TYPE=An inconsistency has been detected in the type definition of property '%1$s'.
 SerializerException.MISSING_PROPERTY=The non-nullable property '%1$s' is missing.
 SerializerException.WRONG_PROPERTY_VALUE=The value '%2$s' is not valid for property '%1$s'.
+
+BatchException.INVALID_BOUNDARY=Invalid boundary at line '%1$s'.
+BatchException.INVALID_CHANGESET_METHOD=Invalid method: a ChangeSet cannot contain retrieve requests at line '%1$s'.
+BatchException.INVALID_CONTENT=Retrieve requests must not contain any body content '%1$s'.
+BatchException.INVALID_CONTENT_LENGTH=Invalid content length: content length have to be an integer and positive at line '%1$s'.
+BatchException.INVALID_CONTENT_TRANSFER_ENCODING=The Content-Transfer-Encoding should be binary: line '%1$s'.
+BatchException.INVALID_CONTENT_TYPE=Content-Type should be '%1$s'.
+BatchException.INVALID_HEADER=Invalid header: '%1$s' at line '%2$s'.
+BatchException.INVALID_HTTP_VERSION=Invalid HTTP version: The version have to be HTTP/1.1 at line '%1$s'.
+BatchException.INVALID_METHOD=Invalid HTTP method at line '%1$s'.
+BatchException.INVALID_QUERY_OPERATION_METHOD=Invalid method: a query operation can only contain retrieve requests at line '%1$s'.
+BatchException.INVALID_STATUS_LINE=Invalid HTTP status line at line '%1$s'.
+BatchException.INVALID_URI=Invalid URI at line '%1$s'.
+BatchException.FORBIDDEN_HEADER=Forbidden header at line '%1$s'.
+BatchException.MISSING_BLANK_LINE=Missing blank line at line '%1$s'.
+BatchException.MISSING_BOUNDARY_DELIMITER=Missing boundary delimiter at line '%1$s'.
+BatchException.MISSING_CLOSE_DELIMITER=Missing close delimiter at line '%1$s'.
+BatchException.MISSING_CONTENT_ID=Missing content-id at line '%1$s'.
+BatchException.MISSING_CONTENT_TRANSFER_ENCODING=Missing content transfer encoding at line '%1$s'.
+BatchException.MISSING_CONTENT_TYPE=Missing content-type at line '%1$s'.
+BatchException.MISSING_MANDATORY_HEADER=Missing mandatory header at line '%1$s'.
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/32247295/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/BatchRequestParserTest.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/BatchRequestParserTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/BatchRequestParserTest.java
new file mode 100644
index 0000000..1d68307
--- /dev/null
+++ b/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/BatchRequestParserTest.java
@@ -0,0 +1,1308 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.batch;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.URISyntaxException;
+import java.util.List;
+
+import org.apache.olingo.commons.api.http.HttpHeader;
+import org.apache.olingo.commons.api.http.HttpMethod;
+import org.apache.olingo.server.api.ODataRequest;
+import org.apache.olingo.server.api.batch.BatchRequestPart;
+import org.apache.olingo.server.core.batch.BatchException.MessageKeys;
+import org.apache.olingo.server.core.batch.parser.BatchParser;
+import org.apache.olingo.server.core.batch.parser.BatchParserCommon;
+import org.junit.Test;
+
+public class BatchRequestParserTest {
+
+  private static final String SERVICE_ROOT = "http://localhost/odata";
+  private static final String CONTENT_TYPE = "multipart/mixed;boundary=batch_8194-cf13-1f56";
+  private static final String CRLF = "\r\n";
+  private static final String MIME_HEADERS = "Content-Type: application/http" + CRLF
+      + "Content-Transfer-Encoding: binary" + CRLF;
+  private static final String GET_REQUEST = ""
+      + MIME_HEADERS
+      + CRLF
+      + "GET Employees('1')/EmployeeName HTTP/1.1" + CRLF
+      + CRLF
+      + CRLF;
+
+  @Test
+  public void test() throws IOException, BatchException, URISyntaxException {
+    final InputStream in = readFile("/batchWithPost.batch");
+    final List<BatchRequestPart> batchRequestParts = parse(in);
+
+    assertNotNull(batchRequestParts);
+    assertFalse(batchRequestParts.isEmpty());
+
+    for (BatchRequestPart object : batchRequestParts) {
+      if (!object.isChangeSet()) {
+        assertEquals(1, object.getRequests().size());
+        ODataRequest retrieveRequest = object.getRequests().get(0);
+        assertEquals(HttpMethod.GET, retrieveRequest.getMethod());
+
+        if (retrieveRequest.getHeaders(HttpHeader.ACCEPT_LANGUAGE) != null) {
+          assertEquals(3, retrieveRequest.getHeaders(HttpHeader.ACCEPT_LANGUAGE).size());
+        }
+
+        assertEquals(SERVICE_ROOT, retrieveRequest.getRawBaseUri());
+        assertEquals("/Employees('2')/EmployeeName", retrieveRequest.getRawODataPath());
+        assertEquals("http://localhost/odata/Employees('2')/EmployeeName?$format=json", retrieveRequest
+            .getRawRequestUri());
+        assertEquals("$format=json", retrieveRequest.getRawQueryPath());
+      } else {
+        List<ODataRequest> requests = object.getRequests();
+        for (ODataRequest request : requests) {
+
+          assertEquals(HttpMethod.PUT, request.getMethod());
+          assertEquals("100000", request.getHeader(HttpHeader.CONTENT_LENGTH));
+          assertEquals("application/json;odata=verbose", request.getHeader(HttpHeader.CONTENT_TYPE));
+
+          List<String> acceptHeader = request.getHeaders(HttpHeader.ACCEPT);
+          assertEquals(3, request.getHeaders(HttpHeader.ACCEPT).size());
+          assertEquals("application/atomsvc+xml;q=0.8", acceptHeader.get(0));
+          assertEquals("*/*;q=0.1", acceptHeader.get(2));
+
+          assertEquals("http://localhost/odata/Employees('2')/EmployeeName", request.getRawRequestUri());
+          assertEquals("http://localhost/odata", request.getRawBaseUri());
+          assertEquals("/Employees('2')/EmployeeName", request.getRawODataPath());
+          assertEquals("", request.getRawQueryPath()); // No query parameter
+        }
+      }
+    }
+  }
+
+  @Test
+  public void testImageInContent() throws IOException, BatchException, URISyntaxException {
+    final InputStream contentInputStream = readFile("/batchWithContent.batch");
+    final String content = StringUtil.toString(contentInputStream);
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + MIME_HEADERS
+        + CRLF
+        + "GET Employees?$filter=Age%20gt%2040 HTTP/1.1" + CRLF
+        + "Accept: application/atomsvc+xml;q=0.8, application/json;odata=verbose;q=0.5, */*;q=0.1" + CRLF
+        + "MaxDataServiceVersion: 2.0" + CRLF
+        + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56" + CRLF
+        + "Content-Type: multipart/mixed; boundary=changeset_f980-1cb6-94dd" + CRLF
+        + CRLF
+        + "--changeset_f980-1cb6-94dd" + CRLF
+        + "content-type:     Application/http" + CRLF
+        + "content-transfer-encoding: Binary" + CRLF
+        + "Content-ID: 1" + CRLF
+        + CRLF
+        + "POST Employees HTTP/1.1" + CRLF
+        + "Content-length: 100000" + CRLF
+        + "Content-type: application/octet-stream" + CRLF
+        + CRLF
+        + content
+        + CRLF
+        + "--changeset_f980-1cb6-94dd--" + CRLF
+        + "--batch_8194-cf13-1f56--";
+    final List<BatchRequestPart> BatchRequestParts = parse(batch);
+
+    for (BatchRequestPart part : BatchRequestParts) {
+      if (!part.isChangeSet()) {
+        assertEquals(1, part.getRequests().size());
+        final ODataRequest retrieveRequest = part.getRequests().get(0);
+
+        assertEquals(HttpMethod.GET, retrieveRequest.getMethod());
+        assertEquals("http://localhost/odata/Employees?$filter=Age%20gt%2040", retrieveRequest.getRawRequestUri());
+        assertEquals("http://localhost/odata", retrieveRequest.getRawBaseUri());
+        assertEquals("/Employees", retrieveRequest.getRawODataPath());
+        assertEquals("$filter=Age%20gt%2040", retrieveRequest.getRawQueryPath());
+      } else {
+        final List<ODataRequest> requests = part.getRequests();
+        for (ODataRequest request : requests) {
+          assertEquals(HttpMethod.POST, request.getMethod());
+          assertEquals("100000", request.getHeader(HttpHeader.CONTENT_LENGTH));
+          assertEquals("1", request.getHeader(BatchParserCommon.HTTP_CONTENT_ID));
+          assertEquals("application/octet-stream", request.getHeader(HttpHeader.CONTENT_TYPE));
+
+          final InputStream body = request.getBody();
+          assertEquals(content, StringUtil.toString(body));
+        }
+      }
+    }
+  }
+
+  @Test
+  public void testPostWithoutBody() throws IOException, BatchException, URISyntaxException {
+    final String batch = CRLF
+        + "--batch_8194-cf13-1f56" + CRLF
+        + "Content-Type: multipart/mixed; boundary=changeset_f980-1cb6-94dd" + CRLF
+        + CRLF
+        + "--changeset_f980-1cb6-94dd" + CRLF
+        + MIME_HEADERS
+        + "Content-ID: changeRequest1" + CRLF
+        + CRLF
+        + "POST Employees('2') HTTP/1.1" + CRLF
+        + "Content-Length: 100" + CRLF
+        + "Content-Type: application/octet-stream" + CRLF
+        + CRLF
+        + CRLF
+        + "--changeset_f980-1cb6-94dd--" + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+    final List<BatchRequestPart> batchRequestParts = parse(batch);
+
+    for (BatchRequestPart object : batchRequestParts) {
+      if (object.isChangeSet()) {
+        final List<ODataRequest> requests = object.getRequests();
+
+        for (ODataRequest request : requests) {
+          assertEquals(HttpMethod.POST, request.getMethod());
+          assertEquals("100", request.getHeader(HttpHeader.CONTENT_LENGTH));
+          assertEquals("application/octet-stream", request.getHeader(HttpHeader.CONTENT_TYPE));
+          assertNotNull(request.getBody());
+        }
+      }
+    }
+  }
+
+  @Test
+  public void testBoundaryParameterWithQuotas() throws BatchException, UnsupportedEncodingException {
+    final String contentType = "multipart/mixed; boundary=\"batch_1.2+34:2j)0?\"";
+    final String batch = ""
+        + "--batch_1.2+34:2j)0?" + CRLF
+        + GET_REQUEST
+        + "--batch_1.2+34:2j)0?--";
+    final BatchParser parser = new BatchParser(contentType, SERVICE_ROOT, "", true);
+    final List<BatchRequestPart> batchRequestParts = parser.parseBatchRequest(StringUtil.toInputStream(batch));
+
+    assertNotNull(batchRequestParts);
+    assertFalse(batchRequestParts.isEmpty());
+  }
+
+  @Test
+  public void testBatchWithInvalidContentType() throws UnsupportedEncodingException {
+    final String invalidContentType = "multipart;boundary=batch_1740-bb84-2f7f";
+    final String batch = ""
+        + "--batch_1740-bb84-2f7f" + CRLF
+        + GET_REQUEST
+        + "--batch_1740-bb84-2f7f--";
+    final BatchParser parser = new BatchParser(invalidContentType, SERVICE_ROOT, "", true);
+
+    try {
+      parser.parseBatchRequest(StringUtil.toInputStream(batch));
+      fail();
+    } catch (BatchException e) {
+      assertMessageKey(e, BatchException.MessageKeys.INVALID_CONTENT_TYPE);
+    }
+  }
+
+  @Test
+  public void testContentTypeCharset() throws BatchException {
+    final String contentType = "multipart/mixed; charset=UTF-8;boundary=batch_14d1-b293-b99a";
+    final String batch = ""
+                    + "--batch_14d1-b293-b99a" + CRLF
+                    + GET_REQUEST
+                    + "--batch_14d1-b293-b99a--";
+    final BatchParser parser = new BatchParser(contentType, SERVICE_ROOT, "", true);
+    final List<BatchRequestPart> parts = parser.parseBatchRequest(StringUtil.toInputStream(batch));
+    
+    assertEquals(1, parts.size());
+  }
+  
+  @Test
+  public void testBatchWithoutBoundaryParameter() throws UnsupportedEncodingException {
+    final String invalidContentType = "multipart/mixed";
+    final String batch = ""
+        + "--batch_1740-bb84-2f7f" + CRLF
+        + GET_REQUEST
+        + "--batch_1740-bb84-2f7f--";
+    final BatchParser parser = new BatchParser(invalidContentType, SERVICE_ROOT, "", true);
+
+    try {
+      parser.parseBatchRequest(StringUtil.toInputStream(batch));
+      fail();
+    } catch (BatchException e) {
+      assertMessageKey(e, BatchException.MessageKeys.INVALID_CONTENT_TYPE);
+    }
+  }
+
+  @Test
+  public void testBoundaryParameterWithoutQuota() throws UnsupportedEncodingException {
+    final String invalidContentType = "multipart/mixed;boundary=batch_1740-bb:84-2f7f";
+    final String batch = ""
+        + "--batch_1740-bb:84-2f7f" + CRLF
+        + GET_REQUEST
+        + "--batch_1740-bb:84-2f7f--";
+    final BatchParser parser = new BatchParser(invalidContentType, SERVICE_ROOT, "", true);
+
+    try {
+      parser.parseBatchRequest(StringUtil.toInputStream(batch));
+      fail();
+    } catch (BatchException e) {
+      assertMessageKey(e, BatchException.MessageKeys.INVALID_BOUNDARY);
+    }
+  }
+
+  @Test
+  public void testWrongBoundaryString() throws BatchException, UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f5" + CRLF
+        + GET_REQUEST
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, BatchException.MessageKeys.MISSING_BOUNDARY_DELIMITER);
+  }
+
+  @Test
+  public void testMissingHttpVersion() throws UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + "Content-Type: application/http" + CRLF
+        + "Content-Transfer-Encoding:binary" + CRLF
+        + CRLF
+        + "GET Employees?$format=json" + CRLF
+        + "Host: localhost:8080" + CRLF
+        + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, BatchException.MessageKeys.INVALID_STATUS_LINE);
+  }
+
+  @Test
+  public void testMissingHttpVersion2() throws UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + "Content-Type: application/http" + CRLF
+        + "Content-Transfer-Encoding:binary" + CRLF
+        + CRLF
+        + "GET Employees?$format=json " + CRLF
+        + "Host: localhost:8080" + CRLF
+        + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, BatchException.MessageKeys.INVALID_HTTP_VERSION);
+  }
+
+  @Test
+  public void testMissingHttpVersion3() throws UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + "Content-Type: application/http" + CRLF
+        + "Content-Transfer-Encoding:binary" + CRLF
+        + CRLF
+        + "GET Employees?$format=json SMTP:3.1" + CRLF
+        + "Host: localhost:8080" + CRLF
+        + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, BatchException.MessageKeys.INVALID_HTTP_VERSION);
+  }
+
+  @Test
+  public void testBoundaryWithoutHyphen() throws UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + GET_REQUEST
+        + "batch_8194-cf13-1f56" + CRLF
+        + GET_REQUEST
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, BatchException.MessageKeys.INVALID_CONTENT);
+  }
+
+  @Test
+  public void testNoBoundaryString() throws UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + GET_REQUEST
+        // + no boundary string
+        + GET_REQUEST
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, BatchException.MessageKeys.INVALID_CONTENT);
+  }
+
+  @Test
+  public void testBatchBoundaryEqualsChangeSetBoundary() throws UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + "Content-Type: multipart/mixed;boundary=batch_8194-cf13-1f56" + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56" + CRLF
+        + MIME_HEADERS
+        + CRLF
+        + "PUT Employees('2')/EmployeeName HTTP/1.1" + CRLF
+        + "Accept: application/atomsvc+xml;q=0.8, application/json;odata=verbose;q=0.5, */*;q=0.1" + CRLF
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + "MaxDataServiceVersion: 2.0" + CRLF
+        + CRLF
+        + "{\"EmployeeName\":\"Frederic Fall MODIFIED\"}" + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--"
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, BatchException.MessageKeys.MISSING_BLANK_LINE);
+  }
+
+  @Test
+  public void testNoContentType() throws UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + "Content-Transfer-Encoding: binary" + CRLF
+        + CRLF
+        + "GET Employees('1')/EmployeeName HTTP/1.1" + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, BatchException.MessageKeys.MISSING_CONTENT_TYPE);
+  }
+
+  @Test
+  public void testMimeHeaderContentType() throws UnsupportedEncodingException {
+    final String batch = "--batch_8194-cf13-1f56" + CRLF
+        + "Content-Type: text/plain" + CRLF
+        + "Content-Transfer-Encoding: binary" + CRLF
+        + CRLF
+        + "GET Employees('1')/EmployeeName HTTP/1.1" + CRLF
+        + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, BatchException.MessageKeys.INVALID_CONTENT_TYPE);
+  }
+
+  @Test
+  public void testMimeHeaderEncoding() throws UnsupportedEncodingException {
+    String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + "Content-Type: application/http" + CRLF
+        + "Content-Transfer-Encoding: 8bit" + CRLF
+        + CRLF
+        + "GET Employees('1')/EmployeeName HTTP/1.1" + CRLF
+        + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, BatchException.MessageKeys.INVALID_CONTENT_TRANSFER_ENCODING);
+  }
+
+  @Test
+  public void testGetRequestMissingCRLF() throws UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + MIME_HEADERS
+        + "Content-ID: 1" + CRLF
+        + CRLF
+        + "GET Employees('1')/EmployeeName HTTP/1.1" + CRLF
+        // + CRLF // Belongs to the GET request
+        + CRLF // Belongs to the
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, BatchException.MessageKeys.MISSING_BLANK_LINE);
+  }
+
+  @Test
+  public void testInvalidMethodForBatch() throws UnsupportedEncodingException {
+    final String batch = "--batch_8194-cf13-1f56" + CRLF
+        + MIME_HEADERS
+        + CRLF
+        + "POST Employees('1')/EmployeeName HTTP/1.1" + CRLF
+        + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, BatchException.MessageKeys.INVALID_QUERY_OPERATION_METHOD);
+  }
+
+  @Test
+  public void testNoBoundaryFound() throws UnsupportedEncodingException {
+    final String batch = "batch_8194-cf13-1f56" + CRLF
+        + MIME_HEADERS
+        + CRLF
+        + "POST Employees('1')/EmployeeName HTTP/1.1" + CRLF
+        + CRLF;
+
+    parseInvalidBatchBody(batch, BatchException.MessageKeys.MISSING_BOUNDARY_DELIMITER);
+  }
+
+  @Test
+  public void testBadRequest() throws UnsupportedEncodingException {
+    final String batch = "This is a bad request. There is no syntax and also no semantic";
+
+    parseInvalidBatchBody(batch, BatchException.MessageKeys.MISSING_BOUNDARY_DELIMITER);
+  }
+
+  @Test
+  public void testNoMethod() throws UnsupportedEncodingException {
+    final String batch = "--batch_8194-cf13-1f56" + CRLF
+        + MIME_HEADERS
+        + CRLF
+        + /* GET */"Employees('1')/EmployeeName HTTP/1.1" + CRLF
+        + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, BatchException.MessageKeys.INVALID_STATUS_LINE);
+  }
+
+  @Test
+  public void testInvalidMethodForChangeset() throws UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + "Content-Type: multipart/mixed; boundary=changeset_f980-1cb6-94dd" + CRLF
+        + CRLF
+        + "--changeset_f980-1cb6-94dd" + CRLF
+        + MIME_HEADERS
+        + "Content-Id: 1" + CRLF
+        + CRLF
+        + "GET Employees('2')/EmployeeName HTTP/1.1" + CRLF
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + "MaxDataServiceVersion: 2.0" + CRLF
+        + CRLF
+        + CRLF
+        + "--changeset_f980-1cb6-94dd--"
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, BatchException.MessageKeys.INVALID_CHANGESET_METHOD);
+  }
+
+  @Test
+  public void testInvalidChangeSetBoundary() throws UnsupportedEncodingException {
+    final String batch = "--batch_8194-cf13-1f56" + CRLF
+        + "Content-Type: multipart/mixed;boundary=changeset_f980-1cb6-94dd" + CRLF
+        + CRLF
+        + "--changeset_f980-1cb6-94d"/* +"d" */+ CRLF
+        + MIME_HEADERS
+        + CRLF
+        + "POST Employees('2') HTTP/1.1" + CRLF
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + "MaxDataServiceVersion: 2.0" + CRLF
+        + CRLF
+        + "--changeset_f980-1cb6-94dd--" + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, BatchException.MessageKeys.MISSING_BOUNDARY_DELIMITER);
+  }
+
+  @Test
+  public void testNestedChangeset() throws UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + "Content-Type: multipart/mixed;boundary=changeset_f980-1cb6-94dd" + CRLF
+        + CRLF
+        + "--changeset_f980-1cb6-94dd" + CRLF
+        + "Content-Transfer-Encoding: binary" + CRLF
+        + "Content-Type: multipart/mixed;boundary=changeset_f980-1cb6-94dd2" + CRLF
+        + CRLF
+        + "--changeset_f980-1cb6-94dd2" + CRLF
+        + MIME_HEADERS
+        + "Content-Id: 1" + CRLF
+        + CRLF
+        + "POST Employees('2') HTTP/1.1" + CRLF
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + "MaxDataServiceVersion: 2.0" + CRLF
+        + "Content-Id: 2"
+        + CRLF
+        + "--changeset_f980-1cb6-94dd--" + CRLF
+        + CRLF
+        + "--changeset_f980-1cb6-94dd--" + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, BatchException.MessageKeys.INVALID_CONTENT_TYPE);
+  }
+
+  @Test
+  public void testMissingContentTransferEncoding() throws UnsupportedEncodingException {
+    final String batch = "--batch_8194-cf13-1f56" + CRLF
+        + "Content-Type: multipart/mixed;boundary=changeset_f980-1cb6-94dd" + CRLF
+        + CRLF
+        + "--changeset_f980-1cb6-94dd" + CRLF
+        + "Content-Id: 1" + CRLF
+        + "Content-Type: application/http" + CRLF
+        // + "Content-Transfer-Encoding: binary" + CRLF
+        + CRLF
+        + "POST Employees('2') HTTP/1.1" + CRLF
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + "MaxDataServiceVersion: 2.0" + CRLF
+        + CRLF
+        + CRLF
+        + "--changeset_f980-1cb6-94dd--" + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, BatchException.MessageKeys.MISSING_CONTENT_TRANSFER_ENCODING);
+  }
+
+  @Test
+  public void testMissingContentType() throws UnsupportedEncodingException {
+    final String batch = "--batch_8194-cf13-1f56" + CRLF
+        + "Content-Type: multipart/mixed;boundary=changeset_f980-1cb6-94dd" + CRLF
+        + CRLF
+        + "--changeset_f980-1cb6-94dd" + CRLF
+        + "Content-Id: 1"
+        // + "Content-Type: application/http" + CRLF
+        + "Content-Transfer-Encoding: binary" + CRLF
+        + CRLF
+        + "POST Employees('2') HTTP/1.1" + CRLF
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + "MaxDataServiceVersion: 2.0" + CRLF
+        + CRLF
+        + CRLF
+        + "--changeset_f980-1cb6-94dd--" + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, BatchException.MessageKeys.MISSING_CONTENT_TYPE);
+  }
+
+  @Test
+  public void testNoCloseDelimiter() throws BatchException, UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + GET_REQUEST;
+
+    parseInvalidBatchBody(batch, BatchException.MessageKeys.MISSING_CLOSE_DELIMITER);
+  }
+
+  @Test
+  public void testNoCloseDelimiter2() throws BatchException, UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + MIME_HEADERS
+        + CRLF
+        + "GET Employees('1')/EmployeeName HTTP/1.1" + CRLF;
+
+    parseInvalidBatchBody(batch, BatchException.MessageKeys.MISSING_CLOSE_DELIMITER);
+  }
+
+  @Test
+  public void testInvalidUri() throws UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + MIME_HEADERS
+        + CRLF
+        + "GET http://localhost/aa/odata/Employees('1')/EmployeeName HTTP/1.1" + CRLF
+        + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, BatchException.MessageKeys.INVALID_URI);
+  }
+
+  @Test
+  public void testUriWithAbsolutePath() throws BatchException, UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + MIME_HEADERS
+        + CRLF
+        + "GET /odata/Employees('1')/EmployeeName HTTP/1.1" + CRLF
+        + "Host: http://localhost" + CRLF
+        + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    final List<BatchRequestPart> parts = parse(batch);
+    assertEquals(1, parts.size());
+
+    final BatchRequestPart part = parts.get(0);
+    assertEquals(1, part.getRequests().size());
+    final ODataRequest request = part.getRequests().get(0);
+
+    assertEquals("http://localhost/odata/Employees('1')/EmployeeName", request.getRawRequestUri());
+    assertEquals("http://localhost/odata", request.getRawBaseUri());
+    assertEquals("/Employees('1')/EmployeeName", request.getRawODataPath());
+    assertEquals("", request.getRawQueryPath());
+  }
+
+  @Test
+  public void testUriWithAbsolutePathMissingHostHeader() throws BatchException, UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + MIME_HEADERS
+        + CRLF
+        + "GET /odata/Employees('1')/EmployeeName HTTP/1.1" + CRLF
+        + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, MessageKeys.MISSING_MANDATORY_HEADER);
+  }
+
+  @Test
+  public void testUriWithAbsolutePathMissingHostDulpicatedHeader() throws BatchException, UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + MIME_HEADERS
+        + CRLF
+        + "GET /odata/Employees('1')/EmployeeName HTTP/1.1" + CRLF
+        + "Host: http://localhost" + CRLF
+        + "Host: http://localhost/odata" + CRLF
+        + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, MessageKeys.MISSING_MANDATORY_HEADER);
+  }
+
+  @Test
+  public void testUriWithAbsolutePathOtherHost() throws BatchException, UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + MIME_HEADERS
+        + CRLF
+        + "GET /odata/Employees('1')/EmployeeName HTTP/1.1" + CRLF
+        + "Host: http://localhost2" + CRLF
+        + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, MessageKeys.INVALID_URI);
+  }
+
+  @Test
+  public void testUriWithAbsolutePathWrongPath() throws BatchException, UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + MIME_HEADERS
+        + CRLF
+        + "GET /myservice/Employees('1')/EmployeeName HTTP/1.1" + CRLF
+        + "Host: http://localhost" + CRLF
+        + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, MessageKeys.INVALID_URI);
+  }
+
+  @Test
+  public void testNoCloseDelimiter3() throws UnsupportedEncodingException {
+    final String batch = "--batch_8194-cf13-1f56" + CRLF + GET_REQUEST + "--batch_8194-cf13-1f56-"/* no hyphen */;
+
+    parseInvalidBatchBody(batch, BatchException.MessageKeys.MISSING_CLOSE_DELIMITER);
+  }
+
+  @Test
+  public void testNegativeContentLengthChangeSet() throws BatchException, IOException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + "Content-Type: multipart/mixed; boundary=changeset_f980-1cb6-94dd" + CRLF
+        + CRLF
+        + "--changeset_f980-1cb6-94dd" + CRLF
+        + MIME_HEADERS
+        + "Content-ID: 1" + CRLF
+        + "Content-Length: -2" + CRLF
+        + CRLF
+        + "PUT EmployeeName HTTP/1.1" + CRLF
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + "Content-Id: 1" + CRLF
+        + CRLF
+        + "{\"EmployeeName\":\"Peter Fall\"}" + CRLF
+        + "--changeset_f980-1cb6-94dd--" + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parse(batch);
+  }
+
+  @Test
+  public void testNegativeContentLengthRequest() throws BatchException, IOException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + "Content-Type: multipart/mixed; boundary=changeset_f980-1cb6-94dd" + CRLF
+        + CRLF
+        + "--changeset_f980-1cb6-94dd" + CRLF
+        + MIME_HEADERS
+        + "Content-ID: 1" + CRLF
+        + CRLF
+        + "PUT EmployeeName HTTP/1.1" + CRLF
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + "Content-Id: 1" + CRLF
+        + "Content-Length: 2" + CRLF
+        + CRLF
+        + "{\"EmployeeName\":\"Peter Fall\"}" + CRLF
+        + "--changeset_f980-1cb6-94dd--" + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parse(batch);
+  }
+
+  @Test
+  public void testContentLengthGreatherThanBodyLength() throws BatchException, IOException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + "Content-Type: multipart/mixed; boundary=changeset_f980-1cb6-94dd" + CRLF
+        + CRLF
+        + "--changeset_f980-1cb6-94dd" + CRLF
+        + MIME_HEADERS
+        + "Content-ID: 1" + CRLF
+        + CRLF
+        + "PUT Employee/Name HTTP/1.1" + CRLF
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + "Content-Length: 100000" + CRLF
+        + CRLF
+        + "{\"EmployeeName\":\"Peter Fall\"}" + CRLF
+        + "--changeset_f980-1cb6-94dd--" + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+    final List<BatchRequestPart> batchRequestParts = parse(batch);
+
+    assertNotNull(batchRequestParts);
+
+    for (BatchRequestPart multipart : batchRequestParts) {
+      if (multipart.isChangeSet()) {
+        assertEquals(1, multipart.getRequests().size());
+
+        final ODataRequest request = multipart.getRequests().get(0);
+        assertEquals("{\"EmployeeName\":\"Peter Fall\"}", StringUtil.toString(request.getBody()));
+      }
+    }
+  }
+
+  @Test
+  public void testContentLengthSmallerThanBodyLength() throws BatchException, IOException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + "Content-Type: multipart/mixed; boundary=changeset_f980-1cb6-94dd" + CRLF
+        + CRLF
+        + "--changeset_f980-1cb6-94dd" + CRLF
+        + MIME_HEADERS
+        + "Content-ID: 1" + CRLF
+        + CRLF
+        + "PUT EmployeeName HTTP/1.1" + CRLF
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + "Content-Length: 10" + CRLF
+        + CRLF
+        + "{\"EmployeeName\":\"Peter Fall\"}" + CRLF
+        + "--changeset_f980-1cb6-94dd--" + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+    final List<BatchRequestPart> batchRequestParts = parse(batch);
+
+    assertNotNull(batchRequestParts);
+
+    for (BatchRequestPart multipart : batchRequestParts) {
+      if (multipart.isChangeSet()) {
+        assertEquals(1, multipart.getRequests().size());
+
+        final ODataRequest request = multipart.getRequests().get(0);
+        assertEquals("{\"Employee", StringUtil.toString(request.getBody()));
+      }
+    }
+  }
+
+  @Test
+  public void testNonNumericContentLength() throws UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + "Content-Type: multipart/mixed; boundary=changeset_f980-1cb6-94dd" + CRLF
+        + CRLF
+        + "--changeset_f980-1cb6-94dd" + CRLF
+        + MIME_HEADERS
+        + "Content-ID: 1" + CRLF
+        + CRLF
+        + "PUT EmployeeName HTTP/1.1" + CRLF
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + "Content-Length: 10abc" + CRLF
+        + CRLF
+        + "{\"EmployeeName\":\"Peter Fall\"}" + CRLF
+        + "--changeset_f980-1cb6-94dd--" + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, BatchException.MessageKeys.INVALID_HEADER);
+  }
+
+  @Test
+  public void testNonStrictParser() throws BatchException, IOException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + "Content-Type: multipart/mixed;boundary=changeset_8194-cf13-1f56" + CRLF
+        + "--changeset_8194-cf13-1f56" + CRLF
+        + MIME_HEADERS
+        + "Content-ID: myRequest" + CRLF
+        + "PUT Employees('2')/EmployeeName HTTP/1.1" + CRLF
+        + "Accept: application/atomsvc+xml;q=0.8, application/json;odata=verbose;q=0.5, */*;q=0.1" + CRLF
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + "MaxDataServiceVersion: 2.0" + CRLF
+        + "{\"EmployeeName\":\"Frederic Fall MODIFIED\"}" + CRLF
+        + "--changeset_8194-cf13-1f56--" + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    final List<BatchRequestPart> requests = parse(batch, false);
+
+    assertNotNull(requests);
+    assertEquals(1, requests.size());
+
+    final BatchRequestPart part = requests.get(0);
+    assertTrue(part.isChangeSet());
+    assertNotNull(part.getRequests());
+    assertEquals(1, part.getRequests().size());
+
+    final ODataRequest changeRequest = part.getRequests().get(0);
+    assertEquals("{\"EmployeeName\":\"Frederic Fall MODIFIED\"}",
+        StringUtil.toString(changeRequest.getBody()));
+    assertEquals("application/json;odata=verbose", changeRequest.getHeader(HttpHeader.CONTENT_TYPE));
+    assertEquals(HttpMethod.PUT, changeRequest.getMethod());
+  }
+
+  @Test
+  public void testNonStrictParserMoreCRLF() throws UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + "Content-Type: multipart/mixed;boundary=changeset_8194-cf13-1f56" + CRLF
+        + "--changeset_8194-cf13-1f56" + CRLF
+        + MIME_HEADERS
+        + CRLF
+        + CRLF // Only one CRLF allowed
+        + "PUT Employees('2')/EmployeeName HTTP/1.1" + CRLF
+        + "Accept: application/atomsvc+xml;q=0.8, application/json;odata=verbose;q=0.5, */*;q=0.1" + CRLF
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + "MaxDataServiceVersion: 2.0" + CRLF
+        + "{\"EmployeeName\":\"Frederic Fall MODIFIED\"}" + CRLF
+        + "--changeset_8194-cf13-1f56--" + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, BatchException.MessageKeys.INVALID_STATUS_LINE, false);
+  }
+
+  @Test
+  public void testContentId() throws BatchException, UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + MIME_HEADERS
+        + CRLF
+        + "GET Employees HTTP/1.1" + CRLF
+        + "accept: */*,application/atom+xml,application/atomsvc+xml,application/xml" + CRLF
+        + "Content-Id: BBB" + CRLF
+        + CRLF + CRLF
+        + "--batch_8194-cf13-1f56" + CRLF
+        + "Content-Type: multipart/mixed; boundary=changeset_f980-1cb6-94dd" + CRLF
+        + CRLF
+        + "--changeset_f980-1cb6-94dd" + CRLF
+        + MIME_HEADERS
+        + "Content-Id: 1" + CRLF
+        + CRLF
+        + "POST Employees HTTP/1.1" + CRLF
+        + "Content-type: application/octet-stream" + CRLF
+        + CRLF
+        + "/9j/4AAQSkZJRgABAQEBLAEsAAD/4RM0RXhpZgAATU0AKgAAAAgABwESAAMAAAABAAEAAAEaAAUAAAABAAAAYgEbAAUAAAA" + CRLF
+        + CRLF
+        + "--changeset_f980-1cb6-94dd" + CRLF
+        + MIME_HEADERS
+        + CRLF
+        + "PUT $1/EmployeeName HTTP/1.1" + CRLF
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + "Content-Id: 2" + CRLF
+        + CRLF
+        + "{\"EmployeeName\":\"Peter Fall\"}" + CRLF
+        + "--changeset_f980-1cb6-94dd--" + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    final List<BatchRequestPart> batchRequestParts = parse(batch);
+    assertNotNull(batchRequestParts);
+
+    for (BatchRequestPart multipart : batchRequestParts) {
+      if (!multipart.isChangeSet()) {
+        assertEquals(1, multipart.getRequests().size());
+        final ODataRequest retrieveRequest = multipart.getRequests().get(0);
+
+        assertEquals("BBB", retrieveRequest.getHeader(BatchParserCommon.HTTP_CONTENT_ID));
+      } else {
+        for (ODataRequest request : multipart.getRequests()) {
+          if (HttpMethod.POST.equals(request.getMethod())) {
+            assertEquals("1", request.getHeader(BatchParserCommon.HTTP_CONTENT_ID));
+          } else if (HttpMethod.PUT.equals(request.getMethod())) {
+            assertEquals("2", request.getHeader(BatchParserCommon.HTTP_CONTENT_ID));
+            assertEquals("/$1/EmployeeName", request.getRawODataPath());
+            assertEquals("http://localhost/odata/$1/EmployeeName", request.getRawRequestUri());
+          }
+        }
+      }
+    }
+  }
+
+  @Test
+  public void testNoContentId() throws BatchException, UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + MIME_HEADERS
+        + CRLF
+        + "GET Employees HTTP/1.1" + CRLF
+        + "accept: */*,application/atom+xml,application/atomsvc+xml,application/xml" + CRLF
+        + CRLF + CRLF
+        + "--batch_8194-cf13-1f56" + CRLF
+        + "Content-Type: multipart/mixed; boundary=changeset_f980-1cb6-94dd" + CRLF
+        + CRLF
+        + "--changeset_f980-1cb6-94dd" + CRLF
+        + MIME_HEADERS
+        + "Content-Id: 1" + CRLF
+        + CRLF
+        + "POST Employees HTTP/1.1" + CRLF
+        + "Content-type: application/octet-stream" + CRLF
+        + CRLF
+        + "/9j/4AAQSkZJRgABAQEBLAEsAAD/4RM0RXhpZgAATU0AKgAAAAgABwESAAMAAAABAAEAAAEaAAUAAAABAAAAYgEbAAUAAAA" + CRLF
+        + CRLF
+        + "--changeset_f980-1cb6-94dd" + CRLF
+        + MIME_HEADERS
+        + "Content-Id: 1" + CRLF
+        + CRLF
+        + "PUT $1/EmployeeName HTTP/1.1" + CRLF
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + CRLF
+        + "{\"EmployeeName\":\"Peter Fall\"}" + CRLF
+        + "--changeset_f980-1cb6-94dd--" + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parse(batch);
+  }
+
+  @Test
+  public void testPreamble() throws BatchException, IOException {
+    final String batch = ""
+        + "This is a preamble and must be ignored" + CRLF
+        + CRLF
+        + CRLF
+        + "----1242" + CRLF
+        + "--batch_8194-cf13-1f56" + CRLF
+        + MIME_HEADERS
+        + CRLF
+        + "GET Employees HTTP/1.1" + CRLF
+        + "accept: */*,application/atom+xml,application/atomsvc+xml,application/xml" + CRLF
+        + "Content-Id: BBB" + CRLF
+        + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56" + CRLF
+        + "Content-Type: multipart/mixed; boundary=changeset_f980-1cb6-94dd" + CRLF
+        + CRLF
+        + "This is a preamble and must be ignored" + CRLF
+        + CRLF
+        + CRLF
+        + "----1242" + CRLF
+        + "--changeset_f980-1cb6-94dd" + CRLF
+        + MIME_HEADERS
+        + "Content-Id: 1" + CRLF
+        + CRLF
+        + "POST Employees HTTP/1.1" + CRLF
+        + "Content-type: application/octet-stream" + CRLF
+        + CRLF
+        + "/9j/4AAQSkZJRgABAQEBLAEsAAD/4RM0RXhpZgAATU0AKgAAAAgABwESAAMAAAABAAEAAAEaAAUAAAABAAAAYgEbAAUAAAA" + CRLF
+        + CRLF
+        + "--changeset_f980-1cb6-94dd" + CRLF
+        + MIME_HEADERS
+        + "Content-ID: 2" + CRLF
+        + CRLF
+        + "PUT $1/EmployeeName HTTP/1.1" + CRLF
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + CRLF
+        + "{\"EmployeeName\":\"Peter Fall\"}" + CRLF
+        + "--changeset_f980-1cb6-94dd--" + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+    final List<BatchRequestPart> batchRequestParts = parse(batch);
+
+    assertNotNull(batchRequestParts);
+    assertEquals(2, batchRequestParts.size());
+
+    final BatchRequestPart getRequestPart = batchRequestParts.get(0);
+    assertEquals(1, getRequestPart.getRequests().size());
+
+    final ODataRequest getRequest = getRequestPart.getRequests().get(0);
+    assertEquals(HttpMethod.GET, getRequest.getMethod());
+
+    final BatchRequestPart changeSetPart = batchRequestParts.get(1);
+    assertEquals(2, changeSetPart.getRequests().size());
+    assertEquals("/9j/4AAQSkZJRgABAQEBLAEsAAD/4RM0RXhpZgAATU0AKgAAAAgABwESAAMAAAABAAEAAAEaAAUAAAABAAAAYgEbAAUAAAA"
+        + CRLF,
+        StringUtil.toString(changeSetPart.getRequests().get(0).getBody()));
+    assertEquals("{\"EmployeeName\":\"Peter Fall\"}",
+        StringUtil.toString(changeSetPart.getRequests().get(1).getBody()));
+  }
+
+  @Test
+  public void testContentTypeCaseInsensitive() throws BatchException, IOException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + "Content-Type: muLTiParT/mixed; boundary=changeset_f980-1cb6-94dd" + CRLF
+        + CRLF
+        + "--changeset_f980-1cb6-94dd" + CRLF
+        + MIME_HEADERS
+        + "Content-ID: 1" + CRLF
+        + "Content-Length: 200" + CRLF
+        + CRLF
+        + "PUT EmployeeName HTTP/1.1" + CRLF
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + CRLF
+        + "{\"EmployeeName\":\"Peter Fall\"}" + CRLF
+        + "--changeset_f980-1cb6-94dd--" + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parse(batch);
+  }
+
+  @Test
+  public void testContentTypeBoundaryCaseInsensitive() throws BatchException, IOException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + "Content-Type: multipart/mixed; bOunDaRy=changeset_f980-1cb6-94dd" + CRLF
+        + CRLF
+        + "--changeset_f980-1cb6-94dd" + CRLF
+        + MIME_HEADERS
+        + "Content-ID: 1" + CRLF
+        + CRLF
+        + "PUT EmployeeName HTTP/1.1" + CRLF
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + CRLF
+        + "{\"EmployeeName\":\"Peter Fall\"}" + CRLF
+        + "--changeset_f980-1cb6-94dd--" + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+    final List<BatchRequestPart> batchRequestParts = parse(batch);
+
+    assertNotNull(batchRequestParts);
+    assertEquals(1, batchRequestParts.size());
+    assertTrue(batchRequestParts.get(0).isChangeSet());
+    assertEquals(1, batchRequestParts.get(0).getRequests().size());
+  }
+
+  @Test
+  public void testEpilog() throws BatchException, IOException {
+    String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + MIME_HEADERS
+        + CRLF
+        + "GET Employees HTTP/1.1" + CRLF
+        + "accept: */*,application/atom+xml,application/atomsvc+xml,application/xml" + CRLF
+        + "Content-Id: BBB" + CRLF
+        + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56" + CRLF
+        + "Content-Type: multipart/mixed; boundary=changeset_f980-1cb6-94dd" + CRLF
+        + CRLF
+        + "--changeset_f980-1cb6-94dd" + CRLF
+        + MIME_HEADERS
+        + "Content-Id: 1" + CRLF
+        + CRLF
+        + "POST Employees HTTP/1.1" + CRLF
+        + "Content-type: application/octet-stream" + CRLF
+        + CRLF
+        + "/9j/4AAQSkZJRgABAQEBLAEsAAD/4RM0RXhpZgAATU0AKgAAAAgABwESAAMAAAABAAEAAAEaAAUAAAABAAAAYgEbAAUAAAA" + CRLF
+        + CRLF
+        + "--changeset_f980-1cb6-94dd" + CRLF
+        + MIME_HEADERS
+        + "Content-ID: 2" + CRLF
+        + CRLF
+        + "PUT $1/EmployeeName HTTP/1.1" + CRLF
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + CRLF
+        + "{\"EmployeeName\":\"Peter Fall\"}" + CRLF
+        + "--changeset_f980-1cb6-94dd--" + CRLF
+        + CRLF
+        + "This is an epilog and must be ignored" + CRLF
+        + CRLF
+        + CRLF
+        + "----1242"
+        + CRLF
+        + "--batch_8194-cf13-1f56--"
+        + CRLF
+        + "This is an epilog and must be ignored" + CRLF
+        + CRLF
+        + CRLF
+        + "----1242";
+    final List<BatchRequestPart> batchRequestParts = parse(batch);
+
+    assertNotNull(batchRequestParts);
+    assertEquals(2, batchRequestParts.size());
+
+    BatchRequestPart getRequestPart = batchRequestParts.get(0);
+    assertEquals(1, getRequestPart.getRequests().size());
+    ODataRequest getRequest = getRequestPart.getRequests().get(0);
+    assertEquals(HttpMethod.GET, getRequest.getMethod());
+
+    BatchRequestPart changeSetPart = batchRequestParts.get(1);
+    assertEquals(2, changeSetPart.getRequests().size());
+    assertEquals("/9j/4AAQSkZJRgABAQEBLAEsAAD/4RM0RXhpZgAATU0AKgAAAAgABwESAAMAAAABAAEAAAEaAAUAAAABAAAAYgEbAAUAAAA"
+        + CRLF,
+        StringUtil.toString(changeSetPart.getRequests().get(0).getBody()));
+    assertEquals("{\"EmployeeName\":\"Peter Fall\"}",
+        StringUtil.toString(changeSetPart.getRequests().get(1).getBody()));
+  }
+
+  @Test
+  public void testLargeBatch() throws BatchException, IOException {
+    final InputStream in = readFile("/batchLarge.batch");
+    parse(in);
+  }
+
+  @Test
+  public void testForddenHeaderAuthorisation() throws UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + MIME_HEADERS
+        + CRLF
+        + "GET Employees('1')/EmployeeName HTTP/1.1" + CRLF
+        + "Authorization: Basic QWxhZdsdsddsduIHNlc2FtZQ==" + CRLF
+        + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, MessageKeys.FORBIDDEN_HEADER);
+  }
+
+  @Test
+  public void testForddenHeaderExpect() throws UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + MIME_HEADERS
+        + CRLF
+        + "GET Employees('1')/EmployeeName HTTP/1.1" + CRLF
+        + "Expect: 100-continue" + CRLF
+        + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, MessageKeys.FORBIDDEN_HEADER);
+  }
+
+  @Test
+  public void testForddenHeaderFrom() throws UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + MIME_HEADERS
+        + CRLF
+        + "GET Employees('1')/EmployeeName HTTP/1.1" + CRLF
+        + "From: test@test.com" + CRLF
+        + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, MessageKeys.FORBIDDEN_HEADER);
+  }
+
+  @Test
+  public void testForddenHeaderRange() throws UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + MIME_HEADERS
+        + CRLF
+        + "GET Employees('1')/EmployeeName HTTP/1.1" + CRLF
+        + "Range: 200-256" + CRLF
+        + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, MessageKeys.FORBIDDEN_HEADER);
+  }
+
+  @Test
+  public void testForddenHeaderMaxForwards() throws UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + MIME_HEADERS
+        + CRLF
+        + "GET Employees('1')/EmployeeName HTTP/1.1" + CRLF
+        + "Max-Forwards: 3" + CRLF
+        + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, MessageKeys.FORBIDDEN_HEADER);
+  }
+
+  @Test
+  public void testForddenHeaderTE() throws UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56" + CRLF
+        + MIME_HEADERS
+        + CRLF
+        + "GET Employees('1')/EmployeeName HTTP/1.1" + CRLF
+        + "TE: deflate" + CRLF
+        + CRLF
+        + CRLF
+        + "--batch_8194-cf13-1f56--";
+
+    parseInvalidBatchBody(batch, MessageKeys.FORBIDDEN_HEADER);
+  }
+
+  private List<BatchRequestPart> parse(final InputStream in, final boolean isStrict) throws BatchException {
+    final BatchParser parser = new BatchParser(CONTENT_TYPE, SERVICE_ROOT, "", isStrict);
+    final List<BatchRequestPart> batchRequestParts = parser.parseBatchRequest(in);
+
+    assertNotNull(batchRequestParts);
+    assertFalse(batchRequestParts.isEmpty());
+
+    return batchRequestParts;
+  }
+
+  private List<BatchRequestPart> parse(final InputStream in) throws BatchException {
+    return parse(in, true);
+  }
+
+  private List<BatchRequestPart> parse(final String batch) throws BatchException, UnsupportedEncodingException {
+    return parse(batch, true);
+  }
+
+  private List<BatchRequestPart> parse(final String batch, final boolean isStrict) throws BatchException,
+      UnsupportedEncodingException {
+    return parse(StringUtil.toInputStream(batch), isStrict);
+  }
+
+  private void parseInvalidBatchBody(final String batch, final MessageKeys key, final boolean isStrict)
+      throws UnsupportedEncodingException {
+    final BatchParser parser = new BatchParser(CONTENT_TYPE, SERVICE_ROOT, "", isStrict);
+
+    try {
+      parser.parseBatchRequest(StringUtil.toInputStream(batch));
+      fail("No exception thrown. Expect: " + key.toString());
+    } catch (BatchException e) {
+      assertMessageKey(e, key);
+    }
+  }
+
+  private void parseInvalidBatchBody(final String batch, final MessageKeys key) throws UnsupportedEncodingException {
+    parseInvalidBatchBody(batch, key, true);
+  }
+
+  private void assertMessageKey(final BatchException e, final MessageKeys key) {
+    assertEquals(key, e.getMessageKey());
+  }
+
+  private InputStream readFile(final String fileName) throws IOException {
+    final InputStream in = ClassLoader.class.getResourceAsStream(fileName);
+    if (in == null) {
+      throw new IOException("Requested file '" + fileName + "' was not found.");
+    }
+    return in;
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/32247295/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/parser/BatchParserCommonTest.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/parser/BatchParserCommonTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/parser/BatchParserCommonTest.java
new file mode 100644
index 0000000..6fb0796
--- /dev/null
+++ b/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/parser/BatchParserCommonTest.java
@@ -0,0 +1,230 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.batch.parser;
+
+import static org.junit.Assert.*;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.olingo.commons.api.http.HttpHeader;
+import org.apache.olingo.server.core.batch.BatchException;
+import org.apache.olingo.server.core.batch.parser.BatchParserCommon;
+import org.apache.olingo.server.core.batch.parser.BufferedReaderIncludingLineEndings.Line;
+import org.apache.olingo.server.core.batch.parser.Header;
+import org.junit.Test;
+
+public class BatchParserCommonTest {
+
+  private static final String CRLF = "\r\n";
+
+  @Test
+  public void testMultipleHeader() throws BatchException {
+    String[] messageRaw = new String[] {
+        "Content-Id: 1" + CRLF,
+        "Content-Id: 2" + CRLF,
+        "content-type: Application/http" + CRLF,
+        "content-transfer-encoding: Binary" + CRLF
+      };
+    List<Line> message = toLineList(messageRaw);
+    
+    final Header header = BatchParserCommon.consumeHeaders(message);
+    assertNotNull(header);
+    
+    final List<String> contentIdHeaders = header.getHeaders(BatchParserCommon.HTTP_CONTENT_ID);
+    assertNotNull(contentIdHeaders);
+    assertEquals(2, contentIdHeaders.size());
+    assertEquals("1", contentIdHeaders.get(0));
+    assertEquals("2", contentIdHeaders.get(1));
+  }
+  
+  @Test
+  public void testMultipleHeaderSameValue() throws BatchException {
+    String[] messageRaw = new String[] {
+        "Content-Id: 1" + CRLF,
+        "Content-Id: 1" + CRLF,
+        "content-type: Application/http" + CRLF,
+        "content-transfer-encoding: Binary" + CRLF
+      };
+    List<Line> message = toLineList(messageRaw);
+    
+    final Header header = BatchParserCommon.consumeHeaders(message);
+    assertNotNull(header);
+    
+    final List<String> contentIdHeaders = header.getHeaders(BatchParserCommon.HTTP_CONTENT_ID);
+    assertNotNull(contentIdHeaders);
+    assertEquals(1, contentIdHeaders.size());
+    assertEquals("1", contentIdHeaders.get(0));
+  }
+  
+  @Test
+  public void testHeaderSperatedByComma() throws BatchException {
+    String[] messageRaw = new String[] {
+        "Content-Id: 1" + CRLF,
+        "Upgrade: HTTP/2.0, SHTTP/1.3, IRC/6.9, RTA/x11" + CRLF,
+        "content-type: Application/http" + CRLF,
+        "content-transfer-encoding: Binary" + CRLF
+      };
+    List<Line> message = toLineList(messageRaw);
+    
+    final Header header = BatchParserCommon.consumeHeaders(message);
+    assertNotNull(header);
+    
+    final List<String> upgradeHeader = header.getHeaders("upgrade");
+    assertNotNull(upgradeHeader);
+    assertEquals(4, upgradeHeader.size());
+    assertEquals("HTTP/2.0", upgradeHeader.get(0));
+    assertEquals("SHTTP/1.3", upgradeHeader.get(1));
+    assertEquals("IRC/6.9", upgradeHeader.get(2));
+    assertEquals("RTA/x11", upgradeHeader.get(3));
+  }
+  
+  @Test
+  public void testMultipleAcceptHeader() throws BatchException {
+    String[] messageRaw = new String[] {
+        "Accept: application/atomsvc+xml;q=0.8, application/json;odata=verbose;q=0.5, */*;q=0.1" + CRLF,
+        "Accept: text/plain;q=0.3" + CRLF,
+        "Accept-Language:en-US,en;q=0.7,en-UK;q=0.9" + CRLF,
+        "content-type: Application/http" + CRLF,
+        "content-transfer-encoding: Binary" + CRLF
+      };
+    List<Line> message = toLineList(messageRaw);
+    
+    final Header header = BatchParserCommon.consumeHeaders(message);
+    assertNotNull(header);
+    
+    final List<String> acceptHeader = header.getHeaders(HttpHeader.ACCEPT);
+    assertNotNull(acceptHeader);
+    assertEquals(4, acceptHeader.size());
+  }
+  
+  @Test
+  public void testMultipleAcceptHeaderSameValue() throws BatchException {
+    String[] messageRaw = new String[] {
+        "Accept: application/atomsvc+xml;q=0.8, application/json;odata=verbose;q=0.5, */*;q=0.1" + CRLF,
+        "Accept: application/atomsvc+xml;q=0.8" + CRLF,
+        "Accept-Language:en-US,en;q=0.7,en-UK;q=0.9" + CRLF,
+        "content-type: Application/http" + CRLF,
+        "content-transfer-encoding: Binary" + CRLF
+      };
+    List<Line> message = toLineList(messageRaw);
+    
+    final Header header = BatchParserCommon.consumeHeaders(message);
+    assertNotNull(header);
+    
+    final List<String> acceptHeader = header.getHeaders(HttpHeader.ACCEPT);
+    assertNotNull(acceptHeader);
+    assertEquals(3, acceptHeader.size());
+  }
+  
+  @Test
+  public void testMultipleAccepLanguagetHeader() throws BatchException {
+    String[] messageRaw = new String[] {
+        "Accept-Language:en-US,en;q=0.7,en-UK;q=0.9" + CRLF,
+        "Accept-Language: de-DE;q=0.3" + CRLF,
+        "content-type: Application/http" + CRLF,
+        "content-transfer-encoding: Binary" + CRLF
+      };
+    List<Line> message = toLineList(messageRaw);
+    
+    final Header header = BatchParserCommon.consumeHeaders(message);
+    assertNotNull(header);
+    
+    final List<String> acceptLanguageHeader = header.getHeaders(HttpHeader.ACCEPT_LANGUAGE);
+    assertNotNull(acceptLanguageHeader);
+    assertEquals(4, acceptLanguageHeader.size());
+  }
+  
+  @Test
+  public void testMultipleAccepLanguagetHeaderSameValue() throws BatchException {
+    String[] messageRaw = new String[] {
+        "Accept-Language:en-US,en;q=0.7,en-UK;q=0.9" + CRLF,
+        "Accept-Language:en-US,en;q=0.7" + CRLF,
+        "content-type: Application/http" + CRLF,
+        "content-transfer-encoding: Binary" + CRLF
+      };
+    List<Line> message = toLineList(messageRaw);
+    
+    final Header header = BatchParserCommon.consumeHeaders(message);
+    assertNotNull(header);
+    
+    final List<String> acceptLanguageHeader = header.getHeaders(HttpHeader.ACCEPT_LANGUAGE);
+    assertNotNull(acceptLanguageHeader);
+    assertEquals(3, acceptLanguageHeader.size());
+  }
+  
+  @Test
+  public void testRemoveEndingCRLF() {
+    String line = "Test\r\n";
+    assertEquals("Test", BatchParserCommon.removeEndingCRLF(new Line(line,1)).toString());
+  }
+
+  @Test
+  public void testRemoveLastEndingCRLF() {
+    String line = "Test\r\n\r\n";
+    assertEquals("Test\r\n", BatchParserCommon.removeEndingCRLF(new Line(line,1)).toString());
+  }
+
+  @Test
+  public void testRemoveEndingCRLFWithWS() {
+    String line = "Test\r\n            ";
+    assertEquals("Test", BatchParserCommon.removeEndingCRLF(new Line(line,1)).toString());
+  }
+
+  @Test
+  public void testRemoveEndingCRLFNothingToRemove() {
+    String line = "Hallo\r\nBla";
+    assertEquals("Hallo\r\nBla", BatchParserCommon.removeEndingCRLF(new Line(line,1)).toString());
+  }
+
+  @Test
+  public void testRemoveEndingCRLFAll() {
+    String line = "\r\n";
+    assertEquals("", BatchParserCommon.removeEndingCRLF(new Line(line,1)).toString());
+  }
+
+  @Test
+  public void testRemoveEndingCRLFSpace() {
+    String line = "\r\n                      ";
+    assertEquals("", BatchParserCommon.removeEndingCRLF(new Line(line,1)).toString());
+  }
+
+  @Test
+  public void testRemoveLastEndingCRLFWithWS() {
+    String line = "Test            \r\n";
+    assertEquals("Test            ", BatchParserCommon.removeEndingCRLF(new Line(line,1)).toString());
+  }
+
+  @Test
+  public void testRemoveLastEndingCRLFWithWSLong() {
+    String line = "Test            \r\nTest2    \r\n";
+    assertEquals("Test            \r\nTest2    ", BatchParserCommon.removeEndingCRLF(new Line(line,1)).toString());
+  }
+  
+  private List<Line> toLineList(String[] messageRaw) {
+    final List<Line> lineList = new ArrayList<Line>();
+    int counter = 1;
+    
+    for(final String currentLine : messageRaw) {
+      lineList.add(new Line(currentLine, counter++));
+    }
+    
+    return lineList;
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/32247295/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/parser/BufferedReaderIncludingLineEndingsTest.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/parser/BufferedReaderIncludingLineEndingsTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/parser/BufferedReaderIncludingLineEndingsTest.java
new file mode 100644
index 0000000..eac9dff
--- /dev/null
+++ b/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/parser/BufferedReaderIncludingLineEndingsTest.java
@@ -0,0 +1,484 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.batch.parser;
+
+import static org.junit.Assert.*;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+import java.util.List;
+
+import org.apache.olingo.server.core.batch.parser.BufferedReaderIncludingLineEndings;
+import org.apache.olingo.server.core.batch.parser.BufferedReaderIncludingLineEndings.Line;
+import org.junit.Test;
+
+public class BufferedReaderIncludingLineEndingsTest {
+
+
+  private static final String TEXT_COMBINED = "Test\r" +
+      "Test2\r\n" +
+      "Test3\n" +
+      "Test4\r" +
+      "\r" +
+      "\r\n" +
+      "\r\n" +
+      "Test5\n" +
+      "Test6\r\n" +
+      "Test7\n" +
+      "\n";
+
+  private static final String TEXT_SMALL = "Test\r" +
+      "123";
+  private static final String TEXT_EMPTY = "";
+
+  @Test
+  public void testSimpleText() throws IOException {
+    final String TEXT = "Test";
+    BufferedReaderIncludingLineEndings reader = create(TEXT);
+
+    assertEquals(TEXT, reader.readLine());
+    assertNull(reader.readLine());
+    assertNull(reader.readLine());
+    reader.close();
+  }
+
+  @Test
+  public void testNoText() throws IOException {
+    final String TEXT = "";
+    BufferedReaderIncludingLineEndings reader = create(TEXT);
+
+    assertNull(reader.readLine());
+    assertNull(reader.readLine());
+    reader.close();
+  }
+
+  @Test
+  public void testNoBytes() throws IOException {
+    BufferedReaderIncludingLineEndings reader =
+        new BufferedReaderIncludingLineEndings(new InputStreamReader(new ByteArrayInputStream(new byte[0])));
+
+    assertNull(reader.readLine());
+    assertNull(reader.readLine());
+    reader.close();
+  }
+
+  @Test
+  public void testCRLF() throws IOException {
+    final String TEXT = "Test\r\n" +
+        "Test2";
+
+    BufferedReaderIncludingLineEndings reader = create(TEXT);
+
+    assertEquals("Test\r\n", reader.readLine());
+    assertEquals("Test2", reader.readLine());
+    assertNull(reader.readLine());
+    assertNull(reader.readLine());
+    reader.close();
+  }
+
+  @Test
+  public void testLF() throws IOException {
+    final String TEXT = "Test\n" +
+        "Test2";
+
+    BufferedReaderIncludingLineEndings reader = create(TEXT);
+
+    assertEquals("Test\n", reader.readLine());
+    assertEquals("Test2", reader.readLine());
+    assertNull(reader.readLine());
+    assertNull(reader.readLine());
+    reader.close();
+  }
+
+  @Test
+  public void testCR() throws IOException {
+    final String TEXT = "Test\r" +
+        "Test2";
+
+    BufferedReaderIncludingLineEndings reader = create(TEXT);
+
+    assertEquals("Test\r", reader.readLine());
+    assertEquals("Test2", reader.readLine());
+    assertNull(reader.readLine());
+    assertNull(reader.readLine());
+    reader.close();
+  }
+
+  @Test
+  public void testCombined() throws IOException {
+    BufferedReaderIncludingLineEndings reader = create(TEXT_COMBINED);
+
+    assertEquals("Test\r", reader.readLine());
+    assertEquals("Test2\r\n", reader.readLine());
+    assertEquals("Test3\n", reader.readLine());
+    assertEquals("Test4\r", reader.readLine());
+    assertEquals("\r", reader.readLine());
+    assertEquals("\r\n", reader.readLine());
+    assertEquals("\r\n", reader.readLine());
+    assertEquals("Test5\n", reader.readLine());
+    assertEquals("Test6\r\n", reader.readLine());
+    assertEquals("Test7\n", reader.readLine());
+    assertEquals("\n", reader.readLine());
+    assertNull(reader.readLine());
+    assertNull(reader.readLine());
+    reader.close();
+  }
+
+  @Test
+  public void testCombinedBufferSizeTwo() throws IOException {
+    BufferedReaderIncludingLineEndings reader = create(TEXT_COMBINED, 2);
+
+    assertEquals("Test\r", reader.readLine());
+    assertEquals("Test2\r\n", reader.readLine());
+    assertEquals("Test3\n", reader.readLine());
+    assertEquals("Test4\r", reader.readLine());
+    assertEquals("\r", reader.readLine());
+    assertEquals("\r\n", reader.readLine());
+    assertEquals("\r\n", reader.readLine());
+    assertEquals("Test5\n", reader.readLine());
+    assertEquals("Test6\r\n", reader.readLine());
+    assertEquals("Test7\n", reader.readLine());
+    assertEquals("\n", reader.readLine());
+    assertNull(reader.readLine());
+    assertNull(reader.readLine());
+    reader.close();
+  }
+
+  @Test
+  public void testCombinedBufferSizeOne() throws IOException {
+    final String TEXT = "Test\r" +
+        "Test2\r\n" +
+        "Test3\n" +
+        "Test4\r" +
+        "\r" +
+        "\r\n" +
+        "\r\n" +
+        "Test5\n" +
+        "Test6\r\n" +
+        "Test7\n" +
+        "\r\n";
+
+    BufferedReaderIncludingLineEndings reader = create(TEXT, 1);
+
+    assertEquals("Test\r", reader.readLine());
+    assertEquals("Test2\r\n", reader.readLine());
+    assertEquals("Test3\n", reader.readLine());
+    assertEquals("Test4\r", reader.readLine());
+    assertEquals("\r", reader.readLine());
+    assertEquals("\r\n", reader.readLine());
+    assertEquals("\r\n", reader.readLine());
+    assertEquals("Test5\n", reader.readLine());
+    assertEquals("Test6\r\n", reader.readLine());
+    assertEquals("Test7\n", reader.readLine());
+    assertEquals("\r\n", reader.readLine());
+    assertNull(reader.readLine());
+    assertNull(reader.readLine());
+
+    reader.close();
+  }
+
+  @Test
+  public void testDoubleLF() throws IOException {
+    final String TEXT = "Test\r" +
+        "\r";
+
+    BufferedReaderIncludingLineEndings reader = create(TEXT, 1);
+
+    assertEquals("Test\r", reader.readLine());
+    assertEquals("\r", reader.readLine());
+    reader.close();
+  }
+
+  @Test
+  public void testSkipSimple() throws IOException {
+    BufferedReaderIncludingLineEndings reader = create(TEXT_SMALL);
+
+    assertEquals(5, reader.skip(5)); // Test\r
+    assertEquals("123", reader.readLine());
+    assertNull(reader.readLine());
+    assertNull(reader.readLine());
+    reader.close();
+  }
+
+  @Test
+  public void testSkipBufferOne() throws IOException {
+    BufferedReaderIncludingLineEndings reader = create(TEXT_SMALL, 1);
+
+    assertEquals(5, reader.skip(5)); // Test\r
+    assertEquals("123", reader.readLine());
+    assertNull(reader.readLine());
+    assertNull(reader.readLine());
+    reader.close();
+  }
+
+  @Test
+  public void testReadThanSkip() throws IOException {
+    final String TEXT = "Test\r" +
+        "\r" +
+        "123";
+
+    BufferedReaderIncludingLineEndings reader = create(TEXT);
+
+    assertEquals("Test\r", reader.readLine());
+    assertEquals(1, reader.skip(1)); // Test\r
+    assertEquals("123", reader.readLine());
+    assertNull(reader.readLine());
+    assertNull(reader.readLine());
+    reader.close();
+  }
+
+  @Test
+  public void testReadMoreBufferCapacityThanCharacterAvailable() throws IOException {
+    final String TEXT = "Foo";
+    char[] buffer = new char[20];
+
+    BufferedReaderIncludingLineEndings reader = create(TEXT);
+    assertEquals(3, reader.read(buffer, 0, 20));
+    assertEquals(-1, reader.read(buffer, 0, 20));
+    reader.close();
+
+    BufferedReaderIncludingLineEndings readerBufferOne = create(TEXT, 1);
+    assertEquals(3, readerBufferOne.read(buffer, 0, 20));
+    assertEquals(-1, readerBufferOne.read(buffer, 0, 20));
+    readerBufferOne.close();
+  }
+
+  @Test
+  public void testSkipZero() throws IOException {
+    final String TEXT = "Test\r" +
+        "123\r\n";
+
+    BufferedReaderIncludingLineEndings reader = create(TEXT);
+
+    assertEquals(0, reader.skip(0)); // Test\r
+    assertEquals("Test\r", reader.readLine());
+    assertEquals("123\r\n", reader.readLine());
+    assertNull(reader.readLine());
+    assertNull(reader.readLine());
+    reader.close();
+  }
+
+  @Test
+  public void testSkipToMuch() throws IOException {
+    BufferedReaderIncludingLineEndings reader = create(TEXT_SMALL);
+
+    assertEquals(8, reader.skip(10)); // Test\r
+    assertEquals(null, reader.readLine());
+    reader.close();
+  }
+
+  @Test
+  public void testReadBufferOne() throws IOException {
+    BufferedReaderIncludingLineEndings reader = create(TEXT_SMALL, 1);
+
+    assertEquals('T', reader.read());
+    assertEquals('e', reader.read());
+    assertEquals('s', reader.read());
+    assertEquals('t', reader.read());
+    assertEquals('\r', reader.read());
+    assertEquals('1', reader.read());
+    assertEquals('2', reader.read());
+    assertEquals('3', reader.read());
+    assertEquals(-1, reader.read());
+    assertEquals(-1, reader.read());
+  }
+
+  @Test
+  public void testReadZeroBytes() throws IOException {
+    BufferedReaderIncludingLineEndings reader = create(TEXT_SMALL, 1);
+
+    char[] buffer = new char[3];
+    assertEquals(0, reader.read(buffer, 0, 0));
+    assertEquals('T', reader.read());
+    assertEquals(0, reader.read(buffer, 0, 0));
+    assertEquals("est\r", reader.readLine());
+    assertEquals("123", reader.readLine());
+
+    reader.close();
+  }
+
+  @Test
+  public void testRead() throws IOException {
+    BufferedReaderIncludingLineEndings reader = create(TEXT_SMALL);
+
+    assertEquals('T', reader.read());
+    assertEquals('e', reader.read());
+    assertEquals('s', reader.read());
+    assertEquals('t', reader.read());
+    assertEquals('\r', reader.read());
+    assertEquals('1', reader.read());
+    assertEquals('2', reader.read());
+    assertEquals('3', reader.read());
+    assertEquals(-1, reader.read());
+    assertEquals(-1, reader.read());
+  }
+
+  @Test(expected = IndexOutOfBoundsException.class)
+  public void testFailReadBufferAndOffsetBiggerThanBuffer() throws IOException {
+    BufferedReaderIncludingLineEndings reader = create("");
+
+    final char[] buffer = new char[3];
+    reader.read(buffer, 1, 3);
+  }
+
+  @Test(expected = IndexOutOfBoundsException.class)
+  public void testFailLengthNegative() throws IOException {
+    final char[] buffer = new char[3];
+    BufferedReaderIncludingLineEndings reader = create("123");
+
+    reader.read(buffer, 1, -2);
+    reader.close();
+  }
+
+  @Test(expected = IndexOutOfBoundsException.class)
+  public void testFailOffsetNegative() throws IOException {
+    final char[] buffer = new char[3];
+    BufferedReaderIncludingLineEndings reader = create("123");
+
+    reader.read(buffer, -1, 2);
+    reader.close();
+  }
+
+  @Test
+  public void testReadAndReadLine() throws IOException {
+    final String TEXT = "Test\r" +
+        "bar\n" +
+        "123\r\n" +
+        "foo";
+
+    BufferedReaderIncludingLineEndings reader = create(TEXT);
+
+    assertEquals('T', reader.read());
+    assertEquals('e', reader.read());
+    assertEquals('s', reader.read());
+    assertEquals('t', reader.read());
+    assertEquals("\r", reader.readLine());
+    assertEquals("bar\n", reader.readLine());
+    assertEquals('1', reader.read());
+    assertEquals('2', reader.read());
+    assertEquals("3\r\n", reader.readLine());
+    assertEquals("foo", reader.readLine());
+    assertEquals(null, reader.readLine());
+    assertEquals(-1, reader.read());
+  }
+  
+  @Test
+  public void testLineEqualsAndHashCode() {
+    Line l1 = new Line("The first line", 1);
+    Line l2 = new Line("The first line", 1);
+    Line l3 = new Line("The second line", 2);
+    
+    assertEquals(l1, l2);
+    assertFalse(l1.equals(l3));
+    assertTrue(l1.hashCode() != l3.hashCode());
+  }
+  
+  @Test(expected = IllegalArgumentException.class)
+  public void testSkipNegative() throws IOException {
+    BufferedReaderIncludingLineEndings reader = create("123");
+    reader.skip(-1);
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testFailBufferSizeZero() throws IOException {
+    BufferedReaderIncludingLineEndings reader = create(TEXT_EMPTY, 0);
+    reader.close();
+  }
+
+  @Test(expected = NullPointerException.class)
+  public void testInputStreamIsNull() throws IOException {
+    // Same behaviour like BufferedReader
+    BufferedReaderIncludingLineEndings reader = new BufferedReaderIncludingLineEndings(null);
+    reader.close();
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testFailBufferSizeNegative() throws IOException {
+    BufferedReaderIncludingLineEndings reader = create(TEXT_EMPTY, -1);
+    reader.close();
+  }
+
+  @Test
+  public void testMarkSupoorted() throws IOException {
+    BufferedReaderIncludingLineEndings reader = create(TEXT_EMPTY);
+
+    assertEquals(false, reader.markSupported());
+    reader.close();
+  }
+
+  @Test(expected = IOException.class)
+  public void testFailMark() throws IOException {
+    BufferedReaderIncludingLineEndings reader = create("123");
+
+    reader.mark(1);
+  }
+
+  @Test(expected = IOException.class)
+  public void testFailReset() throws IOException {
+    BufferedReaderIncludingLineEndings reader = create("123");
+
+    reader.reset();
+  }
+
+  @Test
+  public void testReady() throws IOException {
+    BufferedReaderIncludingLineEndings reader = create("123\r123");
+    assertEquals(false, reader.ready());
+    assertEquals("123\r", reader.readLine());
+    assertEquals(true, reader.ready());
+    assertEquals("123", reader.readLine());
+    assertEquals(false, reader.ready());
+
+    reader.close();
+  }
+
+  @Test
+  public void testToList() throws IOException {
+    BufferedReaderIncludingLineEndings reader = create(TEXT_COMBINED);
+    List<Line> stringList = reader.toLineList();
+
+    assertEquals(11, stringList.size());
+    assertEquals("Test\r", stringList.get(0).toString());
+    assertEquals("Test2\r\n", stringList.get(1).toString());
+    assertEquals("Test3\n", stringList.get(2).toString());
+    assertEquals("Test4\r", stringList.get(3).toString());
+    assertEquals("\r", stringList.get(4).toString());
+    assertEquals("\r\n", stringList.get(5).toString());
+    assertEquals("\r\n", stringList.get(6).toString());
+    assertEquals("Test5\n", stringList.get(7).toString());
+    assertEquals("Test6\r\n", stringList.get(8).toString());
+    assertEquals("Test7\n", stringList.get(9).toString());
+    assertEquals("\n", stringList.get(10).toString());
+    reader.close();
+  }
+
+  private BufferedReaderIncludingLineEndings create(final String inputString) throws UnsupportedEncodingException {
+    return new BufferedReaderIncludingLineEndings(new InputStreamReader(new ByteArrayInputStream(inputString
+        .getBytes("UTF-8"))));
+  }
+
+  private BufferedReaderIncludingLineEndings create(final String inputString, int bufferSize)
+      throws UnsupportedEncodingException {
+    return new BufferedReaderIncludingLineEndings(new InputStreamReader(new ByteArrayInputStream(inputString
+        .getBytes("UTF-8"))), bufferSize);
+  }
+
+}


[21/22] olingo-odata4 git commit: [OLINGO-472] Batch Refactoring

Posted by ch...@apache.org.
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BufferedReaderIncludingLineEndings.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BufferedReaderIncludingLineEndings.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BufferedReaderIncludingLineEndings.java
deleted file mode 100644
index aebf13b..0000000
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BufferedReaderIncludingLineEndings.java
+++ /dev/null
@@ -1,286 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.olingo.server.core.batch.parser;
-
-import java.io.IOException;
-import java.io.Reader;
-import java.util.ArrayList;
-import java.util.List;
-
-public class BufferedReaderIncludingLineEndings extends Reader {
-  private static final char CR = '\r';
-  private static final char LF = '\n';
-  private static final int EOF = -1;
-  private static final int BUFFER_SIZE = 8192;
-  private Reader reader;
-  private char[] buffer;
-  private int offset = 0;
-  private int limit = 0;
-
-  public BufferedReaderIncludingLineEndings(final Reader reader) {
-    this(reader, BUFFER_SIZE);
-  }
-
-  public BufferedReaderIncludingLineEndings(final Reader reader, final int bufferSize) {
-    if (bufferSize <= 0) {
-      throw new IllegalArgumentException("Buffer size must be greater than zero.");
-    }
-
-    this.reader = reader;
-    buffer = new char[bufferSize];
-  }
-
-  @Override
-  public int read(final char[] charBuffer, final int bufferOffset, final int length) throws IOException {
-    if ((bufferOffset + length) > charBuffer.length) {
-      throw new IndexOutOfBoundsException("Buffer is too small");
-    }
-
-    if (length < 0 || bufferOffset < 0) {
-      throw new IndexOutOfBoundsException("Offset and length must be grater than zero");
-    }
-
-    // Check if buffer is filled. Return if EOF is reached
-    // Is buffer refill required
-    if (limit == offset || isEOF()) {
-      fillBuffer();
-
-      if (isEOF()) {
-        return EOF;
-      }
-    }
-
-    int bytesRead = 0;
-    int bytesToRead = length;
-    int currentOutputOffset = bufferOffset;
-
-    while (bytesToRead != 0) {
-      // Is buffer refill required?
-      if (limit == offset) {
-        fillBuffer();
-
-        if (isEOF()) {
-          bytesToRead = 0;
-        }
-      }
-
-      if (bytesToRead > 0) {
-        int readByte = Math.min(limit - offset, bytesToRead);
-        bytesRead += readByte;
-        bytesToRead -= readByte;
-
-        for (int i = 0; i < readByte; i++) {
-          charBuffer[currentOutputOffset++] = buffer[offset++];
-        }
-      }
-    }
-
-    return bytesRead;
-  }
-  
-  public List<String> toList() throws IOException {
-    final List<String> result = new ArrayList<String>();
-    String currentLine;
-    
-    while ((currentLine = readLine()) != null) {
-      result.add(currentLine);
-    }
-
-    return result;
-  }
-  
-  public List<Line> toLineList() throws IOException {
-    final List<Line> result = new ArrayList<Line>();
-    String currentLine;
-    int counter = 1;
-
-    while ((currentLine = readLine()) != null) {
-      result.add(new Line(currentLine, counter++));
-    }
-
-    return result;
-  }
-
-  public String readLine() throws IOException {
-    if (limit == EOF) {
-      return null;
-    }
-
-    final StringBuilder stringBuffer = new StringBuilder();
-    boolean foundLineEnd = false; // EOF will be considered as line ending
-
-    while (!foundLineEnd) {
-      // Is buffer refill required?
-      if (limit == offset) {
-        if (fillBuffer() == EOF) {
-          foundLineEnd = true;
-        }
-      }
-
-      if (!foundLineEnd) {
-        char currentChar = buffer[offset++];
-        stringBuffer.append(currentChar);
-
-        if (currentChar == LF) {
-          foundLineEnd = true;
-        } else if (currentChar == CR) {
-          foundLineEnd = true;
-
-          // Check next char. Consume \n if available
-          // Is buffer refill required?
-          if (limit == offset) {
-            fillBuffer();
-          }
-
-          // Check if there is at least one character
-          if (limit != EOF && buffer[offset] == LF) {
-            stringBuffer.append(LF);
-            offset++;
-          }
-        }
-      }
-    }
-
-    return (stringBuffer.length() == 0) ? null : stringBuffer.toString();
-  }
-
-  @Override
-  public void close() throws IOException {
-    reader.close();
-  }
-
-  @Override
-  public boolean ready() throws IOException {
-    // Not EOF and buffer refill is not required
-    return !isEOF() && !(limit == offset);
-  }
-
-  @Override
-  public void reset() throws IOException {
-    throw new IOException("Reset is not supported");
-  }
-
-  @Override
-  public void mark(final int readAheadLimit) throws IOException {
-    throw new IOException("Mark is not supported");
-  }
-
-  @Override
-  public boolean markSupported() {
-    return false;
-  }
-
-  @Override
-  public long skip(final long n) throws IOException {
-    if (n == 0) {
-      return 0;
-    } else if (n < 0) {
-      throw new IllegalArgumentException("skip value is negative");
-    } else {
-      long charactersToSkip = n;
-      long charactersSkiped = 0;
-
-      while (charactersToSkip != 0) {
-        // Is buffer refill required?
-        if (limit == offset) {
-          fillBuffer();
-
-          if (isEOF()) {
-            charactersToSkip = 0;
-          }
-        }
-
-        // Check if more characters are available
-        if (!isEOF()) {
-          int skipChars = (int) Math.min(limit - offset, charactersToSkip);
-
-          charactersSkiped += skipChars;
-          charactersToSkip -= skipChars;
-          offset += skipChars;
-        }
-      }
-
-      return charactersSkiped;
-    }
-  }
-
-  private boolean isEOF() {
-    return limit == EOF;
-  }
-
-  private int fillBuffer() throws IOException {
-    limit = reader.read(buffer, 0, buffer.length);
-    offset = 0;
-
-    return limit;
-  }
-
-  public static class Line {
-    private final int lineNumber;
-    private final String content;
-
-    public Line(final String content, final int lineNumber) {
-      this.content = content;
-      this.lineNumber = lineNumber;
-    }
-
-    public int getLineNumber() {
-      return lineNumber;
-    }
-
-    @Override
-    public String toString() {
-      return content;
-    }
-
-    @Override
-    public int hashCode() {
-      final int prime = 31;
-      int result = 1;
-      result = prime * result + ((content == null) ? 0 : content.hashCode());
-      result = prime * result + lineNumber;
-      return result;
-    }
-
-    @Override
-    public boolean equals(final Object obj) {
-      if (this == obj) {
-        return true;
-      }
-      if (obj == null) {
-        return false;
-      }
-      if (getClass() != obj.getClass()) {
-        return false;
-      }
-      Line other = (Line) obj;
-      if (content == null) {
-        if (other.content != null) {
-          return false;
-        }
-      } else if (!content.equals(other.content)) {
-        return false;
-      }
-      if (lineNumber != other.lineNumber) {
-        return false;
-      }
-      return true;
-    }
-  }
-}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/Header.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/Header.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/Header.java
deleted file mode 100644
index 2ac009d..0000000
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/Header.java
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.olingo.server.core.batch.parser;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.regex.Pattern;
-
-public class Header implements Iterable<HeaderField> {
-  private final Map<String, HeaderField> headers = new HashMap<String, HeaderField>();
-  private int lineNumber;
-
-  public Header(final int lineNumer) {
-    lineNumber = lineNumer;
-  }
-
-  public void addHeader(final String name, final String value, final int lineNumber) {
-    final HeaderField headerField = getHeaderFieldOrDefault(name, lineNumber);
-    final List<String> headerValues = headerField.getValues();
-
-    if (!headerValues.contains(value)) {
-      headerValues.add(value);
-    }
-  }
-
-  public void addHeader(final String name, final List<String> values, final int lineNumber) {
-    final HeaderField headerField = getHeaderFieldOrDefault(name, lineNumber);
-    final List<String> headerValues = headerField.getValues();
-
-    for (final String value : values) {
-      if (!headerValues.contains(value)) {
-        headerValues.add(value);
-      }
-    }
-  }
-
-  public void replaceHeaderField(final HeaderField headerField) {
-    headers.put(headerField.getFieldName().toLowerCase(Locale.ENGLISH), headerField);
-  }
-
-  public boolean exists(final String name) {
-    final HeaderField field = headers.get(name.toLowerCase(Locale.ENGLISH));
-    
-    return field != null && field.getValues().size() != 0;
-  }
-
-  public boolean isHeaderMatching(final String name, final Pattern pattern) {
-    if (getHeaders(name).size() != 1) {
-      return false;
-    } else {
-      return pattern.matcher(getHeaders(name).get(0)).matches();
-    }
-  }
-
-  public void removeHeader(final String name) {
-    headers.remove(name.toLowerCase(Locale.ENGLISH));
-  }
-
-  public String getHeader(final String name) {
-    final HeaderField headerField = getHeaderField(name);
-
-    return (headerField == null) ? null : headerField.getValue();
-  }
-
-  public String getHeaderNotNull(final String name) {
-    final HeaderField headerField = getHeaderField(name);
-
-    return (headerField == null) ? "" : headerField.getValueNotNull();
-  }
-
-  public List<String> getHeaders(final String name) {
-    final HeaderField headerField = getHeaderField(name);
-
-    return (headerField == null) ? new ArrayList<String>() : headerField.getValues();
-  }
-
-  public HeaderField getHeaderField(final String name) {
-    return headers.get(name.toLowerCase(Locale.ENGLISH));
-  }
-
-  public int getLineNumber() {
-    return lineNumber;
-  }
-
-  public Map<String, String> toSingleMap() {
-    final Map<String, String> singleMap = new HashMap<String, String>();
-
-    for (final String key : headers.keySet()) {
-      HeaderField field = headers.get(key);
-      singleMap.put(field.getFieldName(), getHeader(key));
-    }
-
-    return singleMap;
-  }
-
-  public Map<String, List<String>> toMultiMap() {
-    final Map<String, List<String>> singleMap = new HashMap<String, List<String>>();
-
-    for (final String key : headers.keySet()) {
-      HeaderField field = headers.get(key);
-      singleMap.put(field.getFieldName(), field.getValues());
-    }
-
-    return singleMap;
-  }
-
-  private HeaderField getHeaderFieldOrDefault(final String name, final int lineNumber) {
-    HeaderField headerField = headers.get(name.toLowerCase(Locale.ENGLISH));
-
-    if (headerField == null) {
-      headerField = new HeaderField(name, lineNumber);
-      headers.put(name.toLowerCase(Locale.ENGLISH), headerField);
-    }
-
-    return headerField;
-  }
-
-  @Override
-  public Header clone() {
-    final Header newInstance = new Header(lineNumber);
-
-    for (final String key : headers.keySet()) {
-      newInstance.headers.put(key, headers.get(key).clone());
-    }
-
-    return newInstance;
-  }
-
-  @Override
-  public Iterator<HeaderField> iterator() {
-    return new Iterator<HeaderField>() {
-      Iterator<String> keyIterator = headers.keySet().iterator();
-
-      @Override
-      public boolean hasNext() {
-        return keyIterator.hasNext();
-      }
-
-      @Override
-      public HeaderField next() {
-        return headers.get(keyIterator.next());
-      }
-      
-      @Override
-      public void remove() {
-        throw new UnsupportedOperationException();
-      }
-    };
-  }
-
-  public static List<String> splitValuesByComma(final String headerValue) {
-    final List<String> singleValues = new ArrayList<String>();
-
-    String[] parts = headerValue.split(",");
-    for (final String value : parts) {
-      singleValues.add(value.trim());
-    }
-
-    return singleValues;
-  }
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/HeaderField.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/HeaderField.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/HeaderField.java
deleted file mode 100644
index 4cad817..0000000
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/HeaderField.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.olingo.server.core.batch.parser;
-
-import java.util.ArrayList;
-import java.util.List;
-
-  public class HeaderField implements Cloneable {
-    private final String fieldName;
-    private final List<String> values;
-    private final int lineNumber;
-
-    public HeaderField(final String fieldName, final int lineNumber) {
-      this(fieldName, new ArrayList<String>(), lineNumber);
-    }
-
-    public HeaderField(final String fieldName, final List<String> values, final int lineNumber) {
-      this.fieldName = fieldName;
-      this.values = values;
-      this.lineNumber = lineNumber;
-    }
-
-    public String getFieldName() {
-      return fieldName;
-    }
-
-    public List<String> getValues() {
-      return values;
-    }
-
-    public String getValue() {
-      final StringBuilder result = new StringBuilder();
-
-      for (final String value : values) {
-        result.append(value);
-        result.append(", ");
-      }
-
-      if (result.length() > 0) {
-        result.delete(result.length() - 2, result.length());
-      }
-
-      return result.toString();
-    }
-
-    public String getValueNotNull() {
-      final String value = getValue();
-
-      return (value == null) ? "" : value;
-    }
-
-    @Override
-    public HeaderField clone() {
-      List<String> newValues = new ArrayList<String>();
-      newValues.addAll(values);
-
-      return new HeaderField(fieldName, newValues, lineNumber);
-    }
-
-    public int getLineNumber() {
-      return lineNumber;
-    }
-
-    @Override
-    public int hashCode() {
-      final int prime = 31;
-      int result = 1;
-      result = prime * result + ((fieldName == null) ? 0 : fieldName.hashCode());
-      result = prime * result + lineNumber;
-      result = prime * result + ((values == null) ? 0 : values.hashCode());
-      return result;
-    }
-
-    @Override
-    public boolean equals(final Object obj) {
-      if (this == obj) {
-        return true;
-      }
-      if (obj == null) {
-        return false;
-      }
-      if (getClass() != obj.getClass()) {
-        return false;
-      }
-      HeaderField other = (HeaderField) obj;
-      if (fieldName == null) {
-        if (other.fieldName != null) {
-          return false;
-        }
-      } else if (!fieldName.equals(other.fieldName)) {
-        return false;
-      }
-      if (lineNumber != other.lineNumber) {
-        return false;
-      }
-      if (values == null) {
-        if (other.values != null) {
-          return false;
-        }
-      } else if (!values.equals(other.values)) {
-        return false;
-      }
-      return true;
-    }
-}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchRequestTransformator.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchRequestTransformator.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchRequestTransformator.java
deleted file mode 100644
index 0bf0104..0000000
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchRequestTransformator.java
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.olingo.server.core.batch.transformator;
-
-import java.io.ByteArrayInputStream;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.List;
-
-import org.apache.olingo.commons.api.http.HttpHeader;
-import org.apache.olingo.commons.api.http.HttpMethod;
-import org.apache.olingo.server.api.ODataRequest;
-import org.apache.olingo.server.api.batch.BatchException;
-import org.apache.olingo.server.api.batch.BatchParserResult;
-import org.apache.olingo.server.api.batch.BatchException.MessageKeys;
-import org.apache.olingo.server.core.batch.parser.BatchBodyPart;
-import org.apache.olingo.server.core.batch.parser.BatchChangeSetPart;
-import org.apache.olingo.server.core.batch.parser.BatchParserCommon;
-import org.apache.olingo.server.core.batch.parser.BatchPart;
-import org.apache.olingo.server.core.batch.parser.BatchQueryOperation;
-import org.apache.olingo.server.core.batch.parser.BatchRequestPartImpl;
-import org.apache.olingo.server.core.batch.parser.Header;
-import org.apache.olingo.server.core.batch.parser.HeaderField;
-import org.apache.olingo.server.core.batch.transformator.HttpRequestStatusLine.ODataURI;
-
-public class BatchRequestTransformator implements BatchTransformator {
-  private final String baseUri;
-  private final String rawServiceResolutionUri;
-
-  public BatchRequestTransformator(final String baseUri, final String serviceResolutionUri) {
-    this.baseUri = baseUri;
-    this.rawServiceResolutionUri = serviceResolutionUri;
-  }
-
-  @Override
-  public List<BatchParserResult> transform(final BatchBodyPart bodyPart) throws BatchException {
-    final List<ODataRequest> requests = new LinkedList<ODataRequest>();
-    final List<BatchParserResult> resultList = new ArrayList<BatchParserResult>();
-
-    validateBodyPartHeader(bodyPart);
-
-    for (BatchQueryOperation queryOperation : bodyPart.getRequests()) {
-      requests.add(processQueryOperation(bodyPart, baseUri, queryOperation));
-    }
-
-    resultList.add(new BatchRequestPartImpl(bodyPart.isChangeSet(), requests));
-    return resultList;
-  }
-
-  private ODataRequest
-      processQueryOperation(BatchBodyPart bodyPart, String baseUri, BatchQueryOperation queryOperation)
-          throws BatchException {
-    if (bodyPart.isChangeSet()) {
-      BatchQueryOperation encapsulatedQueryOperation = ((BatchChangeSetPart) queryOperation).getRequest();
-      handleContentId(queryOperation, encapsulatedQueryOperation);
-      validateHeader(queryOperation, true);
-
-      return createRequest(encapsulatedQueryOperation, baseUri, bodyPart.isChangeSet());
-    } else {
-      return createRequest(queryOperation, baseUri, bodyPart.isChangeSet());
-    }
-  }
-
-  private void handleContentId(BatchQueryOperation changeRequestPart, BatchQueryOperation request)
-      throws BatchException {
-    final HeaderField contentIdChangeRequestPart = getContentId(changeRequestPart);
-    final HeaderField contentIdRequest = getContentId(request);
-
-    if (contentIdChangeRequestPart == null && contentIdRequest == null) {
-      throw new BatchException("Missing content id", MessageKeys.MISSING_CONTENT_ID, changeRequestPart.getHeaders()
-          .getLineNumber());
-    } else if (contentIdChangeRequestPart != null) {
-        request.getHeaders().replaceHeaderField(contentIdChangeRequestPart);
-    }
-  }
-
-  private HeaderField getContentId(final BatchQueryOperation queryOperation) throws BatchException {
-    final HeaderField contentTypeHeader = queryOperation.getHeaders().getHeaderField(BatchParserCommon.HTTP_CONTENT_ID);
-
-    if (contentTypeHeader != null) {
-      if (contentTypeHeader.getValues().size() == 1) {
-        return contentTypeHeader;
-      } else {
-        throw new BatchException("Invalid header", MessageKeys.INVALID_HEADER, contentTypeHeader.getLineNumber());
-      }
-    }
-
-    return null;
-  }
-
-  private ODataRequest createRequest(BatchQueryOperation operation, String baseUri, boolean isChangeSet)
-      throws BatchException {
-    final HttpRequestStatusLine statusLine =
-        new HttpRequestStatusLine(operation.getHttpStatusLine(), baseUri, rawServiceResolutionUri, operation
-            .getHeaders());
-    statusLine.validateHttpMethod(isChangeSet);
-    final ODataURI uri = statusLine.getUri();
-    
-    validateBody(statusLine, operation);
-    InputStream bodyStrean = getBodyStream(operation, statusLine);
-
-    validateForbiddenHeader(operation);
-    
-    final ODataRequest request = new ODataRequest();
-    request.setBody(bodyStrean);
-    request.setMethod(statusLine.getMethod());
-    request.setRawBaseUri(uri.getRawBaseUri());
-    request.setRawODataPath(uri.getRawODataPath());
-    request.setRawQueryPath(uri.getRawQueryPath());
-    request.setRawRequestUri(uri.getRawRequestUri());
-    request.setRawServiceResolutionUri(uri.getRawServiceResolutionUri());
-
-    for (final HeaderField field : operation.getHeaders()) {
-      request.addHeader(field.getFieldName(), field.getValues());
-    }
-
-    return request;
-  }
-
-  private void validateForbiddenHeader(BatchQueryOperation operation) throws BatchException {
-    final Header header = operation.getHeaders();
-
-    if (header.exists(HttpHeader.AUTHORIZATION) || header.exists(BatchParserCommon.HTTP_EXPECT)
-        || header.exists(BatchParserCommon.HTTP_FROM) || header.exists(BatchParserCommon.HTTP_MAX_FORWARDS)
-        || header.exists(BatchParserCommon.HTTP_RANGE) || header.exists(BatchParserCommon.HTTP_TE)) {
-      throw new BatchException("Forbidden header", MessageKeys.FORBIDDEN_HEADER, header.getLineNumber());
-    }
-  }
-
-  private InputStream getBodyStream(BatchQueryOperation operation, HttpRequestStatusLine statusLine)
-      throws BatchException {
-    if (statusLine.getMethod().equals(HttpMethod.GET)) {
-      return new ByteArrayInputStream(new byte[0]);
-    } else {
-      int contentLength = BatchTransformatorCommon.getContentLength(operation.getHeaders());
-
-      if (contentLength == -1) {
-        return BatchParserCommon.convertLineListToInputStream(operation.getBody());
-      } else {
-        return BatchParserCommon.convertLineListToInputStream(operation.getBody(), contentLength);
-      }
-    }
-  }
-
-  private void validateBody(HttpRequestStatusLine statusLine, BatchQueryOperation operation) throws BatchException {
-    if (statusLine.getMethod().equals(HttpMethod.GET) && isUnvalidGetRequestBody(operation)) {
-      throw new BatchException("Invalid request line", MessageKeys.INVALID_CONTENT, statusLine.getLineNumber());
-    }
-  }
-
-  private boolean isUnvalidGetRequestBody(final BatchQueryOperation operation) {
-    return (operation.getBody().size() > 1)
-        || (operation.getBody().size() == 1 && !"".equals(operation.getBody().get(0).toString().trim()));
-  }
-
-  private void validateHeader(BatchPart bodyPart, boolean isChangeSet) throws BatchException {
-    final Header headers = bodyPart.getHeaders();
-
-    BatchTransformatorCommon.validateContentType(headers, BatchParserCommon.PATTERN_CONTENT_TYPE_APPLICATION_HTTP);
-    if (isChangeSet) {
-      BatchTransformatorCommon.validateContentTransferEncoding(headers);
-    }
-  }
-
-  private void validateBodyPartHeader(BatchBodyPart bodyPart) throws BatchException {
-    final Header header = bodyPart.getHeaders();
-
-    if (bodyPart.isChangeSet()) {
-      BatchTransformatorCommon.validateContentType(header, BatchParserCommon.PATTERN_MULTIPART_BOUNDARY);
-    } else {
-      BatchTransformatorCommon.validateContentTransferEncoding(header);
-      BatchTransformatorCommon.validateContentType(header, BatchParserCommon.PATTERN_CONTENT_TYPE_APPLICATION_HTTP);
-    }
-  }
-}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchTransformator.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchTransformator.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchTransformator.java
deleted file mode 100644
index 286c0cc..0000000
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchTransformator.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.olingo.server.core.batch.transformator;
-
-import java.util.List;
-
-import org.apache.olingo.server.api.batch.BatchException;
-import org.apache.olingo.server.api.batch.BatchParserResult;
-import org.apache.olingo.server.core.batch.parser.BatchBodyPart;
-
-public interface BatchTransformator {
-  public List<BatchParserResult> transform(BatchBodyPart bodyPart) throws BatchException;
-}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchTransformatorCommon.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchTransformatorCommon.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchTransformatorCommon.java
deleted file mode 100644
index fc2035a..0000000
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchTransformatorCommon.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.olingo.server.core.batch.transformator;
-
-import java.util.List;
-import java.util.regex.Pattern;
-
-import org.apache.olingo.commons.api.http.HttpContentType;
-import org.apache.olingo.commons.api.http.HttpHeader;
-import org.apache.olingo.server.api.batch.BatchException;
-import org.apache.olingo.server.api.batch.BatchException.MessageKeys;
-import org.apache.olingo.server.core.batch.parser.BatchParserCommon;
-import org.apache.olingo.server.core.batch.parser.Header;
-import org.apache.olingo.server.core.batch.parser.HeaderField;
-
-public class BatchTransformatorCommon {
-
-  public static void validateContentType(final Header headers, final Pattern pattern) throws BatchException {
-    List<String> contentTypes = headers.getHeaders(HttpHeader.CONTENT_TYPE);
-
-    if (contentTypes.size() == 0) {
-      throw new BatchException("Missing content type", MessageKeys.MISSING_CONTENT_TYPE, headers.getLineNumber());
-    }
-    if (!headers.isHeaderMatching(HttpHeader.CONTENT_TYPE, pattern)) {
-
-      throw new BatchException("Invalid content type", MessageKeys.INVALID_CONTENT_TYPE,
-          HttpContentType.MULTIPART_MIXED + " or " + HttpContentType.APPLICATION_HTTP);
-    }
-  }
-
-  public static void validateContentTransferEncoding(Header headers) throws BatchException {
-    final HeaderField contentTransferField = headers.getHeaderField(BatchParserCommon.HTTP_CONTENT_TRANSFER_ENCODING);
-
-    if (contentTransferField != null) {
-      final List<String> contentTransferValues = contentTransferField.getValues();
-      if (contentTransferValues.size() == 1) {
-        String encoding = contentTransferValues.get(0);
-
-        if (!BatchParserCommon.BINARY_ENCODING.equalsIgnoreCase(encoding)) {
-          throw new BatchException("Invalid content transfer encoding", MessageKeys.INVALID_CONTENT_TRANSFER_ENCODING,
-              headers.getLineNumber());
-        }
-      } else {
-        throw new BatchException("Invalid header", MessageKeys.INVALID_HEADER, headers.getLineNumber());
-      }
-    } else {
-      throw new BatchException("Missing mandatory content transfer encoding",
-          MessageKeys.MISSING_CONTENT_TRANSFER_ENCODING,
-          headers.getLineNumber());
-    }
-  }
-
-  public static int getContentLength(Header headers) throws BatchException {
-    final HeaderField contentLengthField = headers.getHeaderField(HttpHeader.CONTENT_LENGTH);
-
-    if (contentLengthField != null && contentLengthField.getValues().size() == 1) {
-      final List<String> contentLengthValues = contentLengthField.getValues();
-
-      try {
-        int contentLength = Integer.parseInt(contentLengthValues.get(0));
-
-        if (contentLength < 0) {
-          throw new BatchException("Invalid content length", MessageKeys.INVALID_CONTENT_LENGTH, contentLengthField
-              .getLineNumber());
-        }
-
-        return contentLength;
-      } catch (NumberFormatException e) {
-        throw new BatchException("Invalid header", MessageKeys.INVALID_HEADER, contentLengthField.getLineNumber());
-      }
-    }
-
-    return -1;
-  }
-}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/HttpRequestStatusLine.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/HttpRequestStatusLine.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/HttpRequestStatusLine.java
deleted file mode 100644
index c08bb2e..0000000
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/HttpRequestStatusLine.java
+++ /dev/null
@@ -1,226 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.olingo.server.core.batch.transformator;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import org.apache.olingo.commons.api.http.HttpHeader;
-import org.apache.olingo.commons.api.http.HttpMethod;
-import org.apache.olingo.server.api.batch.BatchException;
-import org.apache.olingo.server.api.batch.BatchException.MessageKeys;
-import org.apache.olingo.server.core.batch.parser.Header;
-import org.apache.olingo.server.core.batch.parser.BufferedReaderIncludingLineEndings.Line;
-
-public class HttpRequestStatusLine {
-  private static final Pattern PATTERN_RELATIVE_URI = Pattern.compile("([^/][^?]*)(?:\\?(.*))?");
-  private static final Pattern PATTERN_ABSOLUTE_URI_WITH_HOST = Pattern.compile("(/[^?]*)(?:\\?(.*))?");
-  private static final Pattern PATTERN_ABSOLUTE_URI = Pattern.compile("(http[s]?://[^?]*)(?:\\?(.*))?");
-
-  private static final Set<String> HTTP_BATCH_METHODS = new HashSet<String>(Arrays.asList(new String[] { "GET" }));
-  private static final Set<String> HTTP_CHANGE_SET_METHODS = new HashSet<String>(Arrays.asList(new String[] { "POST",
-      "PUT", "DELETE", "PATCH" }));
-  private static final String HTTP_VERSION = "HTTP/1.1";
-
-  final private Line statusLine;
-  final String requestBaseUri;
-
-  private HttpMethod method;
-  private String httpVersion;
-  private Header header;
-  private ODataURI uri;
-
-  public HttpRequestStatusLine(final Line httpStatusLine, final String baseUri, final String serviceResolutionUri,
-      final Header requestHeader)
-      throws BatchException {
-    statusLine = httpStatusLine;
-    requestBaseUri = baseUri;
-    header = requestHeader;
-
-    parse();
-  }
-
-  private void parse() throws BatchException {
-    final String[] parts = statusLine.toString().split(" ");
-
-    if (parts.length == 3) {
-      method = parseMethod(parts[0]);
-      uri = new ODataURI(parts[1], requestBaseUri, statusLine.getLineNumber(), header.getHeaders(HttpHeader.HOST));
-      httpVersion = parseHttpVersion(parts[2]);
-    } else {
-      throw new BatchException("Invalid status line", MessageKeys.INVALID_STATUS_LINE, statusLine.getLineNumber());
-    }
-  }
-
-  private HttpMethod parseMethod(final String method) throws BatchException {
-    try {
-      return HttpMethod.valueOf(method.trim());
-    } catch (IllegalArgumentException e) {
-      throw new BatchException("Illegal http method", MessageKeys.INVALID_METHOD, statusLine.getLineNumber());
-    }
-  }
-
-  private String parseHttpVersion(final String httpVersion) throws BatchException {
-    if (!HTTP_VERSION.equals(httpVersion.trim())) {
-      throw new BatchException("Invalid http version", MessageKeys.INVALID_HTTP_VERSION, statusLine.getLineNumber());
-    } else {
-      return HTTP_VERSION;
-    }
-  }
-
-  public void validateHttpMethod(boolean isChangeSet) throws BatchException {
-    Set<String> validMethods = (isChangeSet) ? HTTP_CHANGE_SET_METHODS : HTTP_BATCH_METHODS;
-
-    if (!validMethods.contains(getMethod().toString())) {
-      if (isChangeSet) {
-        throw new BatchException("Invalid change set method", MessageKeys.INVALID_CHANGESET_METHOD, statusLine
-            .getLineNumber());
-      } else {
-        throw new BatchException("Invalid query operation method", MessageKeys.INVALID_QUERY_OPERATION_METHOD,
-            statusLine.getLineNumber());
-      }
-    }
-  }
-
-  public HttpMethod getMethod() {
-    return method;
-  }
-
-  public String getHttpVersion() {
-    return httpVersion;
-  }
-
-  public int getLineNumber() {
-    return statusLine.getLineNumber();
-  }
-
-  public ODataURI getUri() {
-    return uri;
-  }
-
-  public static class ODataURI {
-    private String rawServiceResolutionUri;
-    private String rawQueryPath;
-    private String rawODataPath;
-    private String rawBaseUri;
-    private String rawRequestUri;
-    private final String requestBaseUri;
-    private final int lineNumber;
-
-    public ODataURI(final String rawUri, String requestBaseUri) throws BatchException {
-      this(rawUri, requestBaseUri, 0, new ArrayList<String>());
-    }
-
-    public ODataURI(final String rawUri, String requestBaseUri, int lineNumber, List<String> hostHeader)
-        throws BatchException {
-      this.lineNumber = lineNumber;
-      this.requestBaseUri = requestBaseUri;
-
-      final Matcher absoluteUriMatcher = PATTERN_ABSOLUTE_URI.matcher(rawUri);
-      final Matcher absoluteUriWtithHostMatcher = PATTERN_ABSOLUTE_URI_WITH_HOST.matcher(rawUri);
-      final Matcher relativeUriMatcher = PATTERN_RELATIVE_URI.matcher(rawUri);
-
-      if (absoluteUriMatcher.matches()) {
-        buildUri(absoluteUriMatcher.group(1), absoluteUriMatcher.group(2));
-
-      } else if (absoluteUriWtithHostMatcher.matches()) {
-        if (hostHeader != null && hostHeader.size() == 1) {
-          buildUri(hostHeader.get(0) + absoluteUriWtithHostMatcher.group(1), absoluteUriWtithHostMatcher.group(2));
-        } else {
-          throw new BatchException("Exactly one host header is required", MessageKeys.MISSING_MANDATORY_HEADER,
-              lineNumber);
-        }
-
-      } else if (relativeUriMatcher.matches()) {
-        buildUri(requestBaseUri + "/" + relativeUriMatcher.group(1), relativeUriMatcher.group(2));
-
-      } else {
-        throw new BatchException("Invalid uri", MessageKeys.INVALID_URI, lineNumber);
-      }
-    }
-
-    private void buildUri(final String resourceUri, final String queryOptions) throws BatchException {
-      if (!resourceUri.startsWith(requestBaseUri)) {
-        throw new BatchException("Host do not match", MessageKeys.INVALID_URI, lineNumber);
-      }
-
-      final int oDataPathIndex = resourceUri.indexOf(requestBaseUri);
-
-      rawBaseUri = requestBaseUri;
-      rawODataPath = resourceUri.substring(oDataPathIndex + requestBaseUri.length());
-      rawRequestUri = requestBaseUri + rawODataPath;
-
-      if (queryOptions != null) {
-        rawRequestUri += "?" + queryOptions;
-        rawQueryPath = queryOptions;
-      } else {
-        rawQueryPath = "";
-      }
-    }
-
-    public String getRawServiceResolutionUri() {
-      return rawServiceResolutionUri;
-    }
-
-    public void setRawServiceResolutionUri(String rawServiceResolutionUri) {
-      this.rawServiceResolutionUri = rawServiceResolutionUri;
-    }
-
-    public String getRawQueryPath() {
-      return rawQueryPath;
-    }
-
-    public void setRawQueryPath(String rawQueryPath) {
-      this.rawQueryPath = rawQueryPath;
-    }
-
-    public String getRawODataPath() {
-      return rawODataPath;
-    }
-
-    public void setRawODataPath(String rawODataPath) {
-      this.rawODataPath = rawODataPath;
-    }
-
-    public String getRawBaseUri() {
-      return rawBaseUri;
-    }
-
-    public void setRawBaseUri(String rawBaseUri) {
-      this.rawBaseUri = rawBaseUri;
-    }
-
-    public String getRawRequestUri() {
-      return rawRequestUri;
-    }
-
-    public void setRawRequestUri(String rawRequestUri) {
-      this.rawRequestUri = rawRequestUri;
-    }
-
-    public String getRequestBaseUri() {
-      return requestBaseUri;
-    }
-  }
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/writer/BatchResponseWriter.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/writer/BatchResponseWriter.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/writer/BatchResponseWriter.java
deleted file mode 100644
index 0c13cb9..0000000
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/writer/BatchResponseWriter.java
+++ /dev/null
@@ -1,228 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.olingo.server.core.batch.writer;
-
-import java.io.BufferedWriter;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStreamWriter;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-
-import org.apache.olingo.commons.api.http.HttpContentType;
-import org.apache.olingo.commons.api.http.HttpHeader;
-import org.apache.olingo.commons.api.http.HttpStatusCode;
-import org.apache.olingo.server.api.ODataResponse;
-import org.apache.olingo.server.api.batch.BatchException;
-import org.apache.olingo.server.api.batch.ODataResponsePart;
-import org.apache.olingo.server.api.batch.BatchException.MessageKeys;
-import org.apache.olingo.server.core.batch.parser.BatchParserCommon;
-import org.apache.olingo.server.core.serializer.utils.CircleStreamBuffer;
-
-public class BatchResponseWriter {
-  private static final int BUFFER_SIZE = 4096;
-  private static final String DOUBLE_DASH = "--";
-  private static final String COLON = ":";
-  private static final String SP = " ";
-  private static final String CRLF = "\r\n";
-
-  public void toODataResponse(final List<ODataResponsePart> batchResponse, final ODataResponse response)
-      throws IOException, BatchException {
-    final String boundary = generateBoundary("batch");
-
-    setStatusCode(response);
-    ResponseWriter writer = createBody(batchResponse, boundary);
-
-    response.setContent(writer.toInputStream());
-    setHttpHeader(response, writer, boundary);
-  }
-
-  private ResponseWriter createBody(final List<ODataResponsePart> batchResponses, final String boundary)
-      throws IOException, BatchException {
-    final ResponseWriter writer = new ResponseWriter();
-
-    for (final ODataResponsePart part : batchResponses) {
-      writer.append(getDashBoundary(boundary));
-
-      if (part.isChangeSet()) {
-        appendChangeSet(part, writer);
-      } else {
-        appendBodyPart(part.getResponses().get(0), writer, false);
-      }
-    }
-    writer.append(getCloseDelimiter(boundary));
-
-    return writer;
-  }
-
-  private void appendChangeSet(ODataResponsePart part, ResponseWriter writer) throws IOException, BatchException {
-    final String changeSetBoundary = generateBoundary("changeset");
-
-    appendChangeSetHeader(writer, changeSetBoundary);
-    writer.append(CRLF);
-
-    for (final ODataResponse response : part.getResponses()) {
-      writer.append(getDashBoundary(changeSetBoundary));
-      appendBodyPart(response, writer, true);
-    }
-
-    writer.append(getCloseDelimiter(changeSetBoundary));
-    writer.append(CRLF);
-  }
-
-  private void appendBodyPart(ODataResponse response, ResponseWriter writer, boolean isChangeSet) throws IOException,
-      BatchException {
-    byte[] body = getBody(response);
-    
-    appendBodyPartHeader(response, writer, isChangeSet);
-    writer.append(CRLF);
-
-    appendStatusLine(response, writer);
-    appendResponseHeader(response, body.length, writer);
-    writer.append(CRLF);
-
-    writer.append(body);
-    writer.append(CRLF);
-  }
-
-  private byte[] getBody(final ODataResponse response) throws IOException {
-    final InputStream content = response.getContent();
-    final ByteArrayOutputStream out = new ByteArrayOutputStream();
-    
-    if (content != null) {
-      byte[] buffer = new byte[BUFFER_SIZE];
-      int n;
-
-      while ((n = content.read(buffer, 0, buffer.length)) != -1) {
-        out.write(buffer, 0, n);
-      }
-      out.flush();
-      
-      return out.toByteArray();
-    } else {
-      return new byte[0];
-    }
-  }
-
-  private void appendChangeSetHeader(ResponseWriter writer, final String changeSetBoundary) throws IOException {
-    appendHeader(HttpHeader.CONTENT_TYPE, HttpContentType.MULTIPART_MIXED.toString() + "; boundary="
-        + changeSetBoundary, writer);
-  }
-
-  private void appendHeader(String name, String value, ResponseWriter writer) throws IOException {
-    writer.append(name)
-        .append(COLON)
-        .append(SP)
-        .append(value)
-        .append(CRLF);
-  }
-
-  private void appendStatusLine(ODataResponse response, ResponseWriter writer) throws IOException {
-    writer.append("HTTP/1.1")
-        .append(SP)
-        .append("" + response.getStatusCode())
-        .append(SP)
-        .append(HttpStatusCode.fromStatusCode(response.getStatusCode()).toString())
-        .append(CRLF);
-  }
-
-  private void appendResponseHeader(ODataResponse response, int contentLength, ResponseWriter writer)
-      throws IOException {
-    final Map<String, String> header = response.getHeaders();
-
-    for (final String key : header.keySet()) {
-      // Requests do never has a content id header
-      if (!key.equalsIgnoreCase(BatchParserCommon.HTTP_CONTENT_ID)) {
-        appendHeader(key, header.get(key), writer);
-      }
-    }
-
-    appendHeader(HttpHeader.CONTENT_LENGTH, "" + contentLength, writer);
-  }
-
-  private void appendBodyPartHeader(ODataResponse response, ResponseWriter writer, boolean isChangeSet)
-      throws BatchException, IOException {
-    appendHeader(HttpHeader.CONTENT_TYPE, HttpContentType.APPLICATION_HTTP, writer);
-    appendHeader(BatchParserCommon.HTTP_CONTENT_TRANSFER_ENCODING, BatchParserCommon.BINARY_ENCODING, writer);
-
-    if (isChangeSet) {
-      if (response.getHeaders().get(BatchParserCommon.HTTP_CONTENT_ID) != null) {
-        appendHeader(BatchParserCommon.HTTP_CONTENT_ID, response.getHeaders().get(BatchParserCommon.HTTP_CONTENT_ID),
-            writer);
-      } else {
-        throw new BatchException("Missing content id", MessageKeys.MISSING_CONTENT_ID, "");
-      }
-    }
-  }
-
-  private void setHttpHeader(ODataResponse response, ResponseWriter writer, final String boundary) {
-    response.setHeader(HttpHeader.CONTENT_TYPE, HttpContentType.MULTIPART_MIXED.toString() + "; boundary=" + boundary);
-    response.setHeader(HttpHeader.CONTENT_LENGTH, "" + writer.length());
-  }
-
-  private void setStatusCode(final ODataResponse response) {
-    response.setStatusCode(HttpStatusCode.ACCEPTED.getStatusCode());
-  }
-
-  private String getDashBoundary(String boundary) {
-    return DOUBLE_DASH + boundary + CRLF;
-  }
-
-  private String getCloseDelimiter(final String boundary) {
-    return DOUBLE_DASH + boundary + DOUBLE_DASH + CRLF;
-  }
-
-  private String generateBoundary(final String value) {
-    return value + "_" + UUID.randomUUID().toString();
-  }
-
-  private static class ResponseWriter {
-    private CircleStreamBuffer buffer = new CircleStreamBuffer();
-    private BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(buffer.getOutputStream()));
-    private int length = 0;
-
-    public ResponseWriter append(final String content) throws IOException {
-      length += content.length();
-      writer.write(content);
-      
-      return this;
-    }
-
-    public ResponseWriter append(final byte[] content) throws IOException {
-      length += content.length;
-      writer.flush();
-      buffer.getOutputStream().write(content, 0, content.length);
-      
-      return this;
-    }
-
-    public int length() {
-      return length;
-    }
-
-    public InputStream toInputStream() throws IOException {
-      writer.flush();
-      writer.close();
-
-      return buffer.getInputStream();
-    }
-  }
-}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/main/java/org/apache/olingo/server/core/batchhandler/BatchChangeSetSorter.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batchhandler/BatchChangeSetSorter.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batchhandler/BatchChangeSetSorter.java
new file mode 100644
index 0000000..6c63606
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batchhandler/BatchChangeSetSorter.java
@@ -0,0 +1,156 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.batchhandler;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.olingo.server.api.ODataRequest;
+import org.apache.olingo.server.api.batch.BatchException;
+import org.apache.olingo.server.api.batch.BatchException.MessageKeys;
+import org.apache.olingo.server.core.deserializer.batch.BatchParserCommon;
+
+public class BatchChangeSetSorter {
+  private static final String REG_EX_REFERENCE = "\\$(.*)(/.*)?";
+
+  final List<ODataRequest> orderdList = new ArrayList<ODataRequest>();
+
+  private static Pattern referencePattern = Pattern.compile(REG_EX_REFERENCE);
+  private Set<String> knownContentIds = new HashSet<String>();
+  private Map<String, List<ODataRequest>> requestReferenceMapping = new HashMap<String, List<ODataRequest>>();
+
+  public BatchChangeSetSorter(List<ODataRequest> requests) throws BatchException {
+    sort(requests);
+  }
+
+  public List<ODataRequest> getOrderdRequests() {
+    return orderdList;
+  }
+
+  private List<ODataRequest> sort(final List<ODataRequest> requests) throws BatchException {
+    extractUrlReference(requests);
+    
+    // Add requests without reference (roots)
+    final List<ODataRequest> requestsWithoutReferences = getRequestsWithoutReferences();
+    orderdList.addAll(requestsWithoutReferences);
+    addRequestsToKnownContentIds(requestsWithoutReferences);
+    
+    // Find all requests which can be processed (parent request has been processed)
+    // Compare level order
+    boolean areRequestsProcessed = true;
+    while (requestsToProcessAvailable() && areRequestsProcessed) {
+      areRequestsProcessed = processNextLevel();
+    }
+
+    // Check if there are some requests which are not connected to a tree
+    if (requestsToProcessAvailable()) {
+      throw new BatchException("Invalid content id", MessageKeys.INVALID_CONTENT_ID, 0);
+    }
+
+    return orderdList;
+  }
+
+  private boolean requestsToProcessAvailable() {
+    return requestReferenceMapping.keySet().size() != 0;
+  }
+
+  private boolean processNextLevel() {
+    final List<ODataRequest> addedRequests = getRemainingRequestsWithKownContentId();
+    addRequestsToKnownContentIds(addedRequests);
+    orderdList.addAll(addedRequests);
+
+    return addedRequests.size() != 0;
+  }
+
+  private List<ODataRequest> getRemainingRequestsWithKownContentId() {
+    List<ODataRequest> result = new ArrayList<ODataRequest>();
+
+    for (String contextId : knownContentIds) {
+      final List<ODataRequest> requestsToProcess = requestReferenceMapping.get(contextId);
+      if (requestsToProcess != null && requestsToProcess.size() != 0) {
+        result.addAll(requestsToProcess);
+        requestReferenceMapping.remove(contextId);
+      }
+    }
+
+    return result;
+  }
+
+  private List<ODataRequest> getRequestsWithoutReferences() {
+    List<ODataRequest> requests = requestReferenceMapping.get(null);
+    requestReferenceMapping.remove(null);
+    
+    requests = (requests == null) ? new ArrayList<ODataRequest>() : requests;
+    return requests;
+  }
+
+  private void addRequestsToKnownContentIds(List<ODataRequest> requestsWithoutReference) {
+    for (ODataRequest request : requestsWithoutReference) {
+      final String contentId = getContentIdFromHeader(request);
+      if (contentId != null) {
+        knownContentIds.add(contentId);
+      }
+    }
+  }
+
+  private String getContentIdFromHeader(ODataRequest request) {
+    return request.getHeader(BatchParserCommon.HTTP_CONTENT_ID);
+  }
+
+  private void extractUrlReference(List<ODataRequest> requests) {
+    for (ODataRequest request : requests) {
+      final String reference = getReferenceInURI(request);
+      addRequestToReferenceMapping(reference, request);
+    }
+  }
+
+  private void addRequestToReferenceMapping(final String reference, final ODataRequest request) {
+    List<ODataRequest> requestList = requestReferenceMapping.get(reference);
+    requestList = (requestList == null) ? new ArrayList<ODataRequest>() : requestList;
+
+    requestList.add(request);
+    requestReferenceMapping.put(reference, requestList);
+  }
+
+  public static String getReferenceInURI(ODataRequest request) {
+    Matcher matcher = referencePattern.matcher(removeSlash(removeSlash(request.getRawODataPath(), true), false));
+    return (matcher.matches()) ? matcher.group(1) : null;
+  }
+
+  private static String removeSlash(String rawODataPath, boolean first) {
+    final int indexOfSlash = rawODataPath.indexOf("/");
+    if(first) {
+      return (indexOfSlash == 0) ? rawODataPath.substring(1) : rawODataPath;
+    } else {
+      return (indexOfSlash != -1) ? rawODataPath.substring(0, indexOfSlash) : rawODataPath;
+    }
+  }
+
+  public static void replaceContentIdReference(ODataRequest request, String contentId, String resourceUri) {
+    final String newUri = request.getRawODataPath().replace("/$" + contentId, resourceUri);
+    request.setRawODataPath(newUri);
+    request.setRawRequestUri(request.getRawBaseUri() + "/" + newUri);
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/main/java/org/apache/olingo/server/core/batchhandler/BatchHandler.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batchhandler/BatchHandler.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batchhandler/BatchHandler.java
new file mode 100644
index 0000000..35c7d63
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batchhandler/BatchHandler.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.batchhandler;
+
+import org.apache.olingo.commons.api.http.HttpHeader;
+import org.apache.olingo.commons.api.http.HttpMethod;
+import org.apache.olingo.server.api.ODataRequest;
+import org.apache.olingo.server.api.ODataResponse;
+import org.apache.olingo.server.api.batch.BatchException;
+import org.apache.olingo.server.api.batch.BatchFacade;
+import org.apache.olingo.server.api.batch.BatchException.MessageKeys;
+import org.apache.olingo.server.api.processor.BatchProcessor;
+import org.apache.olingo.server.api.serializer.SerializerException;
+import org.apache.olingo.server.core.ODataHandler;
+import org.apache.olingo.server.core.deserializer.batch.BatchParserCommon;
+
+public class BatchHandler {
+  private final BatchProcessor batchProcessor;
+  private final ODataHandler oDataHandler;
+
+  public BatchHandler(final ODataHandler oDataHandler, final BatchProcessor batchProcessor) {
+
+    this.batchProcessor = batchProcessor;
+    this.oDataHandler = oDataHandler;
+  }
+
+  public void process(final ODataRequest request, final ODataResponse response, final boolean isStrict)
+      throws SerializerException, BatchException {
+    validateRequest(request);
+    
+    final BatchFacade operation = new BatchOperationImpl(oDataHandler, request, batchProcessor, isStrict);
+    batchProcessor.executeBatch(operation, request, response);
+  }
+
+  private void validateRequest(final ODataRequest request) throws BatchException {
+    validateHttpMethod(request);
+    validateContentType(request);
+  }
+
+  private void validateContentType(final ODataRequest request) throws BatchException {
+    final String contentType = request.getHeader(HttpHeader.CONTENT_TYPE);
+
+    if (contentType == null || !BatchParserCommon.PATTERN_MULTIPART_BOUNDARY.matcher(contentType).matches()) {
+      throw new BatchException("Invalid content type", MessageKeys.INVALID_CONTENT_TYPE, 0);
+    }
+  }
+
+  private void validateHttpMethod(final ODataRequest request) throws BatchException {
+    if (request.getMethod() != HttpMethod.POST) {
+      throw new BatchException("Invalid HTTP method", MessageKeys.INVALID_METHOD, 0);
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/main/java/org/apache/olingo/server/core/batchhandler/BatchOperationImpl.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batchhandler/BatchOperationImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batchhandler/BatchOperationImpl.java
new file mode 100644
index 0000000..281ccba
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batchhandler/BatchOperationImpl.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.batchhandler;
+
+import org.apache.olingo.server.api.ODataRequest;
+import org.apache.olingo.server.api.ODataResponse;
+import org.apache.olingo.server.api.batch.BatchException;
+import org.apache.olingo.server.api.batch.BatchFacade;
+import org.apache.olingo.server.api.deserializer.batch.BatchRequestPart;
+import org.apache.olingo.server.api.deserializer.batch.ODataResponsePart;
+import org.apache.olingo.server.api.processor.BatchProcessor;
+import org.apache.olingo.server.core.ODataHandler;
+
+public class BatchOperationImpl implements BatchFacade {
+  private final BatchPartHandler partHandler;
+
+  public BatchOperationImpl(ODataHandler oDataHandler, ODataRequest request, BatchProcessor batchProcessor,
+      final boolean isStrict) {
+    partHandler = new BatchPartHandler(oDataHandler, batchProcessor, this);
+  }
+
+  @Override
+  public ODataResponse handleODataRequest(ODataRequest request, BatchRequestPart requestPart) throws BatchException {
+    return partHandler.handleODataRequest(request, requestPart);
+  }
+
+  @Override
+  public ODataResponsePart handleBatchRequest(BatchRequestPart request) throws BatchException {
+    return partHandler.handleBatchRequest(request);
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/main/java/org/apache/olingo/server/core/batchhandler/BatchPartHandler.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batchhandler/BatchPartHandler.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batchhandler/BatchPartHandler.java
new file mode 100644
index 0000000..71b5870
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batchhandler/BatchPartHandler.java
@@ -0,0 +1,150 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.batchhandler;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.olingo.commons.api.ODataRuntimeException;
+import org.apache.olingo.commons.api.http.HttpHeader;
+import org.apache.olingo.commons.api.http.HttpMethod;
+import org.apache.olingo.server.api.ODataRequest;
+import org.apache.olingo.server.api.ODataResponse;
+import org.apache.olingo.server.api.batch.BatchException;
+import org.apache.olingo.server.api.batch.BatchFacade;
+import org.apache.olingo.server.api.deserializer.batch.BatchRequestPart;
+import org.apache.olingo.server.api.deserializer.batch.ODataResponsePart;
+import org.apache.olingo.server.api.processor.BatchProcessor;
+import org.apache.olingo.server.core.ODataHandler;
+import org.apache.olingo.server.core.deserializer.batch.BatchParserCommon;
+import org.apache.olingo.server.core.deserializer.batch.HttpRequestStatusLine.ODataURI;
+
+public class BatchPartHandler {
+
+  private ODataHandler oDataHandler;
+  private BatchProcessor batchProcessor;
+  private BatchFacade batchOperation;
+  private Map<BatchRequestPart, UriMapping> uriMapping = new HashMap<BatchRequestPart, UriMapping>();
+
+  public BatchPartHandler(final ODataHandler oDataHandler, final BatchProcessor processor,
+      final BatchFacade batchOperation) {
+    this.oDataHandler = oDataHandler;
+    this.batchProcessor = processor;
+    this.batchOperation = batchOperation;
+  }
+
+  public ODataResponse handleODataRequest(ODataRequest request, BatchRequestPart requestPart) throws BatchException {
+    final ODataResponse response;
+
+    if (requestPart.isChangeSet()) {
+      final UriMapping mapping = replaceReference(request, requestPart);
+
+      response = oDataHandler.process(request);
+
+      // Store resource URI
+      final String resourceUri = getODataPath(request, response);
+      final String contentId = request.getHeader(BatchParserCommon.HTTP_CONTENT_ID);
+
+      mapping.addMapping(contentId, resourceUri);
+    } else {
+      response = oDataHandler.process(request);
+    }
+
+    // Add content id to response
+    final String contentId = request.getHeader(BatchParserCommon.HTTP_CONTENT_ID);
+    if (contentId != null) {
+      response.setHeader(BatchParserCommon.HTTP_CONTENT_ID, contentId);
+    }
+
+    return response;
+  }
+
+  private String getODataPath(ODataRequest request, ODataResponse response) throws BatchException {
+    String resourceUri = null;
+
+    if (request.getMethod() == HttpMethod.POST) {
+      // Create entity
+      // The URI of the new resource will be generated by the server and published in the location header
+      ODataURI uri = new ODataURI(response.getHeaders().get(HttpHeader.LOCATION), request.getRawBaseUri());
+      resourceUri = uri.getRawODataPath();
+    } else {
+      // Update, Upsert (PUT, PATCH, Delete)
+      // These methods still addresses a given resource, so we use the URI given by the request
+      resourceUri = request.getRawODataPath();
+    }
+
+    return resourceUri;
+  }
+
+  private UriMapping replaceReference(ODataRequest request, BatchRequestPart requestPart) {
+    final UriMapping mapping = getUriMappingOrDefault(requestPart);
+    final String reference = BatchChangeSetSorter.getReferenceInURI(request);
+
+    if (reference != null) {
+      final String replacement = mapping.getUri(reference);
+
+      if (replacement != null) {
+        BatchChangeSetSorter.replaceContentIdReference(request, reference, replacement);
+      } else {
+        throw new ODataRuntimeException("Required Content-Id for reference \"" + reference + "\" not found.");
+      }
+    }
+
+    return mapping;
+  }
+
+  private UriMapping getUriMappingOrDefault(final BatchRequestPart requestPart) {
+    UriMapping mapping = uriMapping.get(requestPart);
+
+    if (mapping == null) {
+      mapping = new UriMapping();
+    }
+    uriMapping.put(requestPart, mapping);
+
+    return mapping;
+  }
+
+  public ODataResponsePart handleBatchRequest(BatchRequestPart request) throws BatchException {
+    if (request.isChangeSet()) {
+      return handleChangeSet(request);
+    } else {
+      final ODataResponse response = handleODataRequest(request.getRequests().get(0), request);
+      
+      return new ODataResponsePart(response, false);
+    }
+  }
+
+  private ODataResponsePart handleChangeSet(BatchRequestPart request) throws BatchException {
+    final BatchChangeSetSorter sorter = new BatchChangeSetSorter(request.getRequests());
+
+    return batchProcessor.executeChangeSet(batchOperation, sorter.getOrderdRequests(), request);
+  }
+
+  private static class UriMapping {
+    private Map<String, String> uriMapping = new HashMap<String, String>();
+
+    public void addMapping(final String contentId, final String uri) {
+      uriMapping.put(contentId, uri);
+    }
+
+    public String getUri(final String contentId) {
+      return uriMapping.get(contentId);
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/FixedFormatDeserializerImpl.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/FixedFormatDeserializerImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/FixedFormatDeserializerImpl.java
new file mode 100644
index 0000000..8ab3950
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/FixedFormatDeserializerImpl.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.deserializer;
+
+import java.util.List;
+
+import org.apache.olingo.commons.api.http.HttpHeader;
+import org.apache.olingo.server.api.ODataRequest;
+import org.apache.olingo.server.api.batch.BatchException;
+import org.apache.olingo.server.api.deserializer.FixedFormatDeserializer;
+import org.apache.olingo.server.api.deserializer.batch.BatchRequestPart;
+import org.apache.olingo.server.core.deserializer.batch.BatchParser;
+
+public class FixedFormatDeserializerImpl implements FixedFormatDeserializer {
+
+  // TODO: Deserializer
+  @Override
+  public List<BatchRequestPart> parseBatchRequest(ODataRequest request, boolean isStrict) throws BatchException {
+    BatchParser parser = new BatchParser();
+    return parser.parseBatchRequest(request.getBody(), getContentType(request), request.getRawBaseUri(),
+        request.getRawServiceResolutionUri(), isStrict);
+  }
+
+  private String getContentType(ODataRequest request) {
+    return request.getHeader(HttpHeader.CONTENT_TYPE);
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchBodyPart.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchBodyPart.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchBodyPart.java
new file mode 100644
index 0000000..ae00891
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchBodyPart.java
@@ -0,0 +1,135 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.deserializer.batch;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.olingo.commons.api.http.HttpHeader;
+import org.apache.olingo.server.api.batch.BatchException;
+import org.apache.olingo.server.core.deserializer.batch.BufferedReaderIncludingLineEndings.Line;
+
+public class BatchBodyPart implements BatchPart {
+  final private String boundary;
+  final private boolean isStrict;
+  final List<Line> remainingMessage = new LinkedList<Line>();
+
+  private Header headers;
+  private boolean isChangeSet;
+  private List<BatchQueryOperation> requests;
+
+  public BatchBodyPart(final List<Line> message, final String boundary, final boolean isStrict) {
+    this.boundary = boundary;
+    this.isStrict = isStrict;
+    remainingMessage.addAll(message);
+  }
+
+  public BatchBodyPart parse() throws BatchException {
+    headers = BatchParserCommon.consumeHeaders(remainingMessage);
+    BatchParserCommon.consumeBlankLine(remainingMessage, isStrict);
+    isChangeSet = isChangeSet(headers);
+    requests = consumeRequest(remainingMessage);
+
+    return this;
+  }
+
+  private boolean isChangeSet(final Header header) throws BatchException {
+    final List<String> contentTypes = headers.getHeaders(HttpHeader.CONTENT_TYPE);
+    boolean isChangeSet = false;
+
+    if (contentTypes.size() == 0) {
+      throw new BatchException("Missing content type", BatchException.MessageKeys.MISSING_CONTENT_TYPE, ""
+          + headers.getLineNumber());
+    }
+
+    for (String contentType : contentTypes) {
+      if (isContentTypeMultiPartMixed(contentType)) {
+        isChangeSet = true;
+      }
+    }
+
+    return isChangeSet;
+  }
+
+  private List<BatchQueryOperation> consumeRequest(final List<Line> remainingMessage) throws BatchException {
+    if (isChangeSet) {
+      return consumeChangeSet(remainingMessage);
+    } else {
+      return consumeQueryOperation(remainingMessage);
+    }
+  }
+
+  private List<BatchQueryOperation> consumeChangeSet(final List<Line> remainingMessage2) throws BatchException {
+    final List<List<Line>> changeRequests = splitChangeSet(remainingMessage);
+    final List<BatchQueryOperation> requestList = new LinkedList<BatchQueryOperation>();
+
+    for (List<Line> changeRequest : changeRequests) {
+      requestList.add(new BatchChangeSetPart(changeRequest, isStrict).parse());
+    }
+
+    return requestList;
+  }
+
+  private List<List<Line>> splitChangeSet(final List<Line> remainingMessage2) throws BatchException {
+
+    final HeaderField contentTypeField = headers.getHeaderField(HttpHeader.CONTENT_TYPE);
+    final String changeSetBoundary = BatchParserCommon.getBoundary(contentTypeField.getValueNotNull(),
+        contentTypeField.getLineNumber());
+    validateChangeSetBoundary(changeSetBoundary, headers);
+
+    return BatchParserCommon.splitMessageByBoundary(remainingMessage, changeSetBoundary);
+  }
+
+  private void validateChangeSetBoundary(final String changeSetBoundary, final Header header) throws BatchException {
+    if (changeSetBoundary.equals(boundary)) {
+      throw new BatchException("Change set boundary is equals to batch request boundary",
+          BatchException.MessageKeys.INVALID_BOUNDARY,
+          "" + header.getHeaderField(HttpHeader.CONTENT_TYPE).getLineNumber());
+    }
+  }
+
+  private List<BatchQueryOperation> consumeQueryOperation(final List<Line> remainingMessage) throws BatchException {
+    final List<BatchQueryOperation> requestList = new LinkedList<BatchQueryOperation>();
+    requestList.add(new BatchQueryOperation(remainingMessage, isStrict).parse());
+
+    return requestList;
+  }
+
+  private boolean isContentTypeMultiPartMixed(final String contentType) {
+    return BatchParserCommon.PATTERN_MULTIPART_BOUNDARY.matcher(contentType).matches();
+  }
+
+  @Override
+  public Header getHeaders() {
+    return headers;
+  }
+
+  @Override
+  public boolean isStrict() {
+    return isStrict;
+  }
+
+  public boolean isChangeSet() {
+    return isChangeSet;
+  }
+
+  public List<BatchQueryOperation> getRequests() {
+    return requests;
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchChangeSetPart.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchChangeSetPart.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchChangeSetPart.java
new file mode 100644
index 0000000..a2f8da5
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchChangeSetPart.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.deserializer.batch;
+
+import java.util.List;
+
+import org.apache.olingo.server.api.batch.BatchException;
+import org.apache.olingo.server.core.deserializer.batch.BufferedReaderIncludingLineEndings.Line;
+
+public class BatchChangeSetPart extends BatchQueryOperation {
+  private BatchQueryOperation request;
+
+  public BatchChangeSetPart(final List<Line> message, final boolean isStrict) throws BatchException {
+    super(message, isStrict);
+  }
+
+  @Override
+  public BatchChangeSetPart parse() throws BatchException {
+    headers = BatchParserCommon.consumeHeaders(message);
+    BatchParserCommon.consumeBlankLine(message, isStrict);
+
+    request = new BatchQueryOperation(message, isStrict).parse();
+
+    return this;
+  }
+
+  public BatchQueryOperation getRequest() {
+    return request;
+  }
+
+  @Override
+  public List<Line> getBody() {
+    return request.getBody();
+  }
+
+  @Override
+  public Line getHttpStatusLine() {
+    return request.getHttpStatusLine();
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchParser.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchParser.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchParser.java
new file mode 100644
index 0000000..675966a
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchParser.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.deserializer.batch;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.olingo.commons.api.ODataRuntimeException;
+import org.apache.olingo.server.api.batch.BatchException;
+import org.apache.olingo.server.api.deserializer.batch.BatchDeserializerResult;
+import org.apache.olingo.server.api.deserializer.batch.BatchRequestPart;
+import org.apache.olingo.server.core.deserializer.batch.BufferedReaderIncludingLineEndings.Line;
+
+public class BatchParser {
+
+  private String contentTypeMime;
+  private String rawServiceResolutionUri;
+  private boolean isStrict;
+
+  @SuppressWarnings("unchecked")
+  public List<BatchRequestPart> parseBatchRequest(final InputStream in, final String contentType, final String baseUri,
+      final String serviceResolutionUri, final boolean isStrict) throws BatchException {
+
+    contentTypeMime = contentType;
+    this.isStrict = isStrict;
+    this.rawServiceResolutionUri = serviceResolutionUri;
+
+    return (List<BatchRequestPart>) parse(in, new BatchRequestTransformator(baseUri, rawServiceResolutionUri));
+  }
+
+  private List<? extends BatchDeserializerResult> parse(final InputStream in,
+      final BatchRequestTransformator transformator)
+      throws BatchException {
+    try {
+      return parseBatch(in, transformator);
+    } catch (IOException e) {
+      throw new ODataRuntimeException(e);
+    } finally {
+      try {
+        in.close();
+      } catch (IOException e) {
+        throw new ODataRuntimeException(e);
+      }
+    }
+  }
+
+  private List<BatchDeserializerResult> parseBatch(final InputStream in, final BatchRequestTransformator transformator)
+      throws IOException, BatchException {
+    final String boundary = BatchParserCommon.getBoundary(contentTypeMime, 1);
+    final List<BatchDeserializerResult> resultList = new LinkedList<BatchDeserializerResult>();
+    final List<List<Line>> bodyPartStrings = splitBodyParts(in, boundary);
+
+    for (List<Line> bodyPartString : bodyPartStrings) {
+      BatchBodyPart bodyPart = new BatchBodyPart(bodyPartString, boundary, isStrict).parse();
+      resultList.addAll(transformator.transform(bodyPart));
+    }
+
+    return resultList;
+  }
+
+  private List<List<Line>> splitBodyParts(final InputStream in, final String boundary) throws IOException,
+      BatchException {
+    final BufferedReaderIncludingLineEndings reader = new BufferedReaderIncludingLineEndings(new InputStreamReader(in));
+    final List<Line> message = reader.toLineList();
+    reader.close();
+
+    return BatchParserCommon.splitMessageByBoundary(message, boundary);
+  }
+}


[09/22] olingo-odata4 git commit: Batch handler clean up

Posted by ch...@apache.org.
Batch handler clean up

Signed-off-by: Christian Amend <ch...@apache.org>


Project: http://git-wip-us.apache.org/repos/asf/olingo-odata4/repo
Commit: http://git-wip-us.apache.org/repos/asf/olingo-odata4/commit/6c7d11f4
Tree: http://git-wip-us.apache.org/repos/asf/olingo-odata4/tree/6c7d11f4
Diff: http://git-wip-us.apache.org/repos/asf/olingo-odata4/diff/6c7d11f4

Branch: refs/heads/master
Commit: 6c7d11f4d77cb3d95684543b79c039c11990c63a
Parents: 0bd3295
Author: Christian Holzer <c....@sap.com>
Authored: Wed Nov 5 16:02:12 2014 +0100
Committer: Christian Amend <ch...@apache.org>
Committed: Thu Nov 13 17:10:57 2014 +0100

----------------------------------------------------------------------
 .../apache/olingo/server/core/ODataHandler.java |  6 +-
 .../batch/handler/BatchChangeSetSorter.java     | 35 ++++++------
 .../server/core/batch/handler/BatchHandler.java | 40 +++++++-------
 .../core/batch/handler/BatchPartHandler.java    | 17 +++++-
 .../server/core/batch/handler/UriMapping.java   | 34 ------------
 .../batch/handler/MockedBatchHandlerTest.java   | 58 ++++++++------------
 6 files changed, 79 insertions(+), 111 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6c7d11f4/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataHandler.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataHandler.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataHandler.java
index 48d75de..af092cf 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataHandler.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataHandler.java
@@ -162,10 +162,10 @@ public class ODataHandler {
       handleResourceDispatching(request, response);
       break;
     case batch:
-      BatchProcessor bp = selectProcessor(BatchProcessor.class);
+      final BatchProcessor bp = selectProcessor(BatchProcessor.class);
+      final BatchHandler handler = new BatchHandler(this, bp);
       
-      final BatchHandler handler = new BatchHandler(this, request, bp, true);
-      handler.process(response);
+      handler.process(request, response, true);
       
       break;
     default:

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6c7d11f4/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchChangeSetSorter.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchChangeSetSorter.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchChangeSetSorter.java
index f8ac653..408159e 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchChangeSetSorter.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchChangeSetSorter.java
@@ -38,7 +38,7 @@ public class BatchChangeSetSorter {
   final List<ODataRequest> orderdList = new ArrayList<ODataRequest>();
 
   private static Pattern referencePattern = Pattern.compile(REG_EX_REFERENCE);
-  private Set<String> knownContentId = new HashSet<String>();
+  private Set<String> knownContentIds = new HashSet<String>();
   private Map<String, List<ODataRequest>> requestReferenceMapping = new HashMap<String, List<ODataRequest>>();
 
   public BatchChangeSetSorter(List<ODataRequest> requests) throws BatchException {
@@ -52,15 +52,19 @@ public class BatchChangeSetSorter {
   private List<ODataRequest> sort(final List<ODataRequest> requests) throws BatchException {
     extractUrlReference(requests);
     
+    // Add requests without reference (roots)
     final List<ODataRequest> requestsWithoutReferences = getRequestsWithoutReferences();
     orderdList.addAll(requestsWithoutReferences);
     addRequestsToKnownContentIds(requestsWithoutReferences);
     
+    // Find all requests which can be processed (parent request has been processed)
+    // Compare level order
     boolean areRequestsProcessed = true;
     while (requestsToProcessAvailable() && areRequestsProcessed) {
-      areRequestsProcessed = processRemainingRequests(orderdList);
+      areRequestsProcessed = processNextLevel();
     }
 
+    // Check if there are some requests which are not connected to a tree
     if (requestsToProcessAvailable()) {
       throw new BatchException("Invalid content id", MessageKeys.INVALID_CONTENT_ID, 0);
     }
@@ -72,7 +76,7 @@ public class BatchChangeSetSorter {
     return requestReferenceMapping.keySet().size() != 0;
   }
 
-  private boolean processRemainingRequests(List<ODataRequest> orderdList) {
+  private boolean processNextLevel() {
     final List<ODataRequest> addedRequests = getRemainingRequestsWithKownContentId();
     addRequestsToKnownContentIds(addedRequests);
     orderdList.addAll(addedRequests);
@@ -83,10 +87,10 @@ public class BatchChangeSetSorter {
   private List<ODataRequest> getRemainingRequestsWithKownContentId() {
     List<ODataRequest> result = new ArrayList<ODataRequest>();
 
-    for (String contextId : knownContentId) {
-      List<ODataRequest> processedRequests = requestReferenceMapping.get(contextId);
-      if (processedRequests != null && processedRequests.size() != 0) {
-        result.addAll(processedRequests);
+    for (String contextId : knownContentIds) {
+      final List<ODataRequest> requestsToProcess = requestReferenceMapping.get(contextId);
+      if (requestsToProcess != null && requestsToProcess.size() != 0) {
+        result.addAll(requestsToProcess);
         requestReferenceMapping.remove(contextId);
       }
     }
@@ -105,7 +109,7 @@ public class BatchChangeSetSorter {
     for (ODataRequest request : requestsWithoutReference) {
       final String contentId = getContentIdFromHeader(request);
       if (contentId != null) {
-        knownContentId.add(contentId);
+        knownContentIds.add(contentId);
       }
     }
   }
@@ -130,18 +134,17 @@ public class BatchChangeSetSorter {
   }
 
   public static String getReferenceInURI(ODataRequest request) {
-    Matcher matcher = referencePattern.matcher(removeFollingPathSegments(removeFirstSplash(request.getRawODataPath())));
+    Matcher matcher = referencePattern.matcher(removeSlash(removeSlash(request.getRawODataPath(), true), false));
     return (matcher.matches()) ? matcher.group(1) : null;
   }
 
-  private static String removeFirstSplash(String rawODataPath) {
+  private static String removeSlash(String rawODataPath, boolean first) {
     final int indexOfSlash = rawODataPath.indexOf("/");
-    return (indexOfSlash == 0) ? rawODataPath.substring(1) : rawODataPath;
-  }
-
-  private static String removeFollingPathSegments(String rawODataPath) {
-    final int indexOfSlash = rawODataPath.indexOf("/");
-    return (indexOfSlash != -1) ? rawODataPath.substring(0, indexOfSlash) : rawODataPath;
+    if(first) {
+      return (indexOfSlash == 0) ? rawODataPath.substring(1) : rawODataPath;
+    } else {
+      return (indexOfSlash != -1) ? rawODataPath.substring(0, indexOfSlash) : rawODataPath;
+    }
   }
 
   public static void replaceContentIdReference(ODataRequest request, String contentId, String resourceUri) {

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6c7d11f4/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchHandler.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchHandler.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchHandler.java
index 01a149e..df16c90 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchHandler.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchHandler.java
@@ -6,9 +6,9 @@
  * to you under the Apache License, Version 2.0 (the
  * "License"); you may not use this file except in compliance
  * with the License. You may obtain a copy of the License at
- *
+ * 
  * http://www.apache.org/licenses/LICENSE-2.0
- *
+ * 
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@@ -30,38 +30,38 @@ import org.apache.olingo.server.core.ODataHandler;
 import org.apache.olingo.server.core.batch.parser.BatchParserCommon;
 
 public class BatchHandler {
-  private final BatchOperation operation;
   private final BatchProcessor batchProcessor;
-  private ODataRequest request;
+  private final ODataHandler oDataHandler;
+
+  public BatchHandler(final ODataHandler oDataHandler, final BatchProcessor batchProcessor) {
 
-  public BatchHandler(final ODataHandler oDataHandler, final ODataRequest request, final BatchProcessor batchProcessor,
-      final boolean isStrict) {
-    
-    this.request = request;
     this.batchProcessor = batchProcessor;
-    operation = new BatchOperationImpl(oDataHandler, request, batchProcessor, isStrict);
+    this.oDataHandler = oDataHandler;
   }
 
-  public void process(ODataResponse response) throws BatchException {
-    validateRequest();
+  public void process(final ODataRequest request, final ODataResponse response, final boolean isStrict)
+      throws BatchException {
+    validateRequest(request);
+    
+    final BatchOperation operation = new BatchOperationImpl(oDataHandler, request, batchProcessor, isStrict);
     batchProcessor.executeBatch(operation, request, response);
   }
-  
-  private void validateRequest() throws BatchException {
-    validateHttpMethod();
-    validateContentType();
+
+  private void validateRequest(final ODataRequest request) throws BatchException {
+    validateHttpMethod(request);
+    validateContentType(request);
   }
 
-  private void validateContentType() throws BatchException {
+  private void validateContentType(final ODataRequest request) throws BatchException {
     final String contentType = request.getHeader(HttpHeader.CONTENT_TYPE);
-    
-    if(contentType == null || !BatchParserCommon.PATTERN_MULTIPART_BOUNDARY.matcher(contentType).matches()) {
+
+    if (contentType == null || !BatchParserCommon.PATTERN_MULTIPART_BOUNDARY.matcher(contentType).matches()) {
       throw new BatchException("Invalid content type", MessageKeys.INVALID_CONTENT_TYPE, 0);
     }
   }
 
-  private void validateHttpMethod() throws BatchException {
-    if(request.getMethod() != HttpMethod.POST) {
+  private void validateHttpMethod(final ODataRequest request) throws BatchException {
+    if (request.getMethod() != HttpMethod.POST) {
       throw new BatchException("Invalid HTTP method", MessageKeys.INVALID_METHOD, 0);
     }
   }

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6c7d11f4/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchPartHandler.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchPartHandler.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchPartHandler.java
index c678355..5bec30b 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchPartHandler.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchPartHandler.java
@@ -58,7 +58,8 @@ public class BatchPartHandler {
       final UriMapping mapping = replaceReference(request, requestPart);
 
       response = oDataHandler.process(request);
-       
+      
+      // Store resource URI
       final String resourceUri = getODataPath(request, response);
       final String contentId = request.getHeader(BatchParserCommon.HTTP_CONTENT_ID);
 
@@ -67,6 +68,7 @@ public class BatchPartHandler {
       response = oDataHandler.process(request);
     }
     
+    // Add content id to response
     final String contentId = request.getHeader(BatchParserCommon.HTTP_CONTENT_ID);
     if(contentId != null) {
       response.setHeader(BatchParserCommon.HTTP_CONTENT_ID, contentId);
@@ -85,7 +87,7 @@ public class BatchPartHandler {
       resourceUri = uri.getRawODataPath();
     } else {
       // Update, Upsert (PUT, PATCH, Delete)
-      // These methods still addresses a given URI, so we use the URI given by the request
+      // These methods still addresses a given resource, so we use the URI given by the request
       resourceUri = request.getRawODataPath();
     }
     
@@ -140,4 +142,15 @@ public class BatchPartHandler {
     return new ODataResponsePartImpl(responses, true);
   }
 
+  private static class UriMapping {
+    private Map<String, String> uriMapping = new HashMap<String, String>();
+    
+    public void addMapping(final String contentId, final String uri) {
+      uriMapping.put(contentId, uri);
+    }
+    
+    public String getUri(final String contentId) {
+      return uriMapping.get(contentId);
+    }
+  }
 }

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6c7d11f4/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/UriMapping.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/UriMapping.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/UriMapping.java
deleted file mode 100644
index 0123320..0000000
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/UriMapping.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.olingo.server.core.batch.handler;
-
-import java.util.HashMap;
-import java.util.Map;
-
-public class UriMapping {
-  private Map<String, String> uriMapping = new HashMap<String, String>();
-  
-  public void addMapping(final String contentId, final String uri) {
-    uriMapping.put(contentId, uri);
-  }
-  
-  public String getUri(final String contentId) {
-    return uriMapping.get(contentId);
-  }
-}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/6c7d11f4/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/MockedBatchHandlerTest.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/MockedBatchHandlerTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/MockedBatchHandlerTest.java
index ac111e0..d772fbc 100644
--- a/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/MockedBatchHandlerTest.java
+++ b/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/MockedBatchHandlerTest.java
@@ -61,9 +61,19 @@ public class MockedBatchHandlerTest {
   private static final String BATCH_REQUEST_URI = "http://localhost:8080/odata/$batch";
   private static final String BASE_URI = "http://localhost:8080/odata";
   private static final String CRLF = "\r\n";
-  private ODataHandler handler;
+  private ODataHandler oDataHandler;
+  private BatchHandler batchHandler;
   private int entityCounter = 1;
 
+  @Before
+  public void setup() {
+    final BatchProcessor batchProcessor = new BatchTestProcessorImpl();
+
+    entityCounter = 1;
+    oDataHandler = mock(ODataHandler.class);
+    batchHandler = new BatchHandler(oDataHandler, batchProcessor);
+  }
+
   @Test
   public void test() throws BatchException, IOException {
     final String content = "--batch_12345" + CRLF
@@ -130,9 +140,9 @@ public class MockedBatchHandlerTest {
         + "--batch_12345--";
     final Map<String, List<String>> header = getMimeHeader();
     final ODataResponse response = new ODataResponse();
-    final BatchHandler batchHandler = buildBatchHandler(content, header);
+    final ODataRequest request = buildODataRequest(content, header);
 
-    batchHandler.process(response);
+    batchHandler.process(request, response, true);
 
     BufferedReaderIncludingLineEndings reader =
         new BufferedReaderIncludingLineEndings(new InputStreamReader(response.getContent()));
@@ -244,9 +254,9 @@ public class MockedBatchHandlerTest {
         + "--batch_12345--";
     final Map<String, List<String>> header = getMimeHeader();
     final ODataResponse response = new ODataResponse();
-    final BatchHandler batchHandler = buildBatchHandler(content, header);
+    final ODataRequest request = buildODataRequest(content, header);
 
-    batchHandler.process(response);
+    batchHandler.process(request, response, true);
 
     BufferedReaderIncludingLineEndings reader =
         new BufferedReaderIncludingLineEndings(new InputStreamReader(response.getContent()));
@@ -365,9 +375,9 @@ public class MockedBatchHandlerTest {
 
     final Map<String, List<String>> header = getMimeHeader();
     final ODataResponse response = new ODataResponse();
-    final BatchHandler batchHandler = buildBatchHandler(content, header);
+    final ODataRequest request = buildODataRequest(content, header);
 
-    batchHandler.process(response);
+    batchHandler.process(request, response, true);
 
     BufferedReaderIncludingLineEndings reader =
         new BufferedReaderIncludingLineEndings(new InputStreamReader(response.getContent()));
@@ -418,12 +428,6 @@ public class MockedBatchHandlerTest {
     assertEquals(45, line);
   }
 
-  @Before
-  public void setup() {
-    handler = null;
-    entityCounter = 1;
-  }
-
   private String checkChangeSetPartHeader(final List<String> response, int line) {
     assertEquals(CRLF, response.get(line++));
     assertTrue(response.get(line++).contains("--changeset_"));
@@ -442,7 +446,6 @@ public class MockedBatchHandlerTest {
   /*
    * Helper methods
    */
-
   private Map<String, List<String>> getMimeHeader() {
     final Map<String, List<String>> header = new HashMap<String, List<String>>();
     header.put(HttpHeader.CONTENT_TYPE, Arrays.asList(new String[] { BATCH_CONTENT_TYPE }));
@@ -450,24 +453,7 @@ public class MockedBatchHandlerTest {
     return header;
   }
 
-  private BatchHandler buildBatchHandler(final String content, Map<String, List<String>> header) throws BatchException,
-      UnsupportedEncodingException {
-
-    final ODataRequest request = buildODataRequest(content, header);
-    final ODataHandler oDataHandler = buildODataHandler(request);
-    final BatchProcessor batchProcessor = new BatchProcessorImpl();
-
-    return new BatchHandler(oDataHandler, request, batchProcessor, true);
-  }
-
-  private ODataHandler buildODataHandler(ODataRequest request) {
-    handler = mock(ODataHandler.class);
-    when(handler.process(request)).thenCallRealMethod();
-
-    return handler;
-  }
-
-  private ODataRequest buildODataRequest(String content, Map<String, List<String>> header)
+  private ODataRequest buildODataRequest(final String content, final Map<String, List<String>> header)
       throws UnsupportedEncodingException {
     final ODataRequest request = new ODataRequest();
 
@@ -490,7 +476,7 @@ public class MockedBatchHandlerTest {
   /**
    * Batch processor
    */
-  private class BatchProcessorImpl implements BatchProcessor {
+  private class BatchTestProcessorImpl implements BatchProcessor {
     @Override
     public void init(OData odata, ServiceMetadata serviceMetadata) {}
 
@@ -500,8 +486,8 @@ public class MockedBatchHandlerTest {
       List<ODataResponse> responses = new ArrayList<ODataResponse>();
 
       for (ODataRequest request : requests) {
-        // Mock the processor of the changeset requests
-        when(handler.process(request)).then(new Answer<ODataResponse>() {
+        // Mock the processor for a given requests
+        when(oDataHandler.process(request)).then(new Answer<ODataResponse>() {
           @Override
           public ODataResponse answer(InvocationOnMock invocation) throws Throwable {
             Object[] arguments = invocation.getArguments();
@@ -565,7 +551,7 @@ public class MockedBatchHandlerTest {
       // Entity Collection
       oDataPath = parts[1];
     } else {
-      // Navigationproperty
+      // Navigation property
 
       final String navProperty = parts[parts.length - 1];
       if (navProperty.equals("NavPropertyETTwoPrimMany")) {


[12/22] olingo-odata4 git commit: Batch IT test case

Posted by ch...@apache.org.
Batch IT test case

Signed-off-by: Christian Amend <ch...@apache.org>


Project: http://git-wip-us.apache.org/repos/asf/olingo-odata4/repo
Commit: http://git-wip-us.apache.org/repos/asf/olingo-odata4/commit/4ff5fb9c
Tree: http://git-wip-us.apache.org/repos/asf/olingo-odata4/tree/4ff5fb9c
Diff: http://git-wip-us.apache.org/repos/asf/olingo-odata4/diff/4ff5fb9c

Branch: refs/heads/master
Commit: 4ff5fb9c8ce045e87657dec69f76db52001f1508
Parents: 5f4eb03
Author: Christian Holzer <c....@sap.com>
Authored: Mon Nov 10 17:30:18 2014 +0100
Committer: Christian Amend <ch...@apache.org>
Committed: Thu Nov 13 17:11:00 2014 +0100

----------------------------------------------------------------------
 .../fit/tecsvc/client/BatchClientITCase.java    | 459 +++++++++++++++++++
 .../server/api/batch/ODataResponsePart.java     |  52 ++-
 .../server/api/processor/BatchProcessor.java    |   3 +-
 .../server/api/processor/DefaultProcessor.java  |  93 +++-
 .../core/batch/handler/BatchPartHandler.java    |   7 +-
 .../batch/handler/ODataResponsePartImpl.java    |  44 --
 .../core/batch/parser/BatchParserCommon.java    |   8 -
 .../core/batch/writer/BatchResponseWriter.java  |   2 +-
 .../batch/writer/ODataResponsePartImpl.java     |  44 --
 .../core/batch/BatchRequestParserTest.java      |  28 +-
 .../batch/handler/MockedBatchHandlerTest.java   |   4 +-
 .../batch/writer/BatchResponseWriterTest.java   |   8 +-
 12 files changed, 627 insertions(+), 125 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/4ff5fb9c/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/BatchClientITCase.java
----------------------------------------------------------------------
diff --git a/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/BatchClientITCase.java b/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/BatchClientITCase.java
new file mode 100644
index 0000000..f2e1ab6
--- /dev/null
+++ b/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/BatchClientITCase.java
@@ -0,0 +1,459 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.fit.tecsvc.client;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import java.math.BigDecimal;
+import java.net.URI;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.Iterator;
+
+import org.apache.olingo.client.api.ODataBatchConstants;
+import org.apache.olingo.client.api.communication.request.batch.BatchManager;
+import org.apache.olingo.client.api.communication.request.batch.ODataBatchResponseItem;
+import org.apache.olingo.client.api.communication.request.batch.ODataChangeset;
+import org.apache.olingo.client.api.communication.request.batch.v4.ODataBatchRequest;
+import org.apache.olingo.client.api.communication.request.cud.ODataEntityCreateRequest;
+import org.apache.olingo.client.api.communication.request.cud.ODataEntityUpdateRequest;
+import org.apache.olingo.client.api.communication.request.cud.v4.UpdateType;
+import org.apache.olingo.client.api.communication.request.retrieve.ODataEntityRequest;
+import org.apache.olingo.client.api.communication.request.retrieve.ODataEntitySetRequest;
+import org.apache.olingo.client.api.communication.response.ODataBatchResponse;
+import org.apache.olingo.client.api.communication.response.ODataEntityCreateResponse;
+import org.apache.olingo.client.api.communication.response.ODataEntityUpdateResponse;
+import org.apache.olingo.client.api.communication.response.ODataResponse;
+import org.apache.olingo.client.api.uri.v4.URIBuilder;
+import org.apache.olingo.client.core.communication.request.batch.ODataChangesetResponseItem;
+import org.apache.olingo.client.core.uri.URIUtils;
+import org.apache.olingo.commons.api.domain.v4.ODataEntity;
+import org.apache.olingo.commons.api.domain.v4.ODataEntitySet;
+import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeException;
+import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind;
+import org.apache.olingo.commons.api.edm.FullQualifiedName;
+import org.apache.olingo.commons.api.format.ContentType;
+import org.apache.olingo.commons.api.format.ODataFormat;
+import org.apache.olingo.commons.api.http.HttpStatusCode;
+import org.apache.olingo.fit.tecsvc.TecSvcConst;
+import org.apache.olingo.fit.v4.AbstractTestITCase;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class BatchClientITCase extends AbstractTestITCase {
+  private final static String ACCEPT = ContentType.APPLICATION_OCTET_STREAM.toContentTypeString();
+  private static final String SERVICE_URI = TecSvcConst.BASE_URI;
+
+  @Before
+  public void setup() {
+    client.getConfiguration().setContinueOnError(false);
+  }
+
+  @Test
+  public void emptyBatchRequest() {
+    // create your request
+    final ODataBatchRequest request = client.getBatchRequestFactory().getBatchRequest(SERVICE_URI);
+    request.setAccept(ACCEPT);
+
+    final BatchManager payload = request.payloadManager();
+    final ODataBatchResponse response = payload.getResponse();
+
+    assertEquals(202, response.getStatusCode());
+    assertEquals("Accepted", response.getStatusMessage());
+
+    final Iterator<ODataBatchResponseItem> iter = response.getBody();
+    assertFalse(iter.hasNext());
+  }
+
+  @Test
+  public void getBatchRequest() {
+    final ODataBatchRequest request = client.getBatchRequestFactory().getBatchRequest(SERVICE_URI);
+    request.setAccept(ACCEPT);
+
+    final BatchManager payload = request.payloadManager();
+
+    // create new request
+    appendGetRequest(payload, "ESAllPrim", 32767);
+
+    // Fetch result
+    final ODataBatchResponse response = payload.getResponse();
+
+    assertEquals(202, response.getStatusCode());
+    assertEquals("Accepted", response.getStatusMessage());
+
+    final Iterator<ODataBatchResponseItem> iter = response.getBody();
+    assertTrue(iter.hasNext());
+
+    ODataBatchResponseItem item = iter.next();
+    assertFalse(item.isChangeset());
+
+    ODataResponse oDataResonse = item.next();
+    assertNotNull(oDataResonse);
+    assertEquals(HttpStatusCode.OK.getStatusCode(), oDataResonse.getStatusCode());
+    assertEquals(1, oDataResonse.getHeader("OData-Version").size());
+    assertEquals("4.0", oDataResonse.getHeader("OData-Version").toArray()[0]);
+    assertEquals(1, oDataResonse.getHeader("Content-Length").size());
+    assertEquals("538", oDataResonse.getHeader("Content-Length").toArray()[0]);
+    assertEquals("application/json;odata.metadata=minimal", oDataResonse.getContentType());
+  }
+
+  @Test
+  public void testErrorWithoutContinueOnErrorPreferHeader() {
+    final ODataBatchRequest request = client.getBatchRequestFactory().getBatchRequest(SERVICE_URI);
+    request.setAccept(ACCEPT);
+
+    final BatchManager payload = request.payloadManager();
+
+    appendGetRequest(payload, "ESAllPrim", 32767); // Without error
+    appendGetRequest(payload, "ESAllPrim", 42); // Error ( Key does not exist )
+    appendGetRequest(payload, "ESAllPrim", 0); // Without error
+
+    // Fetch result
+    final ODataBatchResponse response = payload.getResponse();
+    assertEquals(202, response.getStatusCode());
+
+    final Iterator<ODataBatchResponseItem> iter = response.getBody();
+
+    // Check first get request
+    assertTrue(iter.hasNext());
+    ODataBatchResponseItem item = iter.next();
+    assertFalse(item.isChangeset());
+
+    ODataResponse oDataResonse = item.next();
+    assertNotNull(oDataResonse);
+    assertEquals(HttpStatusCode.OK.getStatusCode(), oDataResonse.getStatusCode());
+    assertEquals(1, oDataResonse.getHeader("OData-Version").size());
+    assertEquals("4.0", oDataResonse.getHeader("OData-Version").toArray()[0]);
+    assertEquals(1, oDataResonse.getHeader("Content-Length").size());
+    assertEquals("538", oDataResonse.getHeader("Content-Length").toArray()[0]);
+    assertEquals("application/json;odata.metadata=minimal", oDataResonse.getContentType());
+
+    // Check second get request
+    assertTrue(iter.hasNext());
+    item = iter.next();
+    assertFalse(item.isChangeset());
+
+    oDataResonse = item.next();
+    assertNotNull(oDataResonse);
+    assertEquals(HttpStatusCode.NOT_FOUND.getStatusCode(), oDataResonse.getStatusCode());
+
+    // Check if third request is available
+    assertFalse(iter.hasNext());
+  }
+
+  @Test
+  public void testErrorWithContinueOnErrorPreferHeader() {
+    client.getConfiguration().setContinueOnError(true);
+
+    final ODataBatchRequest request = client.getBatchRequestFactory().getBatchRequest(SERVICE_URI);
+    request.setAccept(ACCEPT);
+
+    final BatchManager payload = request.payloadManager();
+
+    appendGetRequest(payload, "ESAllPrim", 32767); // Without error
+    appendGetRequest(payload, "ESAllPrim", 42); // Error ( Key does not exist )
+    appendGetRequest(payload, "ESAllPrim", 0); // Without error
+
+    // Fetch result
+    final ODataBatchResponse response = payload.getResponse();
+    assertEquals(202, response.getStatusCode());
+
+    final Iterator<ODataBatchResponseItem> bodyIterator = response.getBody();
+
+    // Check first get request
+    assertTrue(bodyIterator.hasNext());
+    ODataBatchResponseItem item = bodyIterator.next();
+    assertFalse(item.isChangeset());
+
+    ODataResponse oDataResonse = item.next();
+    assertNotNull(oDataResonse);
+    assertEquals(HttpStatusCode.OK.getStatusCode(), oDataResonse.getStatusCode());
+    assertEquals(1, oDataResonse.getHeader("OData-Version").size());
+    assertEquals("4.0", oDataResonse.getHeader("OData-Version").toArray()[0]);
+    assertEquals(1, oDataResonse.getHeader("Content-Length").size());
+    assertEquals("538", oDataResonse.getHeader("Content-Length").toArray()[0]);
+    assertEquals("application/json;odata.metadata=minimal", oDataResonse.getContentType());
+
+    // Check second get request
+    assertTrue(bodyIterator.hasNext());
+    item = bodyIterator.next();
+    assertFalse(item.isChangeset());
+
+    oDataResonse = item.next();
+    assertNotNull(oDataResonse);
+    assertEquals(HttpStatusCode.NOT_FOUND.getStatusCode(), oDataResonse.getStatusCode());
+
+    // Check if third request is available
+    assertTrue(bodyIterator.hasNext());
+    item = bodyIterator.next();
+    assertFalse(item.isChangeset());
+
+    oDataResonse = item.next();
+    assertNotNull(oDataResonse);
+    assertEquals(HttpStatusCode.OK.getStatusCode(), oDataResonse.getStatusCode());
+    assertEquals(1, oDataResonse.getHeader("OData-Version").size());
+    assertEquals("4.0", oDataResonse.getHeader("OData-Version").toArray()[0]);
+    assertEquals(1, oDataResonse.getHeader("Content-Length").size());
+    assertEquals("446", oDataResonse.getHeader("Content-Length").toArray()[0]);
+    assertEquals("application/json;odata.metadata=minimal", oDataResonse.getContentType());
+  }
+
+  @SuppressWarnings("unchecked")
+  @Test
+  @Ignore("Not implemented")
+  public void changesetWithReferences() throws EdmPrimitiveTypeException {
+    // create your request
+    final ODataBatchRequest request = client.getBatchRequestFactory().getBatchRequest(SERVICE_URI);
+    request.setAccept(ACCEPT);
+    final BatchManager streamManager = request.payloadManager();
+
+    final ODataChangeset changeset = streamManager.addChangeset();
+    ODataEntity esAllPrim = newESAllPrim((short) 23);
+
+    final URIBuilder uriBuilder = client.newURIBuilder(SERVICE_URI).appendEntitySetSegment("ESAllPrim");
+
+    // add create request
+    final ODataEntityCreateRequest<ODataEntity> createReq =
+        client.getCUDRequestFactory().getEntityCreateRequest(uriBuilder.build(), esAllPrim);
+
+    changeset.addRequest(createReq);
+
+    // retrieve request reference
+    int createRequestRef = changeset.getLastContentId();
+
+    // add update request
+    final ODataEntity customerChanges = client.getObjectFactory().newEntity(esAllPrim.getTypeName());
+    customerChanges.addLink(client.getObjectFactory().newEntitySetNavigationLink(
+        "NavPropertyETTwoPrimMany",
+        client.newURIBuilder(SERVICE_URI).appendEntitySetSegment("NavPropertyETTwoPrimMany").
+            appendKeySegment(new HashMap<String, Object>() {
+              private static final long serialVersionUID = 3109256773218160485L;
+
+              {
+                put("PropertyInt16", 4242);
+                put("PropertyString", "Test");
+              }
+            }).build()));
+
+    final ODataEntityUpdateRequest<ODataEntity> updateReq = client.getCUDRequestFactory().getEntityUpdateRequest(
+        URI.create("$" + createRequestRef), UpdateType.PATCH, customerChanges);
+
+    changeset.addRequest(updateReq);
+
+    final ODataBatchResponse response = streamManager.getResponse();
+    assertEquals(200, response.getStatusCode());
+    assertEquals("OK", response.getStatusMessage());
+
+    // verify response payload ...
+    final Iterator<ODataBatchResponseItem> iter = response.getBody();
+
+    final ODataBatchResponseItem item = iter.next();
+    assertTrue(item instanceof ODataChangesetResponseItem);
+
+    final ODataChangesetResponseItem chgitem = (ODataChangesetResponseItem) item;
+
+    ODataResponse res = chgitem.next();
+    assertEquals(201, res.getStatusCode());
+    assertTrue(res instanceof ODataEntityCreateResponse);
+
+    esAllPrim = ((ODataEntityCreateResponse<ODataEntity>) res).getBody();
+    final ODataEntitySetRequest<ODataEntitySet> req = client.getRetrieveRequestFactory().getEntitySetRequest(
+        URIUtils.getURI(SERVICE_URI, esAllPrim.getEditLink().toASCIIString() + "/NavPropertyETTwoPrimMany"));
+
+    assertEquals(Integer.valueOf(4242),
+        req.execute().getBody().getEntities().get(0).getProperty("PropertyInt16").getPrimitiveValue().
+            toCastValue(Integer.class));
+
+    res = chgitem.next();
+    assertEquals(204, res.getStatusCode());
+    assertTrue(res instanceof ODataEntityUpdateResponse);
+
+    // clean ...
+    assertEquals(204, client.getCUDRequestFactory().getDeleteRequest(
+        URIUtils.getURI(SERVICE_URI, esAllPrim.getEditLink().toASCIIString())).execute().
+        getStatusCode());
+
+    try {
+      client.getRetrieveRequestFactory().getEntityRequest(
+          URIUtils.getURI(SERVICE_URI, esAllPrim.getEditLink().toASCIIString())).
+          execute().getBody();
+      fail("Entity not deleted");
+    } catch (Exception e) {
+      // ignore
+    }
+  }
+
+  private ODataEntity newESAllPrim(short id) {
+    final ODataEntity entity = getClient().getObjectFactory().
+        newEntity(new FullQualifiedName("olingo.odata.test1.ESAllPrim"));
+
+    entity.getProperties().add(client.getObjectFactory().newPrimitiveProperty(
+        "PropertyInt16",
+        client.getObjectFactory().newPrimitiveValueBuilder().buildInt16(id)));
+
+    entity.getProperties().add(client.getObjectFactory().newPrimitiveProperty(
+        "PropertyDouble",
+        client.getObjectFactory().newPrimitiveValueBuilder().buildDouble(3.1415)));
+
+    return entity;
+  }
+
+  //TODO If write support is implemented, remove ignore tag
+  @Test
+  @Ignore("Not implemented")
+  public void changesetBatchRequest() {
+    final ODataBatchRequest request = client.getBatchRequestFactory().getBatchRequest(SERVICE_URI);
+    request.setAccept(ACCEPT);
+
+    final BatchManager payload = request.payloadManager();
+    // -----------------------------
+    // - Append get request
+    // -----------------------------
+    appendGetRequest(payload, "ESAllPrim", 32767); // Without error
+
+    // -----------------------------
+    // - Append change set
+    // -----------------------------
+    final ODataChangeset changeset = payload.addChangeset();
+
+    // ------------------------
+    // POST request (Insert)
+    URIBuilder targetURI =
+        client.newURIBuilder(SERVICE_URI).appendEntitySetSegment("ESAllPrim");
+    URI editLink = targetURI.build();
+
+    ODataEntity post = client.getObjectFactory().newEntity(
+        new FullQualifiedName("olingo.odata.test1.ESAllPrim"));
+
+    post.getProperties().add(client.getObjectFactory().newPrimitiveProperty(
+        "PropertyInt16",
+        client.getObjectFactory().newPrimitiveValueBuilder().buildInt16((short) 15)));
+
+    post.getProperties().add(client.getObjectFactory().newPrimitiveProperty(
+        "PropertyDouble",
+        client.getObjectFactory().newPrimitiveValueBuilder().buildDouble(3.1415)));
+
+    final ODataEntityCreateRequest<ODataEntity> createRequest =
+        client.getCUDRequestFactory().getEntityCreateRequest(editLink, post);
+    createRequest.setFormat(ODataFormat.JSON_FULL_METADATA);
+    createRequest.setContentType("1");
+
+    changeset.addRequest(createRequest);
+
+    // ------------------------
+    // Patch request (Update)
+    targetURI = client.newURIBuilder(SERVICE_URI).appendEntitySetSegment("ESAllPrim").appendKeySegment(0);
+    editLink = targetURI.build();
+
+    ODataEntity patch = client.getObjectFactory().newEntity(new FullQualifiedName("olingo.odata.test1.ESAllPrim"));
+    patch.setEditLink(editLink);
+
+    patch.getProperties().add(client.getObjectFactory().newPrimitiveProperty(
+        "PropertyDouble",
+        client.getObjectFactory().newPrimitiveValueBuilder().buildDouble(3.1415)));
+
+    ODataEntityUpdateRequest<ODataEntity> changeReq =
+        client.getCUDRequestFactory().getEntityUpdateRequest(UpdateType.PATCH, patch);
+    changeReq.setFormat(ODataFormat.JSON_FULL_METADATA);
+    changeReq.setContentType("2");
+    changeset.addRequest(changeReq);
+
+    // ------------------------
+    // Patch request (Upsert)
+    targetURI = client.newURIBuilder(SERVICE_URI).appendEntitySetSegment("ESAllPrim").appendKeySegment(35);
+    editLink = targetURI.build();
+
+    patch = client.getObjectFactory().newEntity(new FullQualifiedName("olingo.odata.test1.ESAllPrim"));
+    patch.setEditLink(editLink);
+
+    patch.getProperties().add(client.getObjectFactory().newPrimitiveProperty(
+        "PropertyDouble",
+        client.getObjectFactory().newPrimitiveValueBuilder().buildDouble(3.1415)));
+
+    changeReq = client.getCUDRequestFactory().getEntityUpdateRequest(UpdateType.PATCH, patch);
+    changeReq.setFormat(ODataFormat.JSON_FULL_METADATA);
+    changeReq.setContentType("3");
+    changeset.addRequest(changeReq);
+
+    // -----------------------------
+    // - Append get request
+    // -----------------------------
+    appendGetRequest(payload, "ESAllPrim", 32767); // Without error
+
+    // -----------------------------
+    // - Fetch result
+    // -----------------------------
+    final ODataBatchResponse response = payload.getResponse();
+    assertEquals(202, response.getStatusCode());
+    final Iterator<ODataBatchResponseItem> bodyIterator = response.getBody();
+
+    // Check first get request
+    assertTrue(bodyIterator.hasNext());
+    ODataBatchResponseItem item = bodyIterator.next();
+    assertFalse(item.isChangeset());
+
+    // Check change set
+    assertTrue(bodyIterator.hasNext());
+    item = bodyIterator.next();
+    assertTrue(item.isChangeset());
+
+    for (int i = 0; i < 3; i++) {
+      assertTrue(item.hasNext());
+      assertTrue(item instanceof ODataChangesetResponseItem);
+      ODataChangesetResponseItem changeSetResponseItem = (ODataChangesetResponseItem) item.next();
+      assertNotNull(changeSetResponseItem);
+
+      ODataResponse chgRequest = changeSetResponseItem.next();
+      final String contentId = chgRequest.getHeader(ODataBatchConstants.CHANGESET_CONTENT_ID_NAME).iterator().next();
+
+      if (contentId == "1") {
+        // Insert
+        assertEquals(HttpStatusCode.CREATED.getStatusCode(), chgRequest.getStatusCode());
+      } else if (contentId == "2") {
+        // Update
+        assertEquals(HttpStatusCode.OK.getStatusCode(), chgRequest.getStatusCode());
+      } else if (contentId == "3") {
+        // Upsert
+        assertEquals(HttpStatusCode.CREATED.getStatusCode(), chgRequest.getStatusCode());
+      } else {
+        fail("Unkonwn content id " + contentId);
+      }
+    }
+    assertFalse(item.hasNext());
+
+    // Check second get request
+    assertTrue(bodyIterator.hasNext());
+    item = bodyIterator.next();
+    assertFalse(item.isChangeset());
+  }
+
+  private void appendGetRequest(final BatchManager manager, final String segment, final Object key) {
+    URIBuilder targetURI = client.newURIBuilder(SERVICE_URI);
+    targetURI.appendEntitySetSegment(segment).appendKeySegment(key);
+
+    ODataEntityRequest<ODataEntity> queryReq = client.getRetrieveRequestFactory().getEntityRequest(targetURI.build());
+    queryReq.setFormat(ODataFormat.JSON);
+    manager.addRequest(queryReq);
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/4ff5fb9c/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/ODataResponsePart.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/ODataResponsePart.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/ODataResponsePart.java
index 8dd7480..c9a914a 100644
--- a/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/ODataResponsePart.java
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/ODataResponsePart.java
@@ -18,25 +18,63 @@
  */
 package org.apache.olingo.server.api.batch;
 
+import java.util.Arrays;
 import java.util.List;
 
 import org.apache.olingo.server.api.ODataResponse;
 
-public interface ODataResponsePart {
+public class ODataResponsePart {
+  private List<ODataResponse> responses;
+  private boolean isChangeSet;
+  
   /**
-   * Returns a collection of ODataResponses.
-   * Each collections contains at least one {@link ODataResponse}.
+   * Creates a new ODataResponsePart.
    * 
-   * If this instance represents a change set, there are may many ODataResponses
+   * An ODataResponsePart represents a collections of ODataResponses.
+   * A list of ODataResponseParts can be combined by the BatchSerializer to a single
+   * OData batch response.
    *  
-   * @return a list of {@link ODataResponse}
+   * @param responses     A list of {@link ODataResponse}
+   * @param isChangeSet   True this ODataResponsePart represents a change set, otherwise false
+   */
+  public ODataResponsePart(List<ODataResponse> responses, boolean isChangeSet) {
+    this.responses = responses;
+    this.isChangeSet = isChangeSet;
+  }
+  
+  /**
+   * Creates a new ODataResponsePart.
+   * 
+   * An ODataResponsePart represents a collections of ODataResponses.
+   * A list of ODataResponseParts can be combined by the BatchSerializer to a single
+   * OData batch response.
+   *  
+   * @param responses     A single {@link ODataResponse}
+   * @param isChangeSet   True this ODataResponsePart represents a change set, otherwise false
    */
-  public List<ODataResponse> getResponses();
+  public ODataResponsePart(ODataResponse response, boolean isChangeSet) {
+    this.responses = Arrays.asList(new ODataResponse[] { response });
+    this.isChangeSet = isChangeSet;
+  }
   
   /**
    * Returns true if the current instance represents a change set.
    * 
    * @return true or false
    */
-  public boolean isChangeSet();
+  public List<ODataResponse> getResponses() {
+    return responses;
+  }
+  
+  /**
+   * Returns a collection of ODataResponses.
+   * Each collections contains at least one {@link ODataResponse}.
+   * 
+   * If this instance represents a change set, there are may many ODataResponses
+   *  
+   * @return a list of {@link ODataResponse}
+   */
+  public boolean isChangeSet() {
+    return isChangeSet;
+  }
 }

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/4ff5fb9c/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/BatchProcessor.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/BatchProcessor.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/BatchProcessor.java
index a0a7135..843fa28 100644
--- a/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/BatchProcessor.java
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/BatchProcessor.java
@@ -24,10 +24,11 @@ import org.apache.olingo.server.api.ODataRequest;
 import org.apache.olingo.server.api.ODataResponse;
 import org.apache.olingo.server.api.batch.BatchOperation;
 import org.apache.olingo.server.api.batch.BatchRequestPart;
+import org.apache.olingo.server.api.batch.ODataResponsePart;
 
 public interface BatchProcessor extends Processor {
   void executeBatch(BatchOperation operation, ODataRequest request, ODataResponse response);
 
-  List<ODataResponse> executeChangeSet(BatchOperation operation, List<ODataRequest> requests,
+  ODataResponsePart executeChangeSet(BatchOperation operation, List<ODataRequest> requests,
       BatchRequestPart requestPart);
 }

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/4ff5fb9c/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/DefaultProcessor.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/DefaultProcessor.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/DefaultProcessor.java
index 473c904..25778e3 100644
--- a/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/DefaultProcessor.java
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/DefaultProcessor.java
@@ -19,7 +19,11 @@
 package org.apache.olingo.server.api.processor;
 
 import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
 
+import org.apache.olingo.commons.api.ODataRuntimeException;
 import org.apache.olingo.commons.api.format.ContentType;
 import org.apache.olingo.commons.api.format.ODataFormat;
 import org.apache.olingo.commons.api.http.HttpHeader;
@@ -30,6 +34,10 @@ import org.apache.olingo.server.api.ODataRequest;
 import org.apache.olingo.server.api.ODataResponse;
 import org.apache.olingo.server.api.ODataServerError;
 import org.apache.olingo.server.api.ServiceMetadata;
+import org.apache.olingo.server.api.batch.BatchException;
+import org.apache.olingo.server.api.batch.BatchOperation;
+import org.apache.olingo.server.api.batch.BatchRequestPart;
+import org.apache.olingo.server.api.batch.ODataResponsePart;
 import org.apache.olingo.server.api.serializer.ODataSerializer;
 import org.apache.olingo.server.api.serializer.SerializerException;
 import org.apache.olingo.server.api.uri.UriInfo;
@@ -42,8 +50,12 @@ import org.apache.olingo.server.api.uri.UriInfo;
  * <p>This implementation is registered in the ODataHandler by default.
  * The default can be replaced by re-registering a custom implementation.</p>
  */
-public class DefaultProcessor implements MetadataProcessor, ServiceDocumentProcessor, ExceptionProcessor {
-
+public class DefaultProcessor implements MetadataProcessor, ServiceDocumentProcessor, ExceptionProcessor,
+    BatchProcessor {
+  
+  private static final String PREFERENCE_CONTINUE_ON_ERROR = "odata.continue-on-error";
+  private static final String PREFER_HEADER = "Prefer";
+  
   private OData odata;
   private ServiceMetadata serviceMetadata;
 
@@ -89,4 +101,81 @@ public class DefaultProcessor implements MetadataProcessor, ServiceDocumentProce
       response.setHeader(HttpHeader.CONTENT_TYPE, ContentType.APPLICATION_JSON.toContentTypeString());
     }
   }
+
+  @Override
+  public void executeBatch(BatchOperation operation, ODataRequest request, ODataResponse response) {
+    boolean continueOnError = shouldContinueOnError(request);
+
+    try {
+      final List<BatchRequestPart> parts = operation.parseBatchRequest(request.getBody());
+      final List<ODataResponsePart> responseParts = new ArrayList<ODataResponsePart>();
+
+      for (BatchRequestPart part : parts) {
+        final ODataResponsePart responsePart = operation.handleBatchRequest(part);
+        responseParts.add(responsePart);    // Also add failed responses
+
+        if (responsePart.getResponses().get(0).getStatusCode() >= 400
+            && !continueOnError) {
+
+          // Perform some additions actions
+          // ...
+          
+          break; // Stop processing, but serialize all recent requests
+        }
+      }
+
+      operation.writeResponseParts(responseParts, response);  // Serialize responses
+    } catch (BatchException e) {
+      throw new ODataRuntimeException(e);
+    } catch (IOException e) {
+      throw new ODataRuntimeException(e);
+    }
+  }
+
+  private boolean shouldContinueOnError(ODataRequest request) {
+    final List<String> preferValues = request.getHeaders(PREFER_HEADER);
+
+    if (preferValues != null) {
+      for (final String preference : preferValues) {
+        if (PREFERENCE_CONTINUE_ON_ERROR.equals(preference)) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  @Override
+  public ODataResponsePart executeChangeSet(BatchOperation operation, List<ODataRequest> requests,
+      BatchRequestPart requestPart) {
+    List<ODataResponse> responses = new ArrayList<ODataResponse>();
+
+    for (ODataRequest request : requests) {
+      try {
+        final ODataResponse oDataResponse = operation.handleODataRequest(request, requestPart);
+
+        if (oDataResponse.getStatusCode() < 400) {
+          responses.add(oDataResponse);
+        } else {
+          // Rollback
+          // ...
+
+          // OData Version 4.0 Part 1: Protocol Plus Errata 01
+          // 11.7.4 Responding to a Batch Request
+          //
+          // When a request within a change set fails, the change set response is not represented using
+          // the multipart/mixed media type. Instead, a single response, using the application/http media type
+          // and a Content-Transfer-Encoding header with a value of binary, is returned that applies to all requests
+          // in the change set and MUST be formatted according to the Error Handling defined
+          // for the particular response format.
+
+          return new ODataResponsePart(oDataResponse, false);
+        }
+      } catch (BatchException e) {
+        throw new ODataRuntimeException(e);
+      }
+    }
+
+    return new ODataResponsePart(responses, true);
+  }
 }

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/4ff5fb9c/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchPartHandler.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchPartHandler.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchPartHandler.java
index 5bec30b..23ba106 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchPartHandler.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchPartHandler.java
@@ -129,17 +129,14 @@ public class BatchPartHandler {
       final List<ODataResponse> responses = new ArrayList<ODataResponse>();
       responses.add(handleODataRequest(request.getRequests().get(0), request));
       
-      return new ODataResponsePartImpl(responses, false);
+      return new ODataResponsePart(responses, false);
     }
   }
 
   private ODataResponsePart handleChangeSet(BatchRequestPart request) throws BatchException {
-    final List<ODataResponse> responses = new ArrayList<ODataResponse>();
     final BatchChangeSetSorter sorter = new BatchChangeSetSorter(request.getRequests());
-
-    responses.addAll(batchProcessor.executeChangeSet(batchOperation, sorter.getOrderdRequests(), request));
     
-    return new ODataResponsePartImpl(responses, true);
+    return batchProcessor.executeChangeSet(batchOperation, sorter.getOrderdRequests(), request);
   }
 
   private static class UriMapping {

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/4ff5fb9c/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/ODataResponsePartImpl.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/ODataResponsePartImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/ODataResponsePartImpl.java
deleted file mode 100644
index da52a37..0000000
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/ODataResponsePartImpl.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.olingo.server.core.batch.handler;
-
-import java.util.List;
-
-import org.apache.olingo.server.api.ODataResponse;
-import org.apache.olingo.server.api.batch.ODataResponsePart;
-
-public class ODataResponsePartImpl implements ODataResponsePart {
-  private List<ODataResponse> responses;
-  private boolean isChangeSet;
-  
-  public ODataResponsePartImpl(List<ODataResponse> responses, boolean isChangeSet) {
-    this.responses = responses;
-    this.isChangeSet = isChangeSet;
-  }
-
-  @Override
-  public List<ODataResponse> getResponses() {
-    return responses;
-  }
-
-  @Override
-  public boolean isChangeSet() {
-    return isChangeSet;
-  }
-}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/4ff5fb9c/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchParserCommon.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchParserCommon.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchParserCommon.java
index 43a09b6..b87f8ef 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchParserCommon.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchParserCommon.java
@@ -123,9 +123,6 @@ public class BatchParserCommon {
     // Remove preamble
     if (messageParts.size() > 0) {
       messageParts.remove(0);
-    } else {
-      throw new BatchException("Missing boundary delimiter", BatchException.MessageKeys.MISSING_BOUNDARY_DELIMITER, ""
-          + lineNumer);
     }
 
     if (!isEndReached) {
@@ -133,11 +130,6 @@ public class BatchParserCommon {
           "" + lineNumer);
     }
 
-    if (messageParts.size() == 0) {
-      throw new BatchException("No boundary delimiter found",
-          BatchException.MessageKeys.MISSING_BOUNDARY_DELIMITER, boundary, "" + lineNumer);
-    }
-
     return messageParts;
   }
 

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/4ff5fb9c/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/writer/BatchResponseWriter.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/writer/BatchResponseWriter.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/writer/BatchResponseWriter.java
index 812ea8b..0c13cb9 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/writer/BatchResponseWriter.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/writer/BatchResponseWriter.java
@@ -149,7 +149,7 @@ public class BatchResponseWriter {
     final Map<String, String> header = response.getHeaders();
 
     for (final String key : header.keySet()) {
-      // Requests do never have content id header
+      // Requests do never has a content id header
       if (!key.equalsIgnoreCase(BatchParserCommon.HTTP_CONTENT_ID)) {
         appendHeader(key, header.get(key), writer);
       }

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/4ff5fb9c/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/writer/ODataResponsePartImpl.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/writer/ODataResponsePartImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/writer/ODataResponsePartImpl.java
deleted file mode 100644
index a9711c9..0000000
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/writer/ODataResponsePartImpl.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.olingo.server.core.batch.writer;
-
-import java.util.List;
-
-import org.apache.olingo.server.api.ODataResponse;
-import org.apache.olingo.server.api.batch.ODataResponsePart;
-
-public class ODataResponsePartImpl implements ODataResponsePart {
-  private final List<ODataResponse> responses;
-  private final boolean isChangeSet;
-
-  public ODataResponsePartImpl(final List<ODataResponse> responses, final boolean isChangeSet) {
-    this.responses = responses;
-    this.isChangeSet = isChangeSet;
-  }
-
-  @Override
-  public List<ODataResponse> getResponses() {
-    return responses;
-  }
-
-  @Override
-  public boolean isChangeSet() {
-    return isChangeSet;
-  }
-}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/4ff5fb9c/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/BatchRequestParserTest.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/BatchRequestParserTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/BatchRequestParserTest.java
index c9778e3..88fdf08 100644
--- a/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/BatchRequestParserTest.java
+++ b/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/BatchRequestParserTest.java
@@ -274,7 +274,8 @@ public class BatchRequestParserTest {
         + GET_REQUEST
         + "--batch_8194-cf13-1f56--";
 
-    parseInvalidBatchBody(batch, BatchException.MessageKeys.MISSING_BOUNDARY_DELIMITER);
+    final List<BatchRequestPart> parts = parse(batch);
+    assertEquals(0, parts.size());
   }
 
   @Test
@@ -450,14 +451,23 @@ public class BatchRequestParserTest {
         + "POST Employees('1')/EmployeeName HTTP/1.1" + CRLF
         + CRLF;
 
-    parseInvalidBatchBody(batch, BatchException.MessageKeys.MISSING_BOUNDARY_DELIMITER);
+    parseInvalidBatchBody(batch, BatchException.MessageKeys.MISSING_CLOSE_DELIMITER);
   }
-
+  
+  @Test
+  public void testEmptyRequest() throws BatchException, UnsupportedEncodingException {
+    final String batch = ""
+        + "--batch_8194-cf13-1f56--";
+    
+    final List<BatchRequestPart> parts = parse(batch);
+    assertEquals(0, parts.size());
+  }
+  
   @Test
   public void testBadRequest() throws UnsupportedEncodingException {
     final String batch = "This is a bad request. There is no syntax and also no semantic";
 
-    parseInvalidBatchBody(batch, BatchException.MessageKeys.MISSING_BOUNDARY_DELIMITER);
+    parseInvalidBatchBody(batch, BatchException.MessageKeys.MISSING_CLOSE_DELIMITER);
   }
 
   @Test
@@ -496,7 +506,7 @@ public class BatchRequestParserTest {
   }
 
   @Test
-  public void testInvalidChangeSetBoundary() throws UnsupportedEncodingException {
+  public void testInvalidChangeSetBoundary() throws UnsupportedEncodingException, BatchException {
     final String batch = "--batch_8194-cf13-1f56" + CRLF
         + "Content-Type: multipart/mixed;boundary=changeset_f980-1cb6-94dd" + CRLF
         + CRLF
@@ -511,7 +521,12 @@ public class BatchRequestParserTest {
         + CRLF
         + "--batch_8194-cf13-1f56--";
 
-    parseInvalidBatchBody(batch, BatchException.MessageKeys.MISSING_BOUNDARY_DELIMITER);
+    final List<BatchRequestPart> parts = parse(batch);
+    assertEquals(1, parts.size());
+    
+    final BatchRequestPart part = parts.get(0);
+    assertTrue(part.isChangeSet());
+    assertEquals(0, part.getRequests().size());
   }
 
   @Test
@@ -1261,7 +1276,6 @@ public class BatchRequestParserTest {
     final List<BatchRequestPart> batchRequestParts = parser.parseBatchRequest(in);
 
     assertNotNull(batchRequestParts);
-    assertFalse(batchRequestParts.isEmpty());
 
     return batchRequestParts;
   }

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/4ff5fb9c/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/MockedBatchHandlerTest.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/MockedBatchHandlerTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/MockedBatchHandlerTest.java
index e1864e2..7a85ffa 100644
--- a/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/MockedBatchHandlerTest.java
+++ b/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/MockedBatchHandlerTest.java
@@ -535,7 +535,7 @@ public class MockedBatchHandlerTest {
     public void init(OData odata, ServiceMetadata serviceMetadata) {}
 
     @Override
-    public List<ODataResponse> executeChangeSet(BatchOperation operation, List<ODataRequest> requests,
+    public ODataResponsePart executeChangeSet(BatchOperation operation, List<ODataRequest> requests,
         BatchRequestPart requestPart) {
       List<ODataResponse> responses = new ArrayList<ODataResponse>();
 
@@ -557,7 +557,7 @@ public class MockedBatchHandlerTest {
         }
       }
 
-      return responses;
+      return new ODataResponsePart(responses, true);
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/4ff5fb9c/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/writer/BatchResponseWriterTest.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/writer/BatchResponseWriterTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/writer/BatchResponseWriterTest.java
index b7169b9..ea05bfb 100644
--- a/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/writer/BatchResponseWriterTest.java
+++ b/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/writer/BatchResponseWriterTest.java
@@ -50,14 +50,14 @@ public class BatchResponseWriterTest {
 
     List<ODataResponse> responses = new ArrayList<ODataResponse>(1);
     responses.add(response);
-    parts.add(new ODataResponsePartImpl(responses, false));
+    parts.add(new ODataResponsePart(responses, false));
 
     ODataResponse changeSetResponse = new ODataResponse();
     changeSetResponse.setStatusCode(HttpStatusCode.NO_CONTENT.getStatusCode());
     changeSetResponse.setHeader(BatchParserCommon.HTTP_CONTENT_ID, "1");
     responses = new ArrayList<ODataResponse>(1);
     responses.add(changeSetResponse);
-    parts.add(new ODataResponsePartImpl(responses, true));
+    parts.add(new ODataResponsePart(responses, true));
 
     BatchResponseWriter writer = new BatchResponseWriter();
     ODataResponse batchResponse = new ODataResponse();
@@ -109,7 +109,7 @@ public class BatchResponseWriterTest {
 
     List<ODataResponse> responses = new ArrayList<ODataResponse>(1);
     responses.add(response);
-    parts.add(new ODataResponsePartImpl(responses, false));
+    parts.add(new ODataResponsePart(responses, false));
 
     ODataResponse batchResponse = new ODataResponse();
     new BatchResponseWriter().toODataResponse(parts, batchResponse);
@@ -144,7 +144,7 @@ public class BatchResponseWriterTest {
 
     List<ODataResponse> responses = new ArrayList<ODataResponse>(1);
     responses.add(response);
-    parts.add(new ODataResponsePartImpl(responses, true));
+    parts.add(new ODataResponsePart(responses, true));
 
     BatchResponseWriter writer = new BatchResponseWriter();
     ODataResponse batchResponse = new ODataResponse();


[05/22] olingo-odata4 git commit: [OLINGO-472] BatchParser first draft

Posted by ch...@apache.org.
[OLINGO-472] BatchParser first draft

Signed-off-by: Christian Amend <ch...@apache.org>


Project: http://git-wip-us.apache.org/repos/asf/olingo-odata4/repo
Commit: http://git-wip-us.apache.org/repos/asf/olingo-odata4/commit/32247295
Tree: http://git-wip-us.apache.org/repos/asf/olingo-odata4/tree/32247295
Diff: http://git-wip-us.apache.org/repos/asf/olingo-odata4/diff/32247295

Branch: refs/heads/master
Commit: 32247295fce782a393457bc7d7a08be030d434fd
Parents: 3e11738
Author: Christian Holzer <c....@sap.com>
Authored: Thu Oct 16 17:39:39 2014 +0200
Committer: Christian Amend <ch...@apache.org>
Committed: Mon Nov 3 14:46:08 2014 +0100

----------------------------------------------------------------------
 .../server/api/batch/BatchParserResult.java     |   23 +
 .../server/api/batch/BatchRequestPart.java      |   42 +
 .../server/api/batch/ODataResponsePart.java     |   42 +
 .../server/core/batch/BatchException.java       |   65 +
 .../olingo/server/core/batch/StringUtil.java    |   54 +
 .../server/core/batch/parser/BatchBodyPart.java |  135 +
 .../core/batch/parser/BatchChangeSetPart.java   |   56 +
 .../server/core/batch/parser/BatchParser.java   |   91 +
 .../core/batch/parser/BatchParserCommon.java    |  228 ++
 .../server/core/batch/parser/BatchPart.java     |   25 +
 .../core/batch/parser/BatchQueryOperation.java  |   82 +
 .../core/batch/parser/BatchRequestPartImpl.java |   47 +
 .../BufferedReaderIncludingLineEndings.java     |  286 +++
 .../olingo/server/core/batch/parser/Header.java |  181 ++
 .../server/core/batch/parser/HeaderField.java   |  121 +
 .../BatchRequestTransformator.java              |  191 ++
 .../batch/transformator/BatchTransformator.java |   29 +
 .../transformator/BatchTransformatorCommon.java |  250 ++
 .../core/batch/writer/BatchResponseWriter.java  |  228 ++
 .../batch/writer/ODataResponsePartImpl.java     |   44 +
 .../server-core-exceptions-i18n.properties      |   21 +
 .../core/batch/BatchRequestParserTest.java      | 1308 ++++++++++
 .../batch/parser/BatchParserCommonTest.java     |  230 ++
 .../BufferedReaderIncludingLineEndingsTest.java |  484 ++++
 .../server/core/batch/parser/HeaderTest.java    |  179 ++
 .../batch/writer/BatchResponseWriterTest.java   |  179 ++
 .../src/test/resources/batchLarge.batch         | 2422 ++++++++++++++++++
 .../src/test/resources/batchWithContent.batch   |    1 +
 .../src/test/resources/batchWithPost.batch      |   39 +
 29 files changed, 7083 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/32247295/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchParserResult.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchParserResult.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchParserResult.java
new file mode 100644
index 0000000..93bc34d
--- /dev/null
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchParserResult.java
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.api.batch;
+
+public interface BatchParserResult {
+
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/32247295/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchRequestPart.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchRequestPart.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchRequestPart.java
new file mode 100644
index 0000000..ba5319f
--- /dev/null
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchRequestPart.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.api.batch;
+
+import java.util.List;
+
+import org.apache.olingo.server.api.ODataRequest;
+
+/**
+ * A BatchPart
+ * <p> BatchPart represents a distinct MIME part of a Batch Request body. It can be ChangeSet or Query Operation
+ */
+public interface BatchRequestPart extends BatchParserResult {
+
+  /**
+   * Get the info if a BatchPart is a ChangeSet
+   * @return true or false
+   */
+  public boolean isChangeSet();
+
+  /**
+   * Get requests. If a BatchPart is a Query Operation, the list contains one request.
+   * @return a list of {@link ODataRequest}
+   */
+  public List<ODataRequest> getRequests();
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/32247295/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/ODataResponsePart.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/ODataResponsePart.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/ODataResponsePart.java
new file mode 100644
index 0000000..8dd7480
--- /dev/null
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/ODataResponsePart.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.api.batch;
+
+import java.util.List;
+
+import org.apache.olingo.server.api.ODataResponse;
+
+public interface ODataResponsePart {
+  /**
+   * Returns a collection of ODataResponses.
+   * Each collections contains at least one {@link ODataResponse}.
+   * 
+   * If this instance represents a change set, there are may many ODataResponses
+   *  
+   * @return a list of {@link ODataResponse}
+   */
+  public List<ODataResponse> getResponses();
+  
+  /**
+   * Returns true if the current instance represents a change set.
+   * 
+   * @return true or false
+   */
+  public boolean isChangeSet();
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/32247295/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/BatchException.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/BatchException.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/BatchException.java
new file mode 100644
index 0000000..aafe141
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/BatchException.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.batch;
+
+import org.apache.olingo.server.api.ODataTranslatedException;
+
+public class BatchException extends ODataTranslatedException {
+  public static enum MessageKeys implements MessageKey {
+    INVALID_BOUNDARY,
+    INVALID_CHANGESET_METHOD,
+    INVALID_CONTENT,
+    INVALID_CONTENT_LENGTH,
+    INVALID_CONTENT_TRANSFER_ENCODING,
+    INVALID_CONTENT_TYPE,
+    INVALID_HEADER,
+    INVALID_HTTP_VERSION,
+    INVALID_METHOD,
+    INVALID_QUERY_OPERATION_METHOD,
+    INVALID_STATUS_LINE,
+    INVALID_URI,
+    MISSING_BLANK_LINE,
+    MISSING_BOUNDARY_DELIMITER,
+    MISSING_CLOSE_DELIMITER,
+    MISSING_CONTENT_ID,
+    MISSING_CONTENT_TRANSFER_ENCODING,
+    MISSING_CONTENT_TYPE,
+    MISSING_MANDATORY_HEADER, FORBIDDEN_HEADER;
+
+    @Override
+    public String getKey() {
+      return name();
+    }
+  }
+
+  private static final long serialVersionUID = -907752788975531134L;
+
+  public BatchException(final String developmentMessage, final MessageKey messageKey, final int lineNumber) {
+    this(developmentMessage, messageKey, "" + lineNumber);
+  }
+
+  public BatchException(final String developmentMessage, final MessageKey messageKey, final String... parameters) {
+    super(developmentMessage, messageKey, parameters);
+  }
+
+  @Override
+  protected String getBundleName() {
+    return DEFAULT_SERVER_BUNDLE_NAME;
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/32247295/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/StringUtil.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/StringUtil.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/StringUtil.java
new file mode 100644
index 0000000..2602852
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/StringUtil.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.batch;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+
+import org.apache.olingo.commons.api.ODataRuntimeException;
+import org.apache.olingo.server.core.batch.parser.BufferedReaderIncludingLineEndings;
+
+public class StringUtil {
+  
+  
+  public static String toString(final InputStream in) throws IOException {
+    final StringBuilder builder = new StringBuilder();
+    final BufferedReaderIncludingLineEndings reader = new BufferedReaderIncludingLineEndings(new InputStreamReader(in));
+    String currentLine;
+    
+    while((currentLine = reader.readLine()) != null) {
+      builder.append(currentLine);
+    }
+    
+    reader.close();
+    
+    return builder.toString();
+  }
+
+  public static InputStream toInputStream(final String string) {
+    try {
+      return new ByteArrayInputStream(string.getBytes("UTF-8"));
+    } catch (UnsupportedEncodingException e) {
+      throw new ODataRuntimeException("Charset UTF-8 not found");
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/32247295/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchBodyPart.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchBodyPart.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchBodyPart.java
new file mode 100644
index 0000000..c2d2e0f
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchBodyPart.java
@@ -0,0 +1,135 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.batch.parser;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.olingo.commons.api.http.HttpHeader;
+import org.apache.olingo.server.core.batch.BatchException;
+import org.apache.olingo.server.core.batch.parser.BufferedReaderIncludingLineEndings.Line;
+
+public class BatchBodyPart implements BatchPart {
+  final private String boundary;
+  final private boolean isStrict;
+  final List<Line> remainingMessage = new LinkedList<Line>();
+
+  private Header headers;
+  private boolean isChangeSet;
+  private List<BatchQueryOperation> requests;
+
+  public BatchBodyPart(final List<Line> message, final String boundary, final boolean isStrict) {
+    this.boundary = boundary;
+    this.isStrict = isStrict;
+    remainingMessage.addAll(message);
+  }
+
+  public BatchBodyPart parse() throws BatchException {
+    headers = BatchParserCommon.consumeHeaders(remainingMessage);
+    BatchParserCommon.consumeBlankLine(remainingMessage, isStrict);
+    isChangeSet = isChangeSet(headers);
+    requests = consumeRequest(remainingMessage);
+
+    return this;
+  }
+
+  private boolean isChangeSet(final Header header) throws BatchException {
+    final List<String> contentTypes = headers.getHeaders(HttpHeader.CONTENT_TYPE);
+    boolean isChangeSet = false;
+
+    if (contentTypes.size() == 0) {
+      throw new BatchException("Missing content type", BatchException.MessageKeys.MISSING_CONTENT_TYPE, ""
+          + headers.getLineNumber());
+    }
+
+    for (String contentType : contentTypes) {
+      if (isContentTypeMultiPartMixed(contentType)) {
+        isChangeSet = true;
+      }
+    }
+
+    return isChangeSet;
+  }
+
+  private List<BatchQueryOperation> consumeRequest(final List<Line> remainingMessage) throws BatchException {
+    if (isChangeSet) {
+      return consumeChangeSet(remainingMessage);
+    } else {
+      return consumeQueryOperation(remainingMessage);
+    }
+  }
+
+  private List<BatchQueryOperation> consumeChangeSet(final List<Line> remainingMessage2) throws BatchException {
+    final List<List<Line>> changeRequests = splitChangeSet(remainingMessage);
+    final List<BatchQueryOperation> requestList = new LinkedList<BatchQueryOperation>();
+
+    for (List<Line> changeRequest : changeRequests) {
+      requestList.add(new BatchChangeSetPart(changeRequest, isStrict).parse());
+    }
+
+    return requestList;
+  }
+
+  private List<List<Line>> splitChangeSet(final List<Line> remainingMessage2) throws BatchException {
+
+    final HeaderField contentTypeField = headers.getHeaderField(HttpHeader.CONTENT_TYPE);
+    final String changeSetBoundary = BatchParserCommon.getBoundary(contentTypeField.getValueNotNull(),
+        contentTypeField.getLineNumber());
+    validateChangeSetBoundary(changeSetBoundary, headers);
+
+    return BatchParserCommon.splitMessageByBoundary(remainingMessage, changeSetBoundary);
+  }
+
+  private void validateChangeSetBoundary(final String changeSetBoundary, final Header header) throws BatchException {
+    if (changeSetBoundary.equals(boundary)) {
+      throw new BatchException("Change set boundary is equals to batch request boundary",
+          BatchException.MessageKeys.INVALID_BOUNDARY,
+          "" + header.getHeaderField(HttpHeader.CONTENT_TYPE).getLineNumber());
+    }
+  }
+
+  private List<BatchQueryOperation> consumeQueryOperation(final List<Line> remainingMessage) throws BatchException {
+    final List<BatchQueryOperation> requestList = new LinkedList<BatchQueryOperation>();
+    requestList.add(new BatchQueryOperation(remainingMessage, isStrict).parse());
+
+    return requestList;
+  }
+
+  private boolean isContentTypeMultiPartMixed(final String contentType) {
+    return BatchParserCommon.PATTERN_MULTIPART_BOUNDARY.matcher(contentType).matches();
+  }
+
+  @Override
+  public Header getHeaders() {
+    return headers;
+  }
+
+  @Override
+  public boolean isStrict() {
+    return isStrict;
+  }
+
+  public boolean isChangeSet() {
+    return isChangeSet;
+  }
+
+  public List<BatchQueryOperation> getRequests() {
+    return requests;
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/32247295/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchChangeSetPart.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchChangeSetPart.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchChangeSetPart.java
new file mode 100644
index 0000000..1d0bd6f
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchChangeSetPart.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.batch.parser;
+
+import java.util.List;
+
+import org.apache.olingo.server.core.batch.BatchException;
+import org.apache.olingo.server.core.batch.parser.BufferedReaderIncludingLineEndings.Line;
+
+public class BatchChangeSetPart extends BatchQueryOperation {
+  private BatchQueryOperation request;
+
+  public BatchChangeSetPart(final List<Line> message, final boolean isStrict) throws BatchException {
+    super(message, isStrict);
+  }
+
+  @Override
+  public BatchChangeSetPart parse() throws BatchException {
+    headers = BatchParserCommon.consumeHeaders(message);
+    BatchParserCommon.consumeBlankLine(message, isStrict);
+
+    request = new BatchQueryOperation(message, isStrict).parse();
+
+    return this;
+  }
+
+  public BatchQueryOperation getRequest() {
+    return request;
+  }
+
+  @Override
+  public List<Line> getBody() {
+    return request.getBody();
+  }
+
+  @Override
+  public Line getHttpStatusLine() {
+    return request.getHttpStatusLine();
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/32247295/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchParser.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchParser.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchParser.java
new file mode 100644
index 0000000..37b1a9c
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchParser.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.batch.parser;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.olingo.commons.api.ODataRuntimeException;
+import org.apache.olingo.server.api.batch.BatchParserResult;
+import org.apache.olingo.server.api.batch.BatchRequestPart;
+import org.apache.olingo.server.core.batch.BatchException;
+import org.apache.olingo.server.core.batch.parser.BufferedReaderIncludingLineEndings.Line;
+import org.apache.olingo.server.core.batch.transformator.BatchRequestTransformator;
+
+public class BatchParser {
+
+  private final String contentTypeMime;
+  private final String baseUri;
+  private final String rawServiceResolutionUri;
+  private final boolean isStrict;
+  
+  public BatchParser(final String contentType, final String baseUri, final String serviceResolutionUri, 
+      final boolean isStrict) {
+    contentTypeMime = contentType;
+    this.baseUri = BatchParserCommon.removeEndingSlash(baseUri);
+    this.isStrict = isStrict;
+    this.rawServiceResolutionUri = serviceResolutionUri;
+  }
+
+  @SuppressWarnings("unchecked")
+  public List<BatchRequestPart> parseBatchRequest(final InputStream in) throws BatchException {
+    return (List<BatchRequestPart>) parse(in, new BatchRequestTransformator(baseUri, rawServiceResolutionUri));
+  }
+
+  private List<? extends BatchParserResult> parse(final InputStream in, final BatchRequestTransformator transformator)
+      throws BatchException {
+    try {
+      return parseBatch(in, transformator);
+    } catch (IOException e) {
+      throw new ODataRuntimeException(e);
+    } finally {
+      try {
+        in.close();
+      } catch (IOException e) {
+        throw new ODataRuntimeException(e);
+      }
+    }
+  }
+
+  private List<BatchParserResult> parseBatch(final InputStream in, final BatchRequestTransformator transformator)
+      throws IOException, BatchException {
+    final String boundary = BatchParserCommon.getBoundary(contentTypeMime, 1);
+    final List<BatchParserResult> resultList = new LinkedList<BatchParserResult>();
+    final List<List<Line>> bodyPartStrings = splitBodyParts(in, boundary);
+
+    for (List<Line> bodyPartString : bodyPartStrings) {
+      BatchBodyPart bodyPart = new BatchBodyPart(bodyPartString, boundary, isStrict).parse();
+      resultList.addAll(transformator.transform(bodyPart));
+    }
+
+    return resultList;
+  }
+
+  private List<List<Line>> splitBodyParts(final InputStream in, final String boundary) throws IOException,
+      BatchException {
+    final BufferedReaderIncludingLineEndings reader = new BufferedReaderIncludingLineEndings(new InputStreamReader(in));
+    final List<Line> message = reader.toLineList();
+    reader.close();
+
+    return BatchParserCommon.splitMessageByBoundary(message, boundary);
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/32247295/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchParserCommon.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchParserCommon.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchParserCommon.java
new file mode 100644
index 0000000..e2dc5e5
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchParserCommon.java
@@ -0,0 +1,228 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.batch.parser;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.olingo.commons.api.http.HttpContentType;
+import org.apache.olingo.server.core.batch.BatchException;
+import org.apache.olingo.server.core.batch.parser.BufferedReaderIncludingLineEndings.Line;
+
+public class BatchParserCommon {
+
+  private static final String REG_EX_BOUNDARY =
+      "([a-zA-Z0-9_\\-\\.'\\+]{1,70})|\"([a-zA-Z0-9_\\-\\.'\\+\\s\\" +
+          "(\\),/:=\\?]{1,69}[a-zA-Z0-9_\\-\\.'\\+\\(\\),/:=\\?])\"";
+  private static final Pattern PATTERN_LAST_CRLF = Pattern.compile("(.*)(\r\n){1}( *)", Pattern.DOTALL);
+  private static final Pattern PATTERN_HEADER_LINE = Pattern.compile("([a-zA-Z\\-]+):\\s?(.*)\\s*");
+  private static final String REG_EX_APPLICATION_HTTP = "application/http";
+  
+  public static final Pattern PATTERN_MULTIPART_BOUNDARY = Pattern.compile("multipart/mixed(.*)",
+      Pattern.CASE_INSENSITIVE);
+  public static final Pattern PATTERN_CONTENT_TYPE_APPLICATION_HTTP = Pattern.compile(REG_EX_APPLICATION_HTTP,
+      Pattern.CASE_INSENSITIVE);
+  public static final String BINARY_ENCODING = "binary";
+  public static final String HTTP_CONTENT_ID = "Content-Id";
+  public static final String HTTP_CONTENT_TRANSFER_ENCODING = "Content-Transfer-Encoding";
+  
+  public static final String HTTP_EXPECT = "Expect";
+  public static final String HTTP_FROM = "From";
+  public static final String HTTP_MAX_FORWARDS = "Max-Forwards";
+  public static final String HTTP_RANGE = "Range";
+  public static final String HTTP_TE = "TE";
+  
+  public static String getBoundary(final String contentType, final int line) throws BatchException {
+    if (contentType.toLowerCase(Locale.ENGLISH).startsWith("multipart/mixed")) {
+      final String[] parameter = contentType.split(";");
+
+      for (final String pair : parameter) {
+
+        final String[] attrValue = pair.split("=");
+        if (attrValue.length == 2 && "boundary".equals(attrValue[0].trim().toLowerCase(Locale.ENGLISH))) {
+          if (attrValue[1].matches(REG_EX_BOUNDARY)) {
+            return trimQuota(attrValue[1].trim());
+          } else {
+            throw new BatchException("Invalid boundary format", BatchException.MessageKeys.INVALID_BOUNDARY, "" + line);
+          }
+        }
+
+      }
+    }
+    throw new BatchException("Content type is not multipart mixed", 
+        BatchException.MessageKeys.INVALID_CONTENT_TYPE, HttpContentType.MULTIPART_MIXED);
+  }
+
+  public static String removeEndingSlash(String content) {
+    content = content.trim();
+    int lastSlashIndex = content.lastIndexOf('/');
+
+    return (lastSlashIndex == content.length() - 1) ? content.substring(0, content.length() - 1) : content;
+  }
+
+  private static String trimQuota(String boundary) {
+    if (boundary.matches("\".*\"")) {
+      boundary = boundary.replace("\"", "");
+    }
+
+    return boundary;
+  }
+
+  public static List<List<Line>> splitMessageByBoundary(final List<Line> message, final String boundary)
+      throws BatchException {
+    final List<List<Line>> messageParts = new LinkedList<List<Line>>();
+    List<Line> currentPart = new ArrayList<Line>();
+    boolean isEndReached = false;
+
+    final String quotedBoundary = Pattern.quote(boundary);
+    final Pattern boundaryDelimiterPattern = Pattern.compile("--" + quotedBoundary + "--[\\s ]*");
+    final Pattern boundaryPattern = Pattern.compile("--" + quotedBoundary + "[\\s ]*");
+
+    for (Line currentLine : message) {
+      if (boundaryDelimiterPattern.matcher(currentLine.toString()).matches()) {
+        removeEndingCRLFFromList(currentPart);
+        messageParts.add(currentPart);
+        isEndReached = true;
+      } else if (boundaryPattern.matcher(currentLine.toString()).matches()) {
+        removeEndingCRLFFromList(currentPart);
+        messageParts.add(currentPart);
+        currentPart = new LinkedList<Line>();
+      } else {
+        currentPart.add(currentLine);
+      }
+
+      if (isEndReached) {
+        break;
+      }
+    }
+
+    final int lineNumer = (message.size() > 0) ? message.get(0).getLineNumber() : 0;
+    // Remove preamble
+    if (messageParts.size() > 0) {
+      messageParts.remove(0);
+    } else {
+      throw new BatchException("Missing boundary delimiter", BatchException.MessageKeys.MISSING_BOUNDARY_DELIMITER, ""
+          + lineNumer);
+    }
+
+    if (!isEndReached) {
+      throw new BatchException("Missing close boundary delimiter", BatchException.MessageKeys.MISSING_CLOSE_DELIMITER,
+          "" + lineNumer);
+    }
+
+    if (messageParts.size() == 0) {
+      throw new BatchException("No boundary delimiter found",
+          BatchException.MessageKeys.MISSING_BOUNDARY_DELIMITER, boundary, "" + lineNumer);
+    }
+
+    return messageParts;
+  }
+
+  private static void removeEndingCRLFFromList(final List<Line> list) {
+    if (list.size() > 0) {
+      Line lastLine = list.remove(list.size() - 1);
+      list.add(removeEndingCRLF(lastLine));
+    }
+  }
+
+  public static Line removeEndingCRLF(final Line line) {
+    Pattern pattern = PATTERN_LAST_CRLF;
+    Matcher matcher = pattern.matcher(line.toString());
+
+    if (matcher.matches()) {
+      return new Line(matcher.group(1), line.getLineNumber());
+    } else {
+      return line;
+    }
+  }
+
+  public static Header consumeHeaders(final List<Line> remainingMessage) {
+    final int headerLineNumber = remainingMessage.size() != 0 ? remainingMessage.get(0).getLineNumber() : 0;
+    final Header headers = new Header(headerLineNumber);
+    final Iterator<Line> iter = remainingMessage.iterator();
+    Line currentLine;
+    boolean isHeader = true;
+
+    while (iter.hasNext() && isHeader) {
+      currentLine = iter.next();
+      final Matcher headerMatcher = PATTERN_HEADER_LINE.matcher(currentLine.toString());
+
+      if (headerMatcher.matches() && headerMatcher.groupCount() == 2) {
+        iter.remove();
+
+        String headerName = headerMatcher.group(1).trim();
+        String headerValue = headerMatcher.group(2).trim();
+
+        headers.addHeader(headerName, Header.splitValuesByComma(headerValue), currentLine.getLineNumber());
+      } else {
+        isHeader = false;
+      }
+    }
+
+    return headers;
+  }
+
+  public static void consumeBlankLine(final List<Line> remainingMessage, final boolean isStrict) throws BatchException {
+    if (remainingMessage.size() > 0 && remainingMessage.get(0).toString().matches("\\s*\r\n\\s*")) {
+      remainingMessage.remove(0);
+    } else {
+      if (isStrict) {
+        final int lineNumber = (remainingMessage.size() > 0) ? remainingMessage.get(0).getLineNumber() : 0;
+        throw new BatchException("Missing blank line", BatchException.MessageKeys.MISSING_BLANK_LINE, "[None]", ""
+            + lineNumber);
+      }
+    }
+  }
+
+  public static InputStream convertLineListToInputStream(List<Line> messageList) {
+    final String message = lineListToString(messageList);
+
+    return new ByteArrayInputStream(message.getBytes());
+  }
+
+  private static String lineListToString(List<Line> messageList) {
+    final StringBuilder builder = new StringBuilder();
+
+    for (Line currentLine : messageList) {
+      builder.append(currentLine.toString());
+    }
+
+    return builder.toString();
+  }
+  
+  public static String trimLineListToLength(final List<Line> list, final int length) {
+    final String message = lineListToString(list);
+    final int lastIndex = Math.min(length, message.length());
+
+    return (lastIndex > 0) ? message.substring(0, lastIndex) : "";
+  }
+  
+  public static InputStream convertLineListToInputStream(List<Line> list, int length) {
+    final String message = trimLineListToLength(list, length);
+
+    return new ByteArrayInputStream(message.getBytes());
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/32247295/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchPart.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchPart.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchPart.java
new file mode 100644
index 0000000..104c152
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchPart.java
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.batch.parser;
+
+public interface BatchPart {
+  public Header getHeaders();
+
+  public boolean isStrict();
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/32247295/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchQueryOperation.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchQueryOperation.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchQueryOperation.java
new file mode 100644
index 0000000..5ff7faf
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchQueryOperation.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.batch.parser;
+
+import java.util.List;
+
+import org.apache.olingo.server.core.batch.BatchException;
+import org.apache.olingo.server.core.batch.parser.BufferedReaderIncludingLineEndings.Line;
+
+public class BatchQueryOperation implements BatchPart {
+
+  protected final boolean isStrict;
+  protected Line httpStatusLine;
+  protected Header headers;
+  protected List<Line> body;
+  protected int bodySize;
+  protected List<Line> message;
+
+  public BatchQueryOperation(final List<Line> message, final boolean isStrict) {
+    this.isStrict = isStrict;
+    this.message = message;
+  }
+
+  public BatchQueryOperation parse() throws BatchException {
+    httpStatusLine = consumeHttpStatusLine(message);
+    headers = BatchParserCommon.consumeHeaders(message);
+    BatchParserCommon.consumeBlankLine(message, isStrict);
+    body = message;
+
+    return this;
+  }
+
+  protected Line consumeHttpStatusLine(final List<Line> message) throws BatchException {
+    if (message.size() > 0 && !message.get(0).toString().trim().equals("")) {
+      final Line method = message.get(0);
+      message.remove(0);
+
+      return method;
+    } else {
+      final int line = (message.size() > 0) ? message.get(0).getLineNumber() : 0;
+      throw new BatchException("Missing http request line", BatchException.MessageKeys.INVALID_STATUS_LINE, "" + line);
+    }
+  }
+
+  public Line getHttpStatusLine() {
+    return httpStatusLine;
+  }
+
+  public List<Line> getBody() {
+    return body;
+  }
+
+  public int getBodySize() {
+    return bodySize;
+  }
+
+  @Override
+  public Header getHeaders() {
+    return headers;
+  }
+
+  @Override
+  public boolean isStrict() {
+    return isStrict;
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/32247295/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchRequestPartImpl.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchRequestPartImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchRequestPartImpl.java
new file mode 100644
index 0000000..6c80216
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchRequestPartImpl.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.batch.parser;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.olingo.server.api.ODataRequest;
+import org.apache.olingo.server.api.batch.BatchRequestPart;
+
+public class BatchRequestPartImpl implements BatchRequestPart {
+
+  private List<ODataRequest> requests = new ArrayList<ODataRequest>();
+  private boolean isChangeSet;
+  
+  public BatchRequestPartImpl(final boolean isChangeSet, final List<ODataRequest> requests) {
+    this.isChangeSet = isChangeSet;
+    this.requests = requests;
+  }
+
+  @Override
+  public boolean isChangeSet() {
+    return isChangeSet;
+  }
+
+  @Override
+  public List<ODataRequest> getRequests() {
+    return Collections.unmodifiableList(requests);
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/32247295/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BufferedReaderIncludingLineEndings.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BufferedReaderIncludingLineEndings.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BufferedReaderIncludingLineEndings.java
new file mode 100644
index 0000000..aebf13b
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BufferedReaderIncludingLineEndings.java
@@ -0,0 +1,286 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.batch.parser;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.List;
+
+public class BufferedReaderIncludingLineEndings extends Reader {
+  private static final char CR = '\r';
+  private static final char LF = '\n';
+  private static final int EOF = -1;
+  private static final int BUFFER_SIZE = 8192;
+  private Reader reader;
+  private char[] buffer;
+  private int offset = 0;
+  private int limit = 0;
+
+  public BufferedReaderIncludingLineEndings(final Reader reader) {
+    this(reader, BUFFER_SIZE);
+  }
+
+  public BufferedReaderIncludingLineEndings(final Reader reader, final int bufferSize) {
+    if (bufferSize <= 0) {
+      throw new IllegalArgumentException("Buffer size must be greater than zero.");
+    }
+
+    this.reader = reader;
+    buffer = new char[bufferSize];
+  }
+
+  @Override
+  public int read(final char[] charBuffer, final int bufferOffset, final int length) throws IOException {
+    if ((bufferOffset + length) > charBuffer.length) {
+      throw new IndexOutOfBoundsException("Buffer is too small");
+    }
+
+    if (length < 0 || bufferOffset < 0) {
+      throw new IndexOutOfBoundsException("Offset and length must be grater than zero");
+    }
+
+    // Check if buffer is filled. Return if EOF is reached
+    // Is buffer refill required
+    if (limit == offset || isEOF()) {
+      fillBuffer();
+
+      if (isEOF()) {
+        return EOF;
+      }
+    }
+
+    int bytesRead = 0;
+    int bytesToRead = length;
+    int currentOutputOffset = bufferOffset;
+
+    while (bytesToRead != 0) {
+      // Is buffer refill required?
+      if (limit == offset) {
+        fillBuffer();
+
+        if (isEOF()) {
+          bytesToRead = 0;
+        }
+      }
+
+      if (bytesToRead > 0) {
+        int readByte = Math.min(limit - offset, bytesToRead);
+        bytesRead += readByte;
+        bytesToRead -= readByte;
+
+        for (int i = 0; i < readByte; i++) {
+          charBuffer[currentOutputOffset++] = buffer[offset++];
+        }
+      }
+    }
+
+    return bytesRead;
+  }
+  
+  public List<String> toList() throws IOException {
+    final List<String> result = new ArrayList<String>();
+    String currentLine;
+    
+    while ((currentLine = readLine()) != null) {
+      result.add(currentLine);
+    }
+
+    return result;
+  }
+  
+  public List<Line> toLineList() throws IOException {
+    final List<Line> result = new ArrayList<Line>();
+    String currentLine;
+    int counter = 1;
+
+    while ((currentLine = readLine()) != null) {
+      result.add(new Line(currentLine, counter++));
+    }
+
+    return result;
+  }
+
+  public String readLine() throws IOException {
+    if (limit == EOF) {
+      return null;
+    }
+
+    final StringBuilder stringBuffer = new StringBuilder();
+    boolean foundLineEnd = false; // EOF will be considered as line ending
+
+    while (!foundLineEnd) {
+      // Is buffer refill required?
+      if (limit == offset) {
+        if (fillBuffer() == EOF) {
+          foundLineEnd = true;
+        }
+      }
+
+      if (!foundLineEnd) {
+        char currentChar = buffer[offset++];
+        stringBuffer.append(currentChar);
+
+        if (currentChar == LF) {
+          foundLineEnd = true;
+        } else if (currentChar == CR) {
+          foundLineEnd = true;
+
+          // Check next char. Consume \n if available
+          // Is buffer refill required?
+          if (limit == offset) {
+            fillBuffer();
+          }
+
+          // Check if there is at least one character
+          if (limit != EOF && buffer[offset] == LF) {
+            stringBuffer.append(LF);
+            offset++;
+          }
+        }
+      }
+    }
+
+    return (stringBuffer.length() == 0) ? null : stringBuffer.toString();
+  }
+
+  @Override
+  public void close() throws IOException {
+    reader.close();
+  }
+
+  @Override
+  public boolean ready() throws IOException {
+    // Not EOF and buffer refill is not required
+    return !isEOF() && !(limit == offset);
+  }
+
+  @Override
+  public void reset() throws IOException {
+    throw new IOException("Reset is not supported");
+  }
+
+  @Override
+  public void mark(final int readAheadLimit) throws IOException {
+    throw new IOException("Mark is not supported");
+  }
+
+  @Override
+  public boolean markSupported() {
+    return false;
+  }
+
+  @Override
+  public long skip(final long n) throws IOException {
+    if (n == 0) {
+      return 0;
+    } else if (n < 0) {
+      throw new IllegalArgumentException("skip value is negative");
+    } else {
+      long charactersToSkip = n;
+      long charactersSkiped = 0;
+
+      while (charactersToSkip != 0) {
+        // Is buffer refill required?
+        if (limit == offset) {
+          fillBuffer();
+
+          if (isEOF()) {
+            charactersToSkip = 0;
+          }
+        }
+
+        // Check if more characters are available
+        if (!isEOF()) {
+          int skipChars = (int) Math.min(limit - offset, charactersToSkip);
+
+          charactersSkiped += skipChars;
+          charactersToSkip -= skipChars;
+          offset += skipChars;
+        }
+      }
+
+      return charactersSkiped;
+    }
+  }
+
+  private boolean isEOF() {
+    return limit == EOF;
+  }
+
+  private int fillBuffer() throws IOException {
+    limit = reader.read(buffer, 0, buffer.length);
+    offset = 0;
+
+    return limit;
+  }
+
+  public static class Line {
+    private final int lineNumber;
+    private final String content;
+
+    public Line(final String content, final int lineNumber) {
+      this.content = content;
+      this.lineNumber = lineNumber;
+    }
+
+    public int getLineNumber() {
+      return lineNumber;
+    }
+
+    @Override
+    public String toString() {
+      return content;
+    }
+
+    @Override
+    public int hashCode() {
+      final int prime = 31;
+      int result = 1;
+      result = prime * result + ((content == null) ? 0 : content.hashCode());
+      result = prime * result + lineNumber;
+      return result;
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+      if (this == obj) {
+        return true;
+      }
+      if (obj == null) {
+        return false;
+      }
+      if (getClass() != obj.getClass()) {
+        return false;
+      }
+      Line other = (Line) obj;
+      if (content == null) {
+        if (other.content != null) {
+          return false;
+        }
+      } else if (!content.equals(other.content)) {
+        return false;
+      }
+      if (lineNumber != other.lineNumber) {
+        return false;
+      }
+      return true;
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/32247295/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/Header.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/Header.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/Header.java
new file mode 100644
index 0000000..2ac009d
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/Header.java
@@ -0,0 +1,181 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.batch.parser;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+public class Header implements Iterable<HeaderField> {
+  private final Map<String, HeaderField> headers = new HashMap<String, HeaderField>();
+  private int lineNumber;
+
+  public Header(final int lineNumer) {
+    lineNumber = lineNumer;
+  }
+
+  public void addHeader(final String name, final String value, final int lineNumber) {
+    final HeaderField headerField = getHeaderFieldOrDefault(name, lineNumber);
+    final List<String> headerValues = headerField.getValues();
+
+    if (!headerValues.contains(value)) {
+      headerValues.add(value);
+    }
+  }
+
+  public void addHeader(final String name, final List<String> values, final int lineNumber) {
+    final HeaderField headerField = getHeaderFieldOrDefault(name, lineNumber);
+    final List<String> headerValues = headerField.getValues();
+
+    for (final String value : values) {
+      if (!headerValues.contains(value)) {
+        headerValues.add(value);
+      }
+    }
+  }
+
+  public void replaceHeaderField(final HeaderField headerField) {
+    headers.put(headerField.getFieldName().toLowerCase(Locale.ENGLISH), headerField);
+  }
+
+  public boolean exists(final String name) {
+    final HeaderField field = headers.get(name.toLowerCase(Locale.ENGLISH));
+    
+    return field != null && field.getValues().size() != 0;
+  }
+
+  public boolean isHeaderMatching(final String name, final Pattern pattern) {
+    if (getHeaders(name).size() != 1) {
+      return false;
+    } else {
+      return pattern.matcher(getHeaders(name).get(0)).matches();
+    }
+  }
+
+  public void removeHeader(final String name) {
+    headers.remove(name.toLowerCase(Locale.ENGLISH));
+  }
+
+  public String getHeader(final String name) {
+    final HeaderField headerField = getHeaderField(name);
+
+    return (headerField == null) ? null : headerField.getValue();
+  }
+
+  public String getHeaderNotNull(final String name) {
+    final HeaderField headerField = getHeaderField(name);
+
+    return (headerField == null) ? "" : headerField.getValueNotNull();
+  }
+
+  public List<String> getHeaders(final String name) {
+    final HeaderField headerField = getHeaderField(name);
+
+    return (headerField == null) ? new ArrayList<String>() : headerField.getValues();
+  }
+
+  public HeaderField getHeaderField(final String name) {
+    return headers.get(name.toLowerCase(Locale.ENGLISH));
+  }
+
+  public int getLineNumber() {
+    return lineNumber;
+  }
+
+  public Map<String, String> toSingleMap() {
+    final Map<String, String> singleMap = new HashMap<String, String>();
+
+    for (final String key : headers.keySet()) {
+      HeaderField field = headers.get(key);
+      singleMap.put(field.getFieldName(), getHeader(key));
+    }
+
+    return singleMap;
+  }
+
+  public Map<String, List<String>> toMultiMap() {
+    final Map<String, List<String>> singleMap = new HashMap<String, List<String>>();
+
+    for (final String key : headers.keySet()) {
+      HeaderField field = headers.get(key);
+      singleMap.put(field.getFieldName(), field.getValues());
+    }
+
+    return singleMap;
+  }
+
+  private HeaderField getHeaderFieldOrDefault(final String name, final int lineNumber) {
+    HeaderField headerField = headers.get(name.toLowerCase(Locale.ENGLISH));
+
+    if (headerField == null) {
+      headerField = new HeaderField(name, lineNumber);
+      headers.put(name.toLowerCase(Locale.ENGLISH), headerField);
+    }
+
+    return headerField;
+  }
+
+  @Override
+  public Header clone() {
+    final Header newInstance = new Header(lineNumber);
+
+    for (final String key : headers.keySet()) {
+      newInstance.headers.put(key, headers.get(key).clone());
+    }
+
+    return newInstance;
+  }
+
+  @Override
+  public Iterator<HeaderField> iterator() {
+    return new Iterator<HeaderField>() {
+      Iterator<String> keyIterator = headers.keySet().iterator();
+
+      @Override
+      public boolean hasNext() {
+        return keyIterator.hasNext();
+      }
+
+      @Override
+      public HeaderField next() {
+        return headers.get(keyIterator.next());
+      }
+      
+      @Override
+      public void remove() {
+        throw new UnsupportedOperationException();
+      }
+    };
+  }
+
+  public static List<String> splitValuesByComma(final String headerValue) {
+    final List<String> singleValues = new ArrayList<String>();
+
+    String[] parts = headerValue.split(",");
+    for (final String value : parts) {
+      singleValues.add(value.trim());
+    }
+
+    return singleValues;
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/32247295/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/HeaderField.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/HeaderField.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/HeaderField.java
new file mode 100644
index 0000000..4cad817
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/HeaderField.java
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.batch.parser;
+
+import java.util.ArrayList;
+import java.util.List;
+
+  public class HeaderField implements Cloneable {
+    private final String fieldName;
+    private final List<String> values;
+    private final int lineNumber;
+
+    public HeaderField(final String fieldName, final int lineNumber) {
+      this(fieldName, new ArrayList<String>(), lineNumber);
+    }
+
+    public HeaderField(final String fieldName, final List<String> values, final int lineNumber) {
+      this.fieldName = fieldName;
+      this.values = values;
+      this.lineNumber = lineNumber;
+    }
+
+    public String getFieldName() {
+      return fieldName;
+    }
+
+    public List<String> getValues() {
+      return values;
+    }
+
+    public String getValue() {
+      final StringBuilder result = new StringBuilder();
+
+      for (final String value : values) {
+        result.append(value);
+        result.append(", ");
+      }
+
+      if (result.length() > 0) {
+        result.delete(result.length() - 2, result.length());
+      }
+
+      return result.toString();
+    }
+
+    public String getValueNotNull() {
+      final String value = getValue();
+
+      return (value == null) ? "" : value;
+    }
+
+    @Override
+    public HeaderField clone() {
+      List<String> newValues = new ArrayList<String>();
+      newValues.addAll(values);
+
+      return new HeaderField(fieldName, newValues, lineNumber);
+    }
+
+    public int getLineNumber() {
+      return lineNumber;
+    }
+
+    @Override
+    public int hashCode() {
+      final int prime = 31;
+      int result = 1;
+      result = prime * result + ((fieldName == null) ? 0 : fieldName.hashCode());
+      result = prime * result + lineNumber;
+      result = prime * result + ((values == null) ? 0 : values.hashCode());
+      return result;
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+      if (this == obj) {
+        return true;
+      }
+      if (obj == null) {
+        return false;
+      }
+      if (getClass() != obj.getClass()) {
+        return false;
+      }
+      HeaderField other = (HeaderField) obj;
+      if (fieldName == null) {
+        if (other.fieldName != null) {
+          return false;
+        }
+      } else if (!fieldName.equals(other.fieldName)) {
+        return false;
+      }
+      if (lineNumber != other.lineNumber) {
+        return false;
+      }
+      if (values == null) {
+        if (other.values != null) {
+          return false;
+        }
+      } else if (!values.equals(other.values)) {
+        return false;
+      }
+      return true;
+    }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/32247295/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchRequestTransformator.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchRequestTransformator.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchRequestTransformator.java
new file mode 100644
index 0000000..02c8118
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchRequestTransformator.java
@@ -0,0 +1,191 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.batch.transformator;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.olingo.commons.api.http.HttpHeader;
+import org.apache.olingo.commons.api.http.HttpMethod;
+import org.apache.olingo.server.api.ODataRequest;
+import org.apache.olingo.server.api.batch.BatchParserResult;
+import org.apache.olingo.server.core.batch.BatchException;
+import org.apache.olingo.server.core.batch.BatchException.MessageKeys;
+import org.apache.olingo.server.core.batch.parser.BatchBodyPart;
+import org.apache.olingo.server.core.batch.parser.BatchChangeSetPart;
+import org.apache.olingo.server.core.batch.parser.BatchParserCommon;
+import org.apache.olingo.server.core.batch.parser.BatchPart;
+import org.apache.olingo.server.core.batch.parser.BatchQueryOperation;
+import org.apache.olingo.server.core.batch.parser.BatchRequestPartImpl;
+import org.apache.olingo.server.core.batch.parser.Header;
+import org.apache.olingo.server.core.batch.parser.HeaderField;
+import org.apache.olingo.server.core.batch.transformator.BatchTransformatorCommon.HttpRequestStatusLine;
+
+public class BatchRequestTransformator implements BatchTransformator {
+  private final String baseUri;
+  private final String rawServiceResolutionUri;
+
+  public BatchRequestTransformator(final String baseUri, final String serviceResolutionUri) {
+    this.baseUri = baseUri;
+    this.rawServiceResolutionUri = serviceResolutionUri;
+  }
+
+  @Override
+  public List<BatchParserResult> transform(final BatchBodyPart bodyPart) throws BatchException {
+    final List<ODataRequest> requests = new LinkedList<ODataRequest>();
+    final List<BatchParserResult> resultList = new ArrayList<BatchParserResult>();
+
+    validateBodyPartHeader(bodyPart);
+
+    for (BatchQueryOperation queryOperation : bodyPart.getRequests()) {
+      requests.add(processQueryOperation(bodyPart, baseUri, queryOperation));
+    }
+
+    resultList.add(new BatchRequestPartImpl(bodyPart.isChangeSet(), requests));
+    return resultList;
+  }
+
+  private ODataRequest
+      processQueryOperation(BatchBodyPart bodyPart, String baseUri, BatchQueryOperation queryOperation)
+          throws BatchException {
+    if (bodyPart.isChangeSet()) {
+      BatchQueryOperation encapsulatedQueryOperation = ((BatchChangeSetPart) queryOperation).getRequest();
+      handleContentId(queryOperation, encapsulatedQueryOperation);
+      validateHeader(queryOperation, true);
+
+      return createRequest(encapsulatedQueryOperation, baseUri, bodyPart.isChangeSet());
+    } else {
+      return createRequest(queryOperation, baseUri, bodyPart.isChangeSet());
+    }
+  }
+
+  private void handleContentId(BatchQueryOperation changeRequestPart, BatchQueryOperation request)
+      throws BatchException {
+    final HeaderField contentIdChangeRequestPart = getContentId(changeRequestPart);
+    final HeaderField contentIdRequest = getContentId(request);
+
+    if (contentIdChangeRequestPart == null && contentIdRequest == null) {
+      throw new BatchException("Missing content type", MessageKeys.MISSING_CONTENT_ID, changeRequestPart.getHeaders()
+          .getLineNumber());
+    } else if (contentIdChangeRequestPart != null) {
+      request.getHeaders().replaceHeaderField(contentIdChangeRequestPart);
+    }
+  }
+
+  private HeaderField getContentId(final BatchQueryOperation queryOperation) throws BatchException {
+    final HeaderField contentTypeHeader = queryOperation.getHeaders().getHeaderField(BatchParserCommon.HTTP_CONTENT_ID);
+
+    if (contentTypeHeader != null) {
+      if (contentTypeHeader.getValues().size() == 1) {
+        return contentTypeHeader;
+      } else {
+        throw new BatchException("Invalid header", MessageKeys.INVALID_HEADER, contentTypeHeader.getLineNumber());
+      }
+    }
+
+    return null;
+  }
+
+  private ODataRequest createRequest(BatchQueryOperation operation, String baseUri, boolean isChangeSet)
+      throws BatchException {
+    final HttpRequestStatusLine statusLine =
+        new HttpRequestStatusLine(operation.getHttpStatusLine(), baseUri, rawServiceResolutionUri, operation
+            .getHeaders());
+    statusLine.validateHttpMethod(isChangeSet);
+
+    validateBody(statusLine, operation);
+    InputStream bodyStrean = getBodyStream(operation, statusLine);
+
+    validateForbiddenHeader(operation);
+
+    final ODataRequest request = new ODataRequest();
+    request.setBody(bodyStrean);
+    request.setMethod(statusLine.getMethod());
+    request.setRawBaseUri(statusLine.getRawBaseUri());
+    request.setRawODataPath(statusLine.getRawODataPath());
+    request.setRawQueryPath(statusLine.getRawQueryPath());
+    request.setRawRequestUri(statusLine.getRawRequestUri());
+    request.setRawServiceResolutionUri(statusLine.getRawServiceResolutionUri());
+
+    for (final HeaderField field : operation.getHeaders()) {
+      request.addHeader(field.getFieldName(), field.getValues());
+    }
+
+    return request;
+  }
+
+  private void validateForbiddenHeader(BatchQueryOperation operation) throws BatchException {
+    final Header header = operation.getHeaders();
+
+    if (header.exists(HttpHeader.AUTHORIZATION) || header.exists(BatchParserCommon.HTTP_EXPECT)
+        || header.exists(BatchParserCommon.HTTP_FROM) || header.exists(BatchParserCommon.HTTP_MAX_FORWARDS)
+        || header.exists(BatchParserCommon.HTTP_RANGE) || header.exists(BatchParserCommon.HTTP_TE)) {
+      throw new BatchException("Forbidden header", MessageKeys.FORBIDDEN_HEADER, header.getLineNumber());
+    }
+  }
+
+  private InputStream getBodyStream(BatchQueryOperation operation, HttpRequestStatusLine statusLine)
+      throws BatchException {
+    if (statusLine.getMethod().equals(HttpMethod.GET)) {
+      return new ByteArrayInputStream(new byte[0]);
+    } else {
+      int contentLength = BatchTransformatorCommon.getContentLength(operation.getHeaders());
+
+      if (contentLength == -1) {
+        return BatchParserCommon.convertLineListToInputStream(operation.getBody());
+      } else {
+        return BatchParserCommon.convertLineListToInputStream(operation.getBody(), contentLength);
+      }
+    }
+  }
+
+  private void validateBody(HttpRequestStatusLine statusLine, BatchQueryOperation operation) throws BatchException {
+    if (statusLine.getMethod().equals(HttpMethod.GET) && isUnvalidGetRequestBody(operation)) {
+      throw new BatchException("Invalid request line", MessageKeys.INVALID_CONTENT, statusLine.getLineNumber());
+    }
+  }
+
+  private boolean isUnvalidGetRequestBody(final BatchQueryOperation operation) {
+    return (operation.getBody().size() > 1)
+        || (operation.getBody().size() == 1 && !"".equals(operation.getBody().get(0).toString().trim()));
+  }
+
+  private void validateHeader(BatchPart bodyPart, boolean isChangeSet) throws BatchException {
+    final Header headers = bodyPart.getHeaders();
+
+    BatchTransformatorCommon.validateContentType(headers, BatchParserCommon.PATTERN_CONTENT_TYPE_APPLICATION_HTTP);
+    if (isChangeSet) {
+      BatchTransformatorCommon.validateContentTransferEncoding(headers);
+    }
+  }
+
+  private void validateBodyPartHeader(BatchBodyPart bodyPart) throws BatchException {
+    final Header header = bodyPart.getHeaders();
+
+    if (bodyPart.isChangeSet()) {
+      BatchTransformatorCommon.validateContentType(header, BatchParserCommon.PATTERN_MULTIPART_BOUNDARY);
+    } else {
+      BatchTransformatorCommon.validateContentTransferEncoding(header);
+      BatchTransformatorCommon.validateContentType(header, BatchParserCommon.PATTERN_CONTENT_TYPE_APPLICATION_HTTP);
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/32247295/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchTransformator.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchTransformator.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchTransformator.java
new file mode 100644
index 0000000..becb6c7
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchTransformator.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.batch.transformator;
+
+import java.util.List;
+
+import org.apache.olingo.server.api.batch.BatchParserResult;
+import org.apache.olingo.server.core.batch.BatchException;
+import org.apache.olingo.server.core.batch.parser.BatchBodyPart;
+
+public interface BatchTransformator {
+  public List<BatchParserResult> transform(BatchBodyPart bodyPart) throws BatchException;
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/32247295/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchTransformatorCommon.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchTransformatorCommon.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchTransformatorCommon.java
new file mode 100644
index 0000000..a351cca
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/BatchTransformatorCommon.java
@@ -0,0 +1,250 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.batch.transformator;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.olingo.commons.api.http.HttpContentType;
+import org.apache.olingo.commons.api.http.HttpHeader;
+import org.apache.olingo.commons.api.http.HttpMethod;
+import org.apache.olingo.server.core.batch.BatchException;
+import org.apache.olingo.server.core.batch.BatchException.MessageKeys;
+import org.apache.olingo.server.core.batch.parser.BatchParserCommon;
+import org.apache.olingo.server.core.batch.parser.BufferedReaderIncludingLineEndings.Line;
+import org.apache.olingo.server.core.batch.parser.Header;
+import org.apache.olingo.server.core.batch.parser.HeaderField;
+
+public class BatchTransformatorCommon {
+
+  public static void validateContentType(final Header headers, final Pattern pattern) throws BatchException {
+    List<String> contentTypes = headers.getHeaders(HttpHeader.CONTENT_TYPE);
+
+    if (contentTypes.size() == 0) {
+      throw new BatchException("Missing content type", MessageKeys.MISSING_CONTENT_TYPE, headers.getLineNumber());
+    }
+    if (!headers.isHeaderMatching(HttpHeader.CONTENT_TYPE, pattern)) {
+
+      throw new BatchException("Invalid content type", MessageKeys.INVALID_CONTENT_TYPE,
+          HttpContentType.MULTIPART_MIXED + " or " + HttpContentType.APPLICATION_HTTP);
+    }
+  }
+
+  public static void validateContentTransferEncoding(Header headers) throws BatchException {
+    final HeaderField contentTransferField = headers.getHeaderField(BatchParserCommon.HTTP_CONTENT_TRANSFER_ENCODING);
+
+    if (contentTransferField != null) {
+      final List<String> contentTransferValues = contentTransferField.getValues();
+      if (contentTransferValues.size() == 1) {
+        String encoding = contentTransferValues.get(0);
+
+        if (!BatchParserCommon.BINARY_ENCODING.equalsIgnoreCase(encoding)) {
+          throw new BatchException("Invalid content transfer encoding", MessageKeys.INVALID_CONTENT_TRANSFER_ENCODING,
+              headers.getLineNumber());
+        }
+      } else {
+        throw new BatchException("Invalid header", MessageKeys.INVALID_HEADER, headers.getLineNumber());
+      }
+    } else {
+      throw new BatchException("Missing mandatory content transfer encoding",
+          MessageKeys.MISSING_CONTENT_TRANSFER_ENCODING,
+          headers.getLineNumber());
+    }
+  }
+
+  public static int getContentLength(Header headers) throws BatchException {
+    final HeaderField contentLengthField = headers.getHeaderField(HttpHeader.CONTENT_LENGTH);
+
+    if (contentLengthField != null && contentLengthField.getValues().size() == 1) {
+      final List<String> contentLengthValues = contentLengthField.getValues();
+
+      try {
+        int contentLength = Integer.parseInt(contentLengthValues.get(0));
+
+        if (contentLength < 0) {
+          throw new BatchException("Invalid content length", MessageKeys.INVALID_CONTENT_LENGTH, contentLengthField
+              .getLineNumber());
+        }
+
+        return contentLength;
+      } catch (NumberFormatException e) {
+        throw new BatchException("Invalid header", MessageKeys.INVALID_HEADER, contentLengthField.getLineNumber());
+      }
+    }
+
+    return -1;
+  }
+
+  public static class HttpRequestStatusLine {
+    private static final Pattern PATTERN_RELATIVE_URI = Pattern.compile("([^/][^?]*)(?:\\?(.*))?");
+    private static final Pattern PATTERN_ABSOLUTE_URI_WITH_HOST = Pattern.compile("(/[^?]*)(?:\\?(.*))?");
+    private static final Pattern PATTERN_ABSOLUTE_URI = Pattern.compile("(http[s]?://[^?]*)(?:\\?(.*))?");
+
+    private static final Set<String> HTTP_BATCH_METHODS = new HashSet<String>(Arrays.asList(new String[] { "GET" }));
+    private static final Set<String> HTTP_CHANGE_SET_METHODS = new HashSet<String>(Arrays.asList(new String[] { "POST",
+        "PUT", "DELETE", "MERGE", "PATCH" }));
+    private static final String HTTP_VERSION = "HTTP/1.1";
+
+    final private Line statusLine;
+    final String requestBaseUri;
+
+    private HttpMethod method;
+    private String httpVersion;
+    private String rawServiceResolutionUri;
+    private String rawQueryPath;
+    private String rawODataPath;
+    private String rawBaseUri;
+    private Header header;
+    private String rawRequestUri;
+
+    public HttpRequestStatusLine(final Line httpStatusLine, final String baseUri, final String serviceResolutionUri,
+        final Header requestHeader)
+        throws BatchException {
+      statusLine = httpStatusLine;
+      requestBaseUri = baseUri;
+      header = requestHeader;
+      rawServiceResolutionUri = serviceResolutionUri;
+      
+      parse();
+    }
+
+    private void parse() throws BatchException {
+      final String[] parts = statusLine.toString().split(" ");
+
+      if (parts.length == 3) {
+        method = parseMethod(parts[0]);
+        parseRawPaths(parts[1]);
+        httpVersion = parseHttpVersion(parts[2]);
+      } else {
+        throw new BatchException("Invalid status line", MessageKeys.INVALID_STATUS_LINE, statusLine.getLineNumber());
+      }
+    }
+
+    private HttpMethod parseMethod(final String method) throws BatchException {
+      try {
+        return HttpMethod.valueOf(method.trim());
+      } catch (IllegalArgumentException e) {
+        throw new BatchException("Illegal http method", MessageKeys.INVALID_METHOD, statusLine.getLineNumber());
+      }
+    }
+
+    private void parseRawPaths(final String rawUrl) throws BatchException {
+      final Matcher absoluteUriMatcher = PATTERN_ABSOLUTE_URI.matcher(rawUrl);
+      final Matcher absoluteUriWtithHostMatcher = PATTERN_ABSOLUTE_URI_WITH_HOST.matcher(rawUrl);
+      final Matcher relativeUriMatcher = PATTERN_RELATIVE_URI.matcher(rawUrl);
+
+      if (absoluteUriMatcher.matches()) {
+        buildUri(absoluteUriMatcher.group(1), absoluteUriMatcher.group(2));
+
+      } else if (absoluteUriWtithHostMatcher.matches()) {
+        final List<String> hostHeader = header.getHeaders(HttpHeader.HOST);
+        if (hostHeader.size() == 1) {
+          buildUri(hostHeader.get(0) + absoluteUriWtithHostMatcher.group(1), absoluteUriWtithHostMatcher.group(2));
+        } else {
+          throw new BatchException("Exactly one host header is required", MessageKeys.MISSING_MANDATORY_HEADER,
+              statusLine.getLineNumber());
+        }
+
+      } else if (relativeUriMatcher.matches()) {
+        buildUri(requestBaseUri + "/" + relativeUriMatcher.group(1), relativeUriMatcher.group(2));
+
+      } else {
+        throw new BatchException("Invalid uri", MessageKeys.INVALID_URI, statusLine.getLineNumber());
+      }
+    }
+
+    private void buildUri(final String resourceUri, final String queryOptions) throws BatchException {
+      if(!resourceUri.startsWith(requestBaseUri)) {
+        throw new BatchException("Host do not match", MessageKeys.INVALID_URI, statusLine.getLineNumber());
+      }
+      
+      final int oDataPathIndex = resourceUri.indexOf(requestBaseUri);
+
+      rawBaseUri = requestBaseUri;
+      rawODataPath = resourceUri.substring(oDataPathIndex + requestBaseUri.length());
+      rawServiceResolutionUri = "";
+      rawRequestUri = requestBaseUri + rawODataPath;
+
+      if (queryOptions != null) {
+        rawRequestUri += "?" + queryOptions;
+        rawQueryPath = queryOptions;
+      } else {
+        rawQueryPath = "";
+      }
+    }
+
+    private String parseHttpVersion(final String httpVersion) throws BatchException {
+      if (!HTTP_VERSION.equals(httpVersion.trim())) {
+        throw new BatchException("Invalid http version", MessageKeys.INVALID_HTTP_VERSION, statusLine.getLineNumber());
+      } else {
+        return HTTP_VERSION;
+      }
+    }
+
+    public void validateHttpMethod(boolean isChangeSet) throws BatchException {
+      Set<String> validMethods = (isChangeSet) ? HTTP_CHANGE_SET_METHODS : HTTP_BATCH_METHODS;
+
+      if (!validMethods.contains(getMethod().toString())) {
+        if (isChangeSet) {
+          throw new BatchException("Invalid change set method", MessageKeys.INVALID_CHANGESET_METHOD, statusLine
+              .getLineNumber());
+        } else {
+          throw new BatchException("Invalid query operation method", MessageKeys.INVALID_QUERY_OPERATION_METHOD,
+              statusLine.getLineNumber());
+        }
+      }
+    }
+
+    public HttpMethod getMethod() {
+      return method;
+    }
+
+    public String getHttpVersion() {
+      return httpVersion;
+    }
+
+    public int getLineNumber() {
+      return statusLine.getLineNumber();
+    }
+
+    public String getRawBaseUri() {
+      return rawBaseUri;
+    }
+
+    public String getRawODataPath() {
+      return rawODataPath;
+    }
+
+    public String getRawQueryPath() {
+      return rawQueryPath;
+    }
+
+    public String getRawRequestUri() {
+      return rawRequestUri;
+    }
+
+    public String getRawServiceResolutionUri() {
+      return rawServiceResolutionUri;
+    }
+  }
+}


[10/22] olingo-odata4 git commit: Tests added

Posted by ch...@apache.org.
Tests added

Signed-off-by: Christian Amend <ch...@apache.org>


Project: http://git-wip-us.apache.org/repos/asf/olingo-odata4/repo
Commit: http://git-wip-us.apache.org/repos/asf/olingo-odata4/commit/bb4b5541
Tree: http://git-wip-us.apache.org/repos/asf/olingo-odata4/tree/bb4b5541
Diff: http://git-wip-us.apache.org/repos/asf/olingo-odata4/diff/bb4b5541

Branch: refs/heads/master
Commit: bb4b554116382e61519c6f7e98010aaff31eb441
Parents: 6c7d11f
Author: Christian Holzer <c....@sap.com>
Authored: Wed Nov 5 16:18:40 2014 +0100
Committer: Christian Amend <ch...@apache.org>
Committed: Thu Nov 13 17:10:58 2014 +0100

----------------------------------------------------------------------
 .../batch/handler/BatchChangeSetSorter.java     |  7 +--
 .../batch/handler/MockedBatchHandlerTest.java   | 56 +++++++++++++++++++-
 2 files changed, 59 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/bb4b5541/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchChangeSetSorter.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchChangeSetSorter.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchChangeSetSorter.java
index 408159e..5e1c276 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchChangeSetSorter.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchChangeSetSorter.java
@@ -99,10 +99,11 @@ public class BatchChangeSetSorter {
   }
 
   private List<ODataRequest> getRequestsWithoutReferences() {
-    final List<ODataRequest> requestsWithoutReference = requestReferenceMapping.get(null);
+    List<ODataRequest> requests = requestReferenceMapping.get(null);
     requestReferenceMapping.remove(null);
-
-    return requestsWithoutReference;
+    
+    requests = (requests == null) ? new ArrayList<ODataRequest>() : requests;
+    return requests;
   }
 
   private void addRequestsToKnownContentIds(List<ODataRequest> requestsWithoutReference) {

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/bb4b5541/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/MockedBatchHandlerTest.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/MockedBatchHandlerTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/MockedBatchHandlerTest.java
index d772fbc..e1864e2 100644
--- a/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/MockedBatchHandlerTest.java
+++ b/lib/server-core/src/test/java/org/apache/olingo/server/core/batch/handler/MockedBatchHandlerTest.java
@@ -442,7 +442,61 @@ public class MockedBatchHandlerTest {
 
     return contentId;
   }
-
+  
+  @Test(expected=BatchException.class)
+  public void testInvalidMethod() throws UnsupportedEncodingException, BatchException {
+    final String content = ""
+        + "--batch_12345" + CRLF
+        + "Content-Type: multipart/mixed; boundary=changeset_12345" + CRLF
+        + CRLF
+        + "--changeset_12345" + CRLF
+        + "Content-Type: application/http" + CRLF
+        + "Content-Transfer-Encoding: binary" + CRLF
+        + "Content-Id: 1" + CRLF
+        + CRLF
+        + "PUT ESAllPrim(1) HTTP/1.1" + CRLF 
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + CRLF
+        + CRLF
+        + "--changeset_12345--" + CRLF
+        + CRLF
+        + "--batch_12345--";
+    
+    final Map<String, List<String>> header = getMimeHeader();
+    final ODataResponse response = new ODataResponse();
+    final ODataRequest request = buildODataRequest(content, header);
+    request.setMethod(HttpMethod.GET);
+    
+    batchHandler.process(request, response, true);
+  }
+  
+  @Test(expected=BatchException.class)
+  public void testInvalidContentType() throws UnsupportedEncodingException, BatchException {
+    final String content = ""
+        + "--batch_12345" + CRLF
+        + "Content-Type: multipart/mixed; boundary=changeset_12345" + CRLF
+        + CRLF
+        + "--changeset_12345" + CRLF
+        + "Content-Type: application/http" + CRLF
+        + "Content-Transfer-Encoding: binary" + CRLF
+        + "Content-Id: 1" + CRLF
+        + CRLF
+        + "PUT ESAllPrim(1) HTTP/1.1" + CRLF 
+        + "Content-Type: application/json;odata=verbose" + CRLF
+        + CRLF
+        + CRLF
+        + "--changeset_12345--" + CRLF
+        + CRLF
+        + "--batch_12345--";
+    
+    final Map<String, List<String>> header = new HashMap<String, List<String>>();
+    header.put(HttpHeader.CONTENT_TYPE, Arrays.asList(new String[] { "application/http" }));
+    final ODataResponse response = new ODataResponse();
+    final ODataRequest request = buildODataRequest(content, header);
+    
+    batchHandler.process(request, response, true);
+  }
+  
   /*
    * Helper methods
    */


[02/22] olingo-odata4 git commit: [OLINGO-472] BatchParser first draft

Posted by ch...@apache.org.
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/32247295/lib/server-core/src/test/resources/batchLarge.batch
----------------------------------------------------------------------
diff --git a/lib/server-core/src/test/resources/batchLarge.batch b/lib/server-core/src/test/resources/batchLarge.batch
new file mode 100644
index 0000000..faadea1
--- /dev/null
+++ b/lib/server-core/src/test/resources/batchLarge.batch
@@ -0,0 +1,2422 @@
+--batch_8194-cf13-1f56
+Content-Type: application/http
+Content-Transfer-Encoding: binary
+
+GET Employees HTTP/1.1
+Host: http://localhost/odata
+Connection: keep-alive
+Accept: application/atom+xml
+MaxDataServiceVersion: 2.0
+DataServiceVersion: 2.0
+Content-Type: application/atom+xml
+Accept-Encoding: gzip,deflate
+Accept-Language: de-DE,de;q=0.8,en-US;q=0.6,en;q=0.4
+
+
+--batch_8194-cf13-1f56
+Content-Type: application/http
+Content-Transfer-Encoding: binary
+
+GET Employees HTTP/1.1
+Host: http://localhost/odata
+Connection: keep-alive
+Accept: application/atom+xml
+MaxDataServiceVersion: 2.0
+DataServiceVersion: 2.0
+Content-Type: application/atom+xml
+Accept-Encoding: gzip,deflate
+Accept-Language: de-DE,de;q=0.8,en-US;q=0.6,en;q=0.4
+
+
+--batch_8194-cf13-1f56
+Content-Type: application/http
+Content-Transfer-Encoding: binary
+
+GET Employees HTTP/1.1
+Host: http://localhost/odata
+Connection: keep-alive
+Accept: application/atom+xml
+MaxDataServiceVersion: 2.0
+DataServiceVersion: 2.0
+Content-Type: application/atom+xml
+Accept-Encoding: gzip,deflate
+Accept-Language: de-DE,de;q=0.8,en-US;q=0.6,en;q=0.4
+
+
+--batch_8194-cf13-1f56
+Content-Type: multipart/mixed; boundary=changeset_f980-1cb6-94dd
+
+--changeset_f980-1cb6-94dd
+Content-Type: application/http
+Content-Transfer-Encoding: binary
+Content-ID: 1
+
+POST Employees HTTP/1.1
+Host: http://localhost/odata
+Connection: keep-alive
+Content-Type: application/atom+xml
+Accept: */*
+Accept-Encoding: gzip,deflate
+Accept-Language: de-DE,de;q=0.8,en-US;q=0.6,en;q=0.4
+
+<entry xmlns="http://www.w3.org/2005/Atom"
+  xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
+  xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
+  xml:base="http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/"
+  m:etag="W/2">
+  <id>
+    http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/Rooms('2')
+  </id>
+  <title type="text">Room 2</title>
+  <updated>2013-04-03T10:53:26.021+02:00</updated>
+  <category term="RefScenario.Room"
+    scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
+  <link href="Rooms('2')" rel="edit" title="Room" />
+  <link href="Rooms('2')/nr_Employees"
+    rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/nr_Employees"
+    title="nr_Employees" type="application/atom+xml; type=feed">
+    <m:inline>
+      <feed xmlns="http://www.w3.org/2005/Atom"
+        xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
+        xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
+        xml:base="http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/">
+        <id>
+          http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/Employees
+        </id>
+        <title type="text">Employees</title>
+        <updated>2013-04-03T10:53:26.024+02:00</updated>
+        <author>
+          <name />
+        </author>
+        <link href="Employees" rel="self" title="Employees" />
+        <entry>
+          <id>
+            http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/Employees('2')
+          </id>
+          <title type="text">Frederic Fall</title>
+          <updated>2003-07-01T00:00:00Z</updated>
+          <category term="RefScenario.Employee"
+            scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
+          <link href="Employees('2')" rel="edit" title="Employee" />
+          <link href="Employees('2')/$value" rel="edit-media"
+            type="application/octet-stream" />
+          <link href="Employees('2')/ne_Manager"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Manager"
+            title="ne_Manager" type="application/atom+xml; type=entry" />
+          <link href="Employees('2')/ne_Team"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Team"
+            title="ne_Team" type="application/atom+xml; type=entry" />
+          <link href="Employees('2')/ne_Room"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Room"
+            title="ne_Room" type="application/atom+xml; type=entry" />
+          <content type="application/octet-stream" src="Employees('2')/$value" />
+          <m:properties>
+            <d:EmployeeId>2</d:EmployeeId>
+            <d:EmployeeName>Frederic Fall</d:EmployeeName>
+            <d:ManagerId>1</d:ManagerId>
+            <d:RoomId>2</d:RoomId>
+            <d:TeamId>1</d:TeamId>
+            <d:Location m:type="RefScenario.c_Location">
+              <d:City m:type="RefScenario.c_City">
+                <d:PostalCode>69190</d:PostalCode>
+                <d:CityName>Walldorf</d:CityName>
+              </d:City>
+              <d:Country>Germany</d:Country>
+            </d:Location>
+            <d:Age>32</d:Age>
+            <d:EntryDate>2003-07-01T00:00:00</d:EntryDate>
+            <d:ImageUrl>Employees('2')/$value</d:ImageUrl>
+          </m:properties>
+        </entry>
+        <entry>
+          <id>
+            http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/Employees('3')
+          </id>
+          <title type="text">Jonathan Smith</title>
+          <updated>2013-04-03T10:53:26.025+02:00</updated>
+          <category term="RefScenario.Employee"
+            scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
+          <link href="Employees('3')" rel="edit" title="Employee" />
+          <link href="Employees('3')/$value" rel="edit-media"
+            type="application/octet-stream" />
+          <link href="Employees('3')/ne_Manager"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Manager"
+            title="ne_Manager" type="application/atom+xml; type=entry" />
+          <link href="Employees('3')/ne_Team"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Team"
+            title="ne_Team" type="application/atom+xml; type=entry" />
+          <link href="Employees('3')/ne_Room"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Room"
+            title="ne_Room" type="application/atom+xml; type=entry" />
+          <content type="application/octet-stream" src="Employees('3')/$value" />
+          <m:properties>
+            <d:EmployeeId>3</d:EmployeeId>
+            <d:EmployeeName>Jonathan Smith</d:EmployeeName>
+            <d:ManagerId>1</d:ManagerId>
+            <d:RoomId>2</d:RoomId>
+            <d:TeamId>1</d:TeamId>
+            <d:Location m:type="RefScenario.c_Location">
+              <d:City m:type="RefScenario.c_City">
+                <d:PostalCode>69190</d:PostalCode>
+                <d:CityName>Walldorf</d:CityName>
+              </d:City>
+              <d:Country>Germany</d:Country>
+            </d:Location>
+            <d:Age>56</d:Age>
+            <d:EntryDate m:null="true" />
+            <d:ImageUrl>Employees('3')/$value</d:ImageUrl>
+          </m:properties>
+        </entry>
+        <entry>
+          <id>
+            http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/Employees('4')
+          </id>
+          <title type="text">Peter Burke</title>
+          <updated>2004-09-12T00:00:00Z</updated>
+          <category term="RefScenario.Employee"
+            scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
+          <link href="Employees('4')" rel="edit" title="Employee" />
+          <link href="Employees('4')/$value" rel="edit-media"
+            type="application/octet-stream" />
+          <link href="Employees('4')/ne_Manager"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Manager"
+            title="ne_Manager" type="application/atom+xml; type=entry" />
+          <link href="Employees('4')/ne_Team"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Team"
+            title="ne_Team" type="application/atom+xml; type=entry" />
+          <link href="Employees('4')/ne_Room"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Room"
+            title="ne_Room" type="application/atom+xml; type=entry" />
+          <content type="application/octet-stream" src="Employees('4')/$value" />
+          <m:properties>
+            <d:EmployeeId>4</d:EmployeeId>
+            <d:EmployeeName>Peter Burke</d:EmployeeName>
+            <d:ManagerId>3</d:ManagerId>
+            <d:RoomId>2</d:RoomId>
+            <d:TeamId>2</d:TeamId>
+            <d:Location m:type="RefScenario.c_Location">
+              <d:City m:type="RefScenario.c_City">
+                <d:PostalCode>69190</d:PostalCode>
+                <d:CityName>Walldorf</d:CityName>
+              </d:City>
+              <d:Country>Germany</d:Country>
+            </d:Location>
+            <d:Age>39</d:Age>
+            <d:EntryDate>2004-09-12T00:00:00</d:EntryDate>
+            <d:ImageUrl>Employees('4')/$value</d:ImageUrl>
+          </m:properties>
+        </entry>
+        <entry>
+          <id>
+            http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/Employees('6')
+          </id>
+          <title type="text">Susan Bay</title>
+          <updated>2010-12-01T00:00:00Z</updated>
+          <category term="RefScenario.Employee"
+            scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
+          <link href="Employees('6')" rel="edit" title="Employee" />
+          <link href="Employees('6')/$value" rel="edit-media"
+            type="application/octet-stream" />
+          <link href="Employees('6')/ne_Manager"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Manager"
+            title="ne_Manager" type="application/atom+xml; type=entry" />
+          <link href="Employees('6')/ne_Team"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Team"
+            title="ne_Team" type="application/atom+xml; type=entry" />
+          <link href="Employees('6')/ne_Room"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Room"
+            title="ne_Room" type="application/atom+xml; type=entry" />
+          <content type="application/octet-stream" src="Employees('6')/$value" />
+          <m:properties>
+            <d:EmployeeId>6</d:EmployeeId>
+            <d:EmployeeName>Susan Bay</d:EmployeeName>
+            <d:ManagerId>1</d:ManagerId>
+            <d:RoomId>2</d:RoomId>
+            <d:TeamId>3</d:TeamId>
+            <d:Location m:type="RefScenario.c_Location">
+              <d:City m:type="RefScenario.c_City">
+                <d:PostalCode>69190</d:PostalCode>
+                <d:CityName>Walldorf</d:CityName>
+              </d:City>
+              <d:Country>Germany</d:Country>
+            </d:Location>
+            <d:Age>29</d:Age>
+            <d:EntryDate>2010-12-01T00:00:00</d:EntryDate>
+            <d:ImageUrl>Employees('6')/$value</d:ImageUrl>
+          </m:properties>
+        </entry>
+      </feed>
+    </m:inline>
+  </link>
+  <link href="Rooms('2')/nr_Building"
+    rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/nr_Building"
+    title="nr_Building" type="application/atom+xml; type=entry" />
+  <content type="application/xml">
+    <m:properties>
+      <d:Id>2</d:Id>
+      <d:Name>Room 2</d:Name>
+      <d:Seats>5</d:Seats>
+      <d:Version>2</d:Version>
+    </m:properties>
+  </content>
+</entry>
+
+--changeset_f980-1cb6-94dd
+Content-Type: application/http
+Content-Transfer-Encoding: binary
+Content-ID: 1
+
+POST Employees HTTP/1.1
+Host: http://localhost/odata
+Connection: keep-alive
+Content-Type: application/atom+xml
+Accept: */*
+Accept-Encoding: gzip,deflate
+Accept-Language: de-DE,de;q=0.8,en-US;q=0.6,en;q=0.4
+
+<entry xmlns="http://www.w3.org/2005/Atom"
+  xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
+  xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
+  xml:base="http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/"
+  m:etag="W/2">
+  <id>
+    http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/Rooms('2')
+  </id>
+  <title type="text">Room 2</title>
+  <updated>2013-04-03T10:53:26.021+02:00</updated>
+  <category term="RefScenario.Room"
+    scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
+  <link href="Rooms('2')" rel="edit" title="Room" />
+  <link href="Rooms('2')/nr_Employees"
+    rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/nr_Employees"
+    title="nr_Employees" type="application/atom+xml; type=feed">
+    <m:inline>
+      <feed xmlns="http://www.w3.org/2005/Atom"
+        xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
+        xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
+        xml:base="http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/">
+        <id>
+          http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/Employees
+        </id>
+        <title type="text">Employees</title>
+        <updated>2013-04-03T10:53:26.024+02:00</updated>
+        <author>
+          <name />
+        </author>
+        <link href="Employees" rel="self" title="Employees" />
+        <entry>
+          <id>
+            http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/Employees('2')
+          </id>
+          <title type="text">Frederic Fall</title>
+          <updated>2003-07-01T00:00:00Z</updated>
+          <category term="RefScenario.Employee"
+            scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
+          <link href="Employees('2')" rel="edit" title="Employee" />
+          <link href="Employees('2')/$value" rel="edit-media"
+            type="application/octet-stream" />
+          <link href="Employees('2')/ne_Manager"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Manager"
+            title="ne_Manager" type="application/atom+xml; type=entry" />
+          <link href="Employees('2')/ne_Team"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Team"
+            title="ne_Team" type="application/atom+xml; type=entry" />
+          <link href="Employees('2')/ne_Room"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Room"
+            title="ne_Room" type="application/atom+xml; type=entry" />
+          <content type="application/octet-stream" src="Employees('2')/$value" />
+          <m:properties>
+            <d:EmployeeId>2</d:EmployeeId>
+            <d:EmployeeName>Frederic Fall</d:EmployeeName>
+            <d:ManagerId>1</d:ManagerId>
+            <d:RoomId>2</d:RoomId>
+            <d:TeamId>1</d:TeamId>
+            <d:Location m:type="RefScenario.c_Location">
+              <d:City m:type="RefScenario.c_City">
+                <d:PostalCode>69190</d:PostalCode>
+                <d:CityName>Walldorf</d:CityName>
+              </d:City>
+              <d:Country>Germany</d:Country>
+            </d:Location>
+            <d:Age>32</d:Age>
+            <d:EntryDate>2003-07-01T00:00:00</d:EntryDate>
+            <d:ImageUrl>Employees('2')/$value</d:ImageUrl>
+          </m:properties>
+        </entry>
+        <entry>
+          <id>
+            http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/Employees('3')
+          </id>
+          <title type="text">Jonathan Smith</title>
+          <updated>2013-04-03T10:53:26.025+02:00</updated>
+          <category term="RefScenario.Employee"
+            scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
+          <link href="Employees('3')" rel="edit" title="Employee" />
+          <link href="Employees('3')/$value" rel="edit-media"
+            type="application/octet-stream" />
+          <link href="Employees('3')/ne_Manager"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Manager"
+            title="ne_Manager" type="application/atom+xml; type=entry" />
+          <link href="Employees('3')/ne_Team"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Team"
+            title="ne_Team" type="application/atom+xml; type=entry" />
+          <link href="Employees('3')/ne_Room"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Room"
+            title="ne_Room" type="application/atom+xml; type=entry" />
+          <content type="application/octet-stream" src="Employees('3')/$value" />
+          <m:properties>
+            <d:EmployeeId>3</d:EmployeeId>
+            <d:EmployeeName>Jonathan Smith</d:EmployeeName>
+            <d:ManagerId>1</d:ManagerId>
+            <d:RoomId>2</d:RoomId>
+            <d:TeamId>1</d:TeamId>
+            <d:Location m:type="RefScenario.c_Location">
+              <d:City m:type="RefScenario.c_City">
+                <d:PostalCode>69190</d:PostalCode>
+                <d:CityName>Walldorf</d:CityName>
+              </d:City>
+              <d:Country>Germany</d:Country>
+            </d:Location>
+            <d:Age>56</d:Age>
+            <d:EntryDate m:null="true" />
+            <d:ImageUrl>Employees('3')/$value</d:ImageUrl>
+          </m:properties>
+        </entry>
+        <entry>
+          <id>
+            http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/Employees('4')
+          </id>
+          <title type="text">Peter Burke</title>
+          <updated>2004-09-12T00:00:00Z</updated>
+          <category term="RefScenario.Employee"
+            scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
+          <link href="Employees('4')" rel="edit" title="Employee" />
+          <link href="Employees('4')/$value" rel="edit-media"
+            type="application/octet-stream" />
+          <link href="Employees('4')/ne_Manager"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Manager"
+            title="ne_Manager" type="application/atom+xml; type=entry" />
+          <link href="Employees('4')/ne_Team"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Team"
+            title="ne_Team" type="application/atom+xml; type=entry" />
+          <link href="Employees('4')/ne_Room"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Room"
+            title="ne_Room" type="application/atom+xml; type=entry" />
+          <content type="application/octet-stream" src="Employees('4')/$value" />
+          <m:properties>
+            <d:EmployeeId>4</d:EmployeeId>
+            <d:EmployeeName>Peter Burke</d:EmployeeName>
+            <d:ManagerId>3</d:ManagerId>
+            <d:RoomId>2</d:RoomId>
+            <d:TeamId>2</d:TeamId>
+            <d:Location m:type="RefScenario.c_Location">
+              <d:City m:type="RefScenario.c_City">
+                <d:PostalCode>69190</d:PostalCode>
+                <d:CityName>Walldorf</d:CityName>
+              </d:City>
+              <d:Country>Germany</d:Country>
+            </d:Location>
+            <d:Age>39</d:Age>
+            <d:EntryDate>2004-09-12T00:00:00</d:EntryDate>
+            <d:ImageUrl>Employees('4')/$value</d:ImageUrl>
+          </m:properties>
+        </entry>
+        <entry>
+          <id>
+            http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/Employees('6')
+          </id>
+          <title type="text">Susan Bay</title>
+          <updated>2010-12-01T00:00:00Z</updated>
+          <category term="RefScenario.Employee"
+            scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
+          <link href="Employees('6')" rel="edit" title="Employee" />
+          <link href="Employees('6')/$value" rel="edit-media"
+            type="application/octet-stream" />
+          <link href="Employees('6')/ne_Manager"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Manager"
+            title="ne_Manager" type="application/atom+xml; type=entry" />
+          <link href="Employees('6')/ne_Team"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Team"
+            title="ne_Team" type="application/atom+xml; type=entry" />
+          <link href="Employees('6')/ne_Room"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Room"
+            title="ne_Room" type="application/atom+xml; type=entry" />
+          <content type="application/octet-stream" src="Employees('6')/$value" />
+          <m:properties>
+            <d:EmployeeId>6</d:EmployeeId>
+            <d:EmployeeName>Susan Bay</d:EmployeeName>
+            <d:ManagerId>1</d:ManagerId>
+            <d:RoomId>2</d:RoomId>
+            <d:TeamId>3</d:TeamId>
+            <d:Location m:type="RefScenario.c_Location">
+              <d:City m:type="RefScenario.c_City">
+                <d:PostalCode>69190</d:PostalCode>
+                <d:CityName>Walldorf</d:CityName>
+              </d:City>
+              <d:Country>Germany</d:Country>
+            </d:Location>
+            <d:Age>29</d:Age>
+            <d:EntryDate>2010-12-01T00:00:00</d:EntryDate>
+            <d:ImageUrl>Employees('6')/$value</d:ImageUrl>
+          </m:properties>
+        </entry>
+      </feed>
+    </m:inline>
+  </link>
+  <link href="Rooms('2')/nr_Building"
+    rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/nr_Building"
+    title="nr_Building" type="application/atom+xml; type=entry" />
+  <content type="application/xml">
+    <m:properties>
+      <d:Id>2</d:Id>
+      <d:Name>Room 2</d:Name>
+      <d:Seats>5</d:Seats>
+      <d:Version>2</d:Version>
+    </m:properties>
+  </content>
+</entry>
+
+--changeset_f980-1cb6-94dd
+Content-Type: application/http
+Content-Transfer-Encoding: binary
+Content-ID: 1
+
+PUT Employees('1') HTTP/1.1
+Host: http://localhost/odata
+Connection: keep-alive
+
+Content-Type: application/atom+xml
+Accept: */*
+Accept-Encoding: gzip,deflate
+Accept-Language: de-DE,de;q=0.8,en-US;q=0.6,en;q=0.4
+
+<?xml version='1.0' encoding='utf-8'?>
+<entry xmlns="http://www.w3.org/2005/Atom"
+  xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
+  xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
+  xml:base="http://localhost:19000/abc/EntryXmlChangeTest/">
+  <id>http://localhost:19000/abc/EntryXmlChangeTest/Employees('9')</id>
+  <title type="text">Mister X</title>
+  <updated m:null='true'>Z</updated>
+  <category term="RefScenario.Employee"
+    scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
+  <content type="image/jpeg" src="Employees('1')/$value" />
+  <m:properties>
+    <d:EmployeeId>1</d:EmployeeId>
+    <d:EmployeeName>Mister X</d:EmployeeName>
+    <d:ManagerId>1</d:ManagerId>
+    <d:RoomId>2</d:RoomId>
+    <d:TeamId>1</d:TeamId>
+    <d:Location m:type="RefScenario.c_Location">
+      <d:City m:type="RefScenario.c_City">
+        <d:PostalCode>69190</d:PostalCode>
+        <d:CityName>Walldorf</d:CityName>
+      </d:City>
+      <d:Country>Germany</d:Country>
+    </d:Location>
+    <d:EntryDate m:null='true'></d:EntryDate>
+    <d:ImageUrl>Employees('1')/$value</d:ImageUrl>
+  </m:properties>
+</entry>
+--changeset_f980-1cb6-94dd--
+--batch_8194-cf13-1f56
+Content-Type: multipart/mixed; boundary=changeset_f980-1cb6-94dd
+
+--changeset_f980-1cb6-94dd
+Content-Type: application/http
+Content-Transfer-Encoding: binary
+Content-ID: 1
+
+POST Employees HTTP/1.1
+Host: http://localhost/odata
+Connection: keep-alive
+Content-Type: application/atom+xml
+Accept: */*
+Accept-Encoding: gzip,deflate
+Accept-Language: de-DE,de;q=0.8,en-US;q=0.6,en;q=0.4
+
+<entry xmlns="http://www.w3.org/2005/Atom"
+  xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
+  xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
+  xml:base="http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/"
+  m:etag="W/2">
+  <id>
+    http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/Rooms('2')
+  </id>
+  <title type="text">Room 2</title>
+  <updated>2013-04-03T10:53:26.021+02:00</updated>
+  <category term="RefScenario.Room"
+    scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
+  <link href="Rooms('2')" rel="edit" title="Room" />
+  <link href="Rooms('2')/nr_Employees"
+    rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/nr_Employees"
+    title="nr_Employees" type="application/atom+xml; type=feed">
+    <m:inline>
+      <feed xmlns="http://www.w3.org/2005/Atom"
+        xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
+        xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
+        xml:base="http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/">
+        <id>
+          http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/Employees
+        </id>
+        <title type="text">Employees</title>
+        <updated>2013-04-03T10:53:26.024+02:00</updated>
+        <author>
+          <name />
+        </author>
+        <link href="Employees" rel="self" title="Employees" />
+        <entry>
+          <id>
+            http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/Employees('2')
+          </id>
+          <title type="text">Frederic Fall</title>
+          <updated>2003-07-01T00:00:00Z</updated>
+          <category term="RefScenario.Employee"
+            scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
+          <link href="Employees('2')" rel="edit" title="Employee" />
+          <link href="Employees('2')/$value" rel="edit-media"
+            type="application/octet-stream" />
+          <link href="Employees('2')/ne_Manager"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Manager"
+            title="ne_Manager" type="application/atom+xml; type=entry" />
+          <link href="Employees('2')/ne_Team"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Team"
+            title="ne_Team" type="application/atom+xml; type=entry" />
+          <link href="Employees('2')/ne_Room"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Room"
+            title="ne_Room" type="application/atom+xml; type=entry" />
+          <content type="application/octet-stream" src="Employees('2')/$value" />
+          <m:properties>
+            <d:EmployeeId>2</d:EmployeeId>
+            <d:EmployeeName>Frederic Fall</d:EmployeeName>
+            <d:ManagerId>1</d:ManagerId>
+            <d:RoomId>2</d:RoomId>
+            <d:TeamId>1</d:TeamId>
+            <d:Location m:type="RefScenario.c_Location">
+              <d:City m:type="RefScenario.c_City">
+                <d:PostalCode>69190</d:PostalCode>
+                <d:CityName>Walldorf</d:CityName>
+              </d:City>
+              <d:Country>Germany</d:Country>
+            </d:Location>
+            <d:Age>32</d:Age>
+            <d:EntryDate>2003-07-01T00:00:00</d:EntryDate>
+            <d:ImageUrl>Employees('2')/$value</d:ImageUrl>
+          </m:properties>
+        </entry>
+        <entry>
+          <id>
+            http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/Employees('3')
+          </id>
+          <title type="text">Jonathan Smith</title>
+          <updated>2013-04-03T10:53:26.025+02:00</updated>
+          <category term="RefScenario.Employee"
+            scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
+          <link href="Employees('3')" rel="edit" title="Employee" />
+          <link href="Employees('3')/$value" rel="edit-media"
+            type="application/octet-stream" />
+          <link href="Employees('3')/ne_Manager"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Manager"
+            title="ne_Manager" type="application/atom+xml; type=entry" />
+          <link href="Employees('3')/ne_Team"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Team"
+            title="ne_Team" type="application/atom+xml; type=entry" />
+          <link href="Employees('3')/ne_Room"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Room"
+            title="ne_Room" type="application/atom+xml; type=entry" />
+          <content type="application/octet-stream" src="Employees('3')/$value" />
+          <m:properties>
+            <d:EmployeeId>3</d:EmployeeId>
+            <d:EmployeeName>Jonathan Smith</d:EmployeeName>
+            <d:ManagerId>1</d:ManagerId>
+            <d:RoomId>2</d:RoomId>
+            <d:TeamId>1</d:TeamId>
+            <d:Location m:type="RefScenario.c_Location">
+              <d:City m:type="RefScenario.c_City">
+                <d:PostalCode>69190</d:PostalCode>
+                <d:CityName>Walldorf</d:CityName>
+              </d:City>
+              <d:Country>Germany</d:Country>
+            </d:Location>
+            <d:Age>56</d:Age>
+            <d:EntryDate m:null="true" />
+            <d:ImageUrl>Employees('3')/$value</d:ImageUrl>
+          </m:properties>
+        </entry>
+        <entry>
+          <id>
+            http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/Employees('4')
+          </id>
+          <title type="text">Peter Burke</title>
+          <updated>2004-09-12T00:00:00Z</updated>
+          <category term="RefScenario.Employee"
+            scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
+          <link href="Employees('4')" rel="edit" title="Employee" />
+          <link href="Employees('4')/$value" rel="edit-media"
+            type="application/octet-stream" />
+          <link href="Employees('4')/ne_Manager"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Manager"
+            title="ne_Manager" type="application/atom+xml; type=entry" />
+          <link href="Employees('4')/ne_Team"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Team"
+            title="ne_Team" type="application/atom+xml; type=entry" />
+          <link href="Employees('4')/ne_Room"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Room"
+            title="ne_Room" type="application/atom+xml; type=entry" />
+          <content type="application/octet-stream" src="Employees('4')/$value" />
+          <m:properties>
+            <d:EmployeeId>4</d:EmployeeId>
+            <d:EmployeeName>Peter Burke</d:EmployeeName>
+            <d:ManagerId>3</d:ManagerId>
+            <d:RoomId>2</d:RoomId>
+            <d:TeamId>2</d:TeamId>
+            <d:Location m:type="RefScenario.c_Location">
+              <d:City m:type="RefScenario.c_City">
+                <d:PostalCode>69190</d:PostalCode>
+                <d:CityName>Walldorf</d:CityName>
+              </d:City>
+              <d:Country>Germany</d:Country>
+            </d:Location>
+            <d:Age>39</d:Age>
+            <d:EntryDate>2004-09-12T00:00:00</d:EntryDate>
+            <d:ImageUrl>Employees('4')/$value</d:ImageUrl>
+          </m:properties>
+        </entry>
+        <entry>
+          <id>
+            http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/Employees('6')
+          </id>
+          <title type="text">Susan Bay</title>
+          <updated>2010-12-01T00:00:00Z</updated>
+          <category term="RefScenario.Employee"
+            scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
+          <link href="Employees('6')" rel="edit" title="Employee" />
+          <link href="Employees('6')/$value" rel="edit-media"
+            type="application/octet-stream" />
+          <link href="Employees('6')/ne_Manager"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Manager"
+            title="ne_Manager" type="application/atom+xml; type=entry" />
+          <link href="Employees('6')/ne_Team"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Team"
+            title="ne_Team" type="application/atom+xml; type=entry" />
+          <link href="Employees('6')/ne_Room"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Room"
+            title="ne_Room" type="application/atom+xml; type=entry" />
+          <content type="application/octet-stream" src="Employees('6')/$value" />
+          <m:properties>
+            <d:EmployeeId>6</d:EmployeeId>
+            <d:EmployeeName>Susan Bay</d:EmployeeName>
+            <d:ManagerId>1</d:ManagerId>
+            <d:RoomId>2</d:RoomId>
+            <d:TeamId>3</d:TeamId>
+            <d:Location m:type="RefScenario.c_Location">
+              <d:City m:type="RefScenario.c_City">
+                <d:PostalCode>69190</d:PostalCode>
+                <d:CityName>Walldorf</d:CityName>
+              </d:City>
+              <d:Country>Germany</d:Country>
+            </d:Location>
+            <d:Age>29</d:Age>
+            <d:EntryDate>2010-12-01T00:00:00</d:EntryDate>
+            <d:ImageUrl>Employees('6')/$value</d:ImageUrl>
+          </m:properties>
+        </entry>
+      </feed>
+    </m:inline>
+  </link>
+  <link href="Rooms('2')/nr_Building"
+    rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/nr_Building"
+    title="nr_Building" type="application/atom+xml; type=entry" />
+  <content type="application/xml">
+    <m:properties>
+      <d:Id>2</d:Id>
+      <d:Name>Room 2</d:Name>
+      <d:Seats>5</d:Seats>
+      <d:Version>2</d:Version>
+    </m:properties>
+  </content>
+</entry>
+
+--changeset_f980-1cb6-94dd
+Content-Type: application/http
+Content-Transfer-Encoding: binary
+Content-ID: 1
+
+POST Employees HTTP/1.1
+Host: http://localhost/odata
+Connection: keep-alive
+Content-Type: application/atom+xml
+Accept: */*
+Accept-Encoding: gzip,deflate
+Accept-Language: de-DE,de;q=0.8,en-US;q=0.6,en;q=0.4
+
+<entry xmlns="http://www.w3.org/2005/Atom"
+  xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
+  xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
+  xml:base="http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/"
+  m:etag="W/2">
+  <id>
+    http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/Rooms('2')
+  </id>
+  <title type="text">Room 2</title>
+  <updated>2013-04-03T10:53:26.021+02:00</updated>
+  <category term="RefScenario.Room"
+    scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
+  <link href="Rooms('2')" rel="edit" title="Room" />
+  <link href="Rooms('2')/nr_Employees"
+    rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/nr_Employees"
+    title="nr_Employees" type="application/atom+xml; type=feed">
+    <m:inline>
+      <feed xmlns="http://www.w3.org/2005/Atom"
+        xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
+        xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
+        xml:base="http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/">
+        <id>
+          http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/Employees
+        </id>
+        <title type="text">Employees</title>
+        <updated>2013-04-03T10:53:26.024+02:00</updated>
+        <author>
+          <name />
+        </author>
+        <link href="Employees" rel="self" title="Employees" />
+        <entry>
+          <id>
+            http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/Employees('2')
+          </id>
+          <title type="text">Frederic Fall</title>
+          <updated>2003-07-01T00:00:00Z</updated>
+          <category term="RefScenario.Employee"
+            scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
+          <link href="Employees('2')" rel="edit" title="Employee" />
+          <link href="Employees('2')/$value" rel="edit-media"
+            type="application/octet-stream" />
+          <link href="Employees('2')/ne_Manager"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Manager"
+            title="ne_Manager" type="application/atom+xml; type=entry" />
+          <link href="Employees('2')/ne_Team"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Team"
+            title="ne_Team" type="application/atom+xml; type=entry" />
+          <link href="Employees('2')/ne_Room"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Room"
+            title="ne_Room" type="application/atom+xml; type=entry" />
+          <content type="application/octet-stream" src="Employees('2')/$value" />
+          <m:properties>
+            <d:EmployeeId>2</d:EmployeeId>
+            <d:EmployeeName>Frederic Fall</d:EmployeeName>
+            <d:ManagerId>1</d:ManagerId>
+            <d:RoomId>2</d:RoomId>
+            <d:TeamId>1</d:TeamId>
+            <d:Location m:type="RefScenario.c_Location">
+              <d:City m:type="RefScenario.c_City">
+                <d:PostalCode>69190</d:PostalCode>
+                <d:CityName>Walldorf</d:CityName>
+              </d:City>
+              <d:Country>Germany</d:Country>
+            </d:Location>
+            <d:Age>32</d:Age>
+            <d:EntryDate>2003-07-01T00:00:00</d:EntryDate>
+            <d:ImageUrl>Employees('2')/$value</d:ImageUrl>
+          </m:properties>
+        </entry>
+        <entry>
+          <id>
+            http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/Employees('3')
+          </id>
+          <title type="text">Jonathan Smith</title>
+          <updated>2013-04-03T10:53:26.025+02:00</updated>
+          <category term="RefScenario.Employee"
+            scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
+          <link href="Employees('3')" rel="edit" title="Employee" />
+          <link href="Employees('3')/$value" rel="edit-media"
+            type="application/octet-stream" />
+          <link href="Employees('3')/ne_Manager"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Manager"
+            title="ne_Manager" type="application/atom+xml; type=entry" />
+          <link href="Employees('3')/ne_Team"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Team"
+            title="ne_Team" type="application/atom+xml; type=entry" />
+          <link href="Employees('3')/ne_Room"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Room"
+            title="ne_Room" type="application/atom+xml; type=entry" />
+          <content type="application/octet-stream" src="Employees('3')/$value" />
+          <m:properties>
+            <d:EmployeeId>3</d:EmployeeId>
+            <d:EmployeeName>Jonathan Smith</d:EmployeeName>
+            <d:ManagerId>1</d:ManagerId>
+            <d:RoomId>2</d:RoomId>
+            <d:TeamId>1</d:TeamId>
+            <d:Location m:type="RefScenario.c_Location">
+              <d:City m:type="RefScenario.c_City">
+                <d:PostalCode>69190</d:PostalCode>
+                <d:CityName>Walldorf</d:CityName>
+              </d:City>
+              <d:Country>Germany</d:Country>
+            </d:Location>
+            <d:Age>56</d:Age>
+            <d:EntryDate m:null="true" />
+            <d:ImageUrl>Employees('3')/$value</d:ImageUrl>
+          </m:properties>
+        </entry>
+        <entry>
+          <id>
+            http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/Employees('4')
+          </id>
+          <title type="text">Peter Burke</title>
+          <updated>2004-09-12T00:00:00Z</updated>
+          <category term="RefScenario.Employee"
+            scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
+          <link href="Employees('4')" rel="edit" title="Employee" />
+          <link href="Employees('4')/$value" rel="edit-media"
+            type="application/octet-stream" />
+          <link href="Employees('4')/ne_Manager"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Manager"
+            title="ne_Manager" type="application/atom+xml; type=entry" />
+          <link href="Employees('4')/ne_Team"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Team"
+            title="ne_Team" type="application/atom+xml; type=entry" />
+          <link href="Employees('4')/ne_Room"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Room"
+            title="ne_Room" type="application/atom+xml; type=entry" />
+          <content type="application/octet-stream" src="Employees('4')/$value" />
+          <m:properties>
+            <d:EmployeeId>4</d:EmployeeId>
+            <d:EmployeeName>Peter Burke</d:EmployeeName>
+            <d:ManagerId>3</d:ManagerId>
+            <d:RoomId>2</d:RoomId>
+            <d:TeamId>2</d:TeamId>
+            <d:Location m:type="RefScenario.c_Location">
+              <d:City m:type="RefScenario.c_City">
+                <d:PostalCode>69190</d:PostalCode>
+                <d:CityName>Walldorf</d:CityName>
+              </d:City>
+              <d:Country>Germany</d:Country>
+            </d:Location>
+            <d:Age>39</d:Age>
+            <d:EntryDate>2004-09-12T00:00:00</d:EntryDate>
+            <d:ImageUrl>Employees('4')/$value</d:ImageUrl>
+          </m:properties>
+        </entry>
+        <entry>
+          <id>
+            http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/Employees('6')
+          </id>
+          <title type="text">Susan Bay</title>
+          <updated>2010-12-01T00:00:00Z</updated>
+          <category term="RefScenario.Employee"
+            scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
+          <link href="Employees('6')" rel="edit" title="Employee" />
+          <link href="Employees('6')/$value" rel="edit-media"
+            type="application/octet-stream" />
+          <link href="Employees('6')/ne_Manager"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Manager"
+            title="ne_Manager" type="application/atom+xml; type=entry" />
+          <link href="Employees('6')/ne_Team"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Team"
+            title="ne_Team" type="application/atom+xml; type=entry" />
+          <link href="Employees('6')/ne_Room"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Room"
+            title="ne_Room" type="application/atom+xml; type=entry" />
+          <content type="application/octet-stream" src="Employees('6')/$value" />
+          <m:properties>
+            <d:EmployeeId>6</d:EmployeeId>
+            <d:EmployeeName>Susan Bay</d:EmployeeName>
+            <d:ManagerId>1</d:ManagerId>
+            <d:RoomId>2</d:RoomId>
+            <d:TeamId>3</d:TeamId>
+            <d:Location m:type="RefScenario.c_Location">
+              <d:City m:type="RefScenario.c_City">
+                <d:PostalCode>69190</d:PostalCode>
+                <d:CityName>Walldorf</d:CityName>
+              </d:City>
+              <d:Country>Germany</d:Country>
+            </d:Location>
+            <d:Age>29</d:Age>
+            <d:EntryDate>2010-12-01T00:00:00</d:EntryDate>
+            <d:ImageUrl>Employees('6')/$value</d:ImageUrl>
+          </m:properties>
+        </entry>
+      </feed>
+    </m:inline>
+  </link>
+  <link href="Rooms('2')/nr_Building"
+    rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/nr_Building"
+    title="nr_Building" type="application/atom+xml; type=entry" />
+  <content type="application/xml">
+    <m:properties>
+      <d:Id>2</d:Id>
+      <d:Name>Room 2</d:Name>
+      <d:Seats>5</d:Seats>
+      <d:Version>2</d:Version>
+    </m:properties>
+  </content>
+</entry>
+
+--changeset_f980-1cb6-94dd
+Content-Type: application/http
+Content-Transfer-Encoding: binary
+Content-ID: 1
+
+PUT Employees('1') HTTP/1.1
+Host: http://localhost/odata
+Connection: keep-alive
+Content-Type: application/atom+xml
+Accept: */*
+Accept-Encoding: gzip,deflate
+Accept-Language: de-DE,de;q=0.8,en-US;q=0.6,en;q=0.4
+
+<?xml version='1.0' encoding='utf-8'?>
+<entry xmlns="http://www.w3.org/2005/Atom"
+  xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
+  xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
+  xml:base="http://localhost:19000/abc/EntryXmlChangeTest/">
+  <id>http://localhost:19000/abc/EntryXmlChangeTest/Employees('9')</id>
+  <title type="text">Mister X</title>
+  <updated m:null='true'>Z</updated>
+  <category term="RefScenario.Employee"
+    scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
+  <content type="image/jpeg" src="Employees('1')/$value" />
+  <m:properties>
+    <d:EmployeeId>1</d:EmployeeId>
+    <d:EmployeeName>Mister X</d:EmployeeName>
+    <d:ManagerId>1</d:ManagerId>
+    <d:RoomId>2</d:RoomId>
+    <d:TeamId>1</d:TeamId>
+    <d:Location m:type="RefScenario.c_Location">
+      <d:City m:type="RefScenario.c_City">
+        <d:PostalCode>69190</d:PostalCode>
+        <d:CityName>Walldorf</d:CityName>
+      </d:City>
+      <d:Country>Germany</d:Country>
+    </d:Location>
+    <d:EntryDate m:null='true'></d:EntryDate>
+    <d:ImageUrl>Employees('1')/$value</d:ImageUrl>
+  </m:properties>
+</entry>
+--changeset_f980-1cb6-94dd--
+--batch_8194-cf13-1f56
+Content-Type: multipart/mixed; boundary=changeset_f980-1cb6-94dd
+
+--changeset_f980-1cb6-94dd
+Content-Type: application/http
+Content-Transfer-Encoding: binary
+Content-ID: 1
+
+POST Employees HTTP/1.1
+Host: http://localhost/odata
+Connection: keep-alive
+Content-Type: application/atom+xml
+Accept: */*
+Accept-Encoding: gzip,deflate
+Accept-Language: de-DE,de;q=0.8,en-US;q=0.6,en;q=0.4
+
+<entry xmlns="http://www.w3.org/2005/Atom"
+  xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
+  xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
+  xml:base="http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/"
+  m:etag="W/2">
+  <id>
+    http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/Rooms('2')
+  </id>
+  <title type="text">Room 2</title>
+  <updated>2013-04-03T10:53:26.021+02:00</updated>
+  <category term="RefScenario.Room"
+    scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
+  <link href="Rooms('2')" rel="edit" title="Room" />
+  <link href="Rooms('2')/nr_Employees"
+    rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/nr_Employees"
+    title="nr_Employees" type="application/atom+xml; type=feed">
+    <m:inline>
+      <feed xmlns="http://www.w3.org/2005/Atom"
+        xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
+        xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
+        xml:base="http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/">
+        <id>
+          http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/Employees
+        </id>
+        <title type="text">Employees</title>
+        <updated>2013-04-03T10:53:26.024+02:00</updated>
+        <author>
+          <name />
+        </author>
+        <link href="Employees" rel="self" title="Employees" />
+        <entry>
+          <id>
+            http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/Employees('2')
+          </id>
+          <title type="text">Frederic Fall</title>
+          <updated>2003-07-01T00:00:00Z</updated>
+          <category term="RefScenario.Employee"
+            scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
+          <link href="Employees('2')" rel="edit" title="Employee" />
+          <link href="Employees('2')/$value" rel="edit-media"
+            type="application/octet-stream" />
+          <link href="Employees('2')/ne_Manager"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Manager"
+            title="ne_Manager" type="application/atom+xml; type=entry" />
+          <link href="Employees('2')/ne_Team"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Team"
+            title="ne_Team" type="application/atom+xml; type=entry" />
+          <link href="Employees('2')/ne_Room"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Room"
+            title="ne_Room" type="application/atom+xml; type=entry" />
+          <content type="application/octet-stream" src="Employees('2')/$value" />
+          <m:properties>
+            <d:EmployeeId>2</d:EmployeeId>
+            <d:EmployeeName>Frederic Fall</d:EmployeeName>
+            <d:ManagerId>1</d:ManagerId>
+            <d:RoomId>2</d:RoomId>
+            <d:TeamId>1</d:TeamId>
+            <d:Location m:type="RefScenario.c_Location">
+              <d:City m:type="RefScenario.c_City">
+                <d:PostalCode>69190</d:PostalCode>
+                <d:CityName>Walldorf</d:CityName>
+              </d:City>
+              <d:Country>Germany</d:Country>
+            </d:Location>
+            <d:Age>32</d:Age>
+            <d:EntryDate>2003-07-01T00:00:00</d:EntryDate>
+            <d:ImageUrl>Employees('2')/$value</d:ImageUrl>
+          </m:properties>
+        </entry>
+        <entry>
+          <id>
+            http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/Employees('3')
+          </id>
+          <title type="text">Jonathan Smith</title>
+          <updated>2013-04-03T10:53:26.025+02:00</updated>
+          <category term="RefScenario.Employee"
+            scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
+          <link href="Employees('3')" rel="edit" title="Employee" />
+          <link href="Employees('3')/$value" rel="edit-media"
+            type="application/octet-stream" />
+          <link href="Employees('3')/ne_Manager"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Manager"
+            title="ne_Manager" type="application/atom+xml; type=entry" />
+          <link href="Employees('3')/ne_Team"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Team"
+            title="ne_Team" type="application/atom+xml; type=entry" />
+          <link href="Employees('3')/ne_Room"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Room"
+            title="ne_Room" type="application/atom+xml; type=entry" />
+          <content type="application/octet-stream" src="Employees('3')/$value" />
+          <m:properties>
+            <d:EmployeeId>3</d:EmployeeId>
+            <d:EmployeeName>Jonathan Smith</d:EmployeeName>
+            <d:ManagerId>1</d:ManagerId>
+            <d:RoomId>2</d:RoomId>
+            <d:TeamId>1</d:TeamId>
+            <d:Location m:type="RefScenario.c_Location">
+              <d:City m:type="RefScenario.c_City">
+                <d:PostalCode>69190</d:PostalCode>
+                <d:CityName>Walldorf</d:CityName>
+              </d:City>
+              <d:Country>Germany</d:Country>
+            </d:Location>
+            <d:Age>56</d:Age>
+            <d:EntryDate m:null="true" />
+            <d:ImageUrl>Employees('3')/$value</d:ImageUrl>
+          </m:properties>
+        </entry>
+        <entry>
+          <id>
+            http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/Employees('4')
+          </id>
+          <title type="text">Peter Burke</title>
+          <updated>2004-09-12T00:00:00Z</updated>
+          <category term="RefScenario.Employee"
+            scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
+          <link href="Employees('4')" rel="edit" title="Employee" />
+          <link href="Employees('4')/$value" rel="edit-media"
+            type="application/octet-stream" />
+          <link href="Employees('4')/ne_Manager"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Manager"
+            title="ne_Manager" type="application/atom+xml; type=entry" />
+          <link href="Employees('4')/ne_Team"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Team"
+            title="ne_Team" type="application/atom+xml; type=entry" />
+          <link href="Employees('4')/ne_Room"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Room"
+            title="ne_Room" type="application/atom+xml; type=entry" />
+          <content type="application/octet-stream" src="Employees('4')/$value" />
+          <m:properties>
+            <d:EmployeeId>4</d:EmployeeId>
+            <d:EmployeeName>Peter Burke</d:EmployeeName>
+            <d:ManagerId>3</d:ManagerId>
+            <d:RoomId>2</d:RoomId>
+            <d:TeamId>2</d:TeamId>
+            <d:Location m:type="RefScenario.c_Location">
+              <d:City m:type="RefScenario.c_City">
+                <d:PostalCode>69190</d:PostalCode>
+                <d:CityName>Walldorf</d:CityName>
+              </d:City>
+              <d:Country>Germany</d:Country>
+            </d:Location>
+            <d:Age>39</d:Age>
+            <d:EntryDate>2004-09-12T00:00:00</d:EntryDate>
+            <d:ImageUrl>Employees('4')/$value</d:ImageUrl>
+          </m:properties>
+        </entry>
+        <entry>
+          <id>
+            http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/Employees('6')
+          </id>
+          <title type="text">Susan Bay</title>
+          <updated>2010-12-01T00:00:00Z</updated>
+          <category term="RefScenario.Employee"
+            scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
+          <link href="Employees('6')" rel="edit" title="Employee" />
+          <link href="Employees('6')/$value" rel="edit-media"
+            type="application/octet-stream" />
+          <link href="Employees('6')/ne_Manager"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Manager"
+            title="ne_Manager" type="application/atom+xml; type=entry" />
+          <link href="Employees('6')/ne_Team"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Team"
+            title="ne_Team" type="application/atom+xml; type=entry" />
+          <link href="Employees('6')/ne_Room"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Room"
+            title="ne_Room" type="application/atom+xml; type=entry" />
+          <content type="application/octet-stream" src="Employees('6')/$value" />
+          <m:properties>
+            <d:EmployeeId>6</d:EmployeeId>
+            <d:EmployeeName>Susan Bay</d:EmployeeName>
+            <d:ManagerId>1</d:ManagerId>
+            <d:RoomId>2</d:RoomId>
+            <d:TeamId>3</d:TeamId>
+            <d:Location m:type="RefScenario.c_Location">
+              <d:City m:type="RefScenario.c_City">
+                <d:PostalCode>69190</d:PostalCode>
+                <d:CityName>Walldorf</d:CityName>
+              </d:City>
+              <d:Country>Germany</d:Country>
+            </d:Location>
+            <d:Age>29</d:Age>
+            <d:EntryDate>2010-12-01T00:00:00</d:EntryDate>
+            <d:ImageUrl>Employees('6')/$value</d:ImageUrl>
+          </m:properties>
+        </entry>
+      </feed>
+    </m:inline>
+  </link>
+  <link href="Rooms('2')/nr_Building"
+    rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/nr_Building"
+    title="nr_Building" type="application/atom+xml; type=entry" />
+  <content type="application/xml">
+    <m:properties>
+      <d:Id>2</d:Id>
+      <d:Name>Room 2</d:Name>
+      <d:Seats>5</d:Seats>
+      <d:Version>2</d:Version>
+    </m:properties>
+  </content>
+</entry>
+
+--changeset_f980-1cb6-94dd
+Content-Type: application/http
+Content-Transfer-Encoding: binary
+Content-ID: 1
+
+POST Employees HTTP/1.1
+Host: http://localhost/odata
+Connection: keep-alive
+Content-Type: application/atom+xml
+Accept: */*
+Accept-Encoding: gzip,deflate
+Accept-Language: de-DE,de;q=0.8,en-US;q=0.6,en;q=0.4
+
+<entry xmlns="http://www.w3.org/2005/Atom"
+  xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
+  xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
+  xml:base="http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/"
+  m:etag="W/2">
+  <id>
+    http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/Rooms('2')
+  </id>
+  <title type="text">Room 2</title>
+  <updated>2013-04-03T10:53:26.021+02:00</updated>
+  <category term="RefScenario.Room"
+    scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
+  <link href="Rooms('2')" rel="edit" title="Room" />
+  <link href="Rooms('2')/nr_Employees"
+    rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/nr_Employees"
+    title="nr_Employees" type="application/atom+xml; type=feed">
+    <m:inline>
+      <feed xmlns="http://www.w3.org/2005/Atom"
+        xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
+        xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
+        xml:base="http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/">
+        <id>
+          http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/Employees
+        </id>
+        <title type="text">Employees</title>
+        <updated>2013-04-03T10:53:26.024+02:00</updated>
+        <author>
+          <name />
+        </author>
+        <link href="Employees" rel="self" title="Employees" />
+        <entry>
+          <id>
+            http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/Employees('2')
+          </id>
+          <title type="text">Frederic Fall</title>
+          <updated>2003-07-01T00:00:00Z</updated>
+          <category term="RefScenario.Employee"
+            scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
+          <link href="Employees('2')" rel="edit" title="Employee" />
+          <link href="Employees('2')/$value" rel="edit-media"
+            type="application/octet-stream" />
+          <link href="Employees('2')/ne_Manager"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Manager"
+            title="ne_Manager" type="application/atom+xml; type=entry" />
+          <link href="Employees('2')/ne_Team"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Team"
+            title="ne_Team" type="application/atom+xml; type=entry" />
+          <link href="Employees('2')/ne_Room"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Room"
+            title="ne_Room" type="application/atom+xml; type=entry" />
+          <content type="application/octet-stream" src="Employees('2')/$value" />
+          <m:properties>
+            <d:EmployeeId>2</d:EmployeeId>
+            <d:EmployeeName>Frederic Fall</d:EmployeeName>
+            <d:ManagerId>1</d:ManagerId>
+            <d:RoomId>2</d:RoomId>
+            <d:TeamId>1</d:TeamId>
+            <d:Location m:type="RefScenario.c_Location">
+              <d:City m:type="RefScenario.c_City">
+                <d:PostalCode>69190</d:PostalCode>
+                <d:CityName>Walldorf</d:CityName>
+              </d:City>
+              <d:Country>Germany</d:Country>
+            </d:Location>
+            <d:Age>32</d:Age>
+            <d:EntryDate>2003-07-01T00:00:00</d:EntryDate>
+            <d:ImageUrl>Employees('2')/$value</d:ImageUrl>
+          </m:properties>
+        </entry>
+        <entry>
+          <id>
+            http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/Employees('3')
+          </id>
+          <title type="text">Jonathan Smith</title>
+          <updated>2013-04-03T10:53:26.025+02:00</updated>
+          <category term="RefScenario.Employee"
+            scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
+          <link href="Employees('3')" rel="edit" title="Employee" />
+          <link href="Employees('3')/$value" rel="edit-media"
+            type="application/octet-stream" />
+          <link href="Employees('3')/ne_Manager"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Manager"
+            title="ne_Manager" type="application/atom+xml; type=entry" />
+          <link href="Employees('3')/ne_Team"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Team"
+            title="ne_Team" type="application/atom+xml; type=entry" />
+          <link href="Employees('3')/ne_Room"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Room"
+            title="ne_Room" type="application/atom+xml; type=entry" />
+          <content type="application/octet-stream" src="Employees('3')/$value" />
+          <m:properties>
+            <d:EmployeeId>3</d:EmployeeId>
+            <d:EmployeeName>Jonathan Smith</d:EmployeeName>
+            <d:ManagerId>1</d:ManagerId>
+            <d:RoomId>2</d:RoomId>
+            <d:TeamId>1</d:TeamId>
+            <d:Location m:type="RefScenario.c_Location">
+              <d:City m:type="RefScenario.c_City">
+                <d:PostalCode>69190</d:PostalCode>
+                <d:CityName>Walldorf</d:CityName>
+              </d:City>
+              <d:Country>Germany</d:Country>
+            </d:Location>
+            <d:Age>56</d:Age>
+            <d:EntryDate m:null="true" />
+            <d:ImageUrl>Employees('3')/$value</d:ImageUrl>
+          </m:properties>
+        </entry>
+        <entry>
+          <id>
+            http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/Employees('4')
+          </id>
+          <title type="text">Peter Burke</title>
+          <updated>2004-09-12T00:00:00Z</updated>
+          <category term="RefScenario.Employee"
+            scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
+          <link href="Employees('4')" rel="edit" title="Employee" />
+          <link href="Employees('4')/$value" rel="edit-media"
+            type="application/octet-stream" />
+          <link href="Employees('4')/ne_Manager"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Manager"
+            title="ne_Manager" type="application/atom+xml; type=entry" />
+          <link href="Employees('4')/ne_Team"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Team"
+            title="ne_Team" type="application/atom+xml; type=entry" />
+          <link href="Employees('4')/ne_Room"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Room"
+            title="ne_Room" type="application/atom+xml; type=entry" />
+          <content type="application/octet-stream" src="Employees('4')/$value" />
+          <m:properties>
+            <d:EmployeeId>4</d:EmployeeId>
+            <d:EmployeeName>Peter Burke</d:EmployeeName>
+            <d:ManagerId>3</d:ManagerId>
+            <d:RoomId>2</d:RoomId>
+            <d:TeamId>2</d:TeamId>
+            <d:Location m:type="RefScenario.c_Location">
+              <d:City m:type="RefScenario.c_City">
+                <d:PostalCode>69190</d:PostalCode>
+                <d:CityName>Walldorf</d:CityName>
+              </d:City>
+              <d:Country>Germany</d:Country>
+            </d:Location>
+            <d:Age>39</d:Age>
+            <d:EntryDate>2004-09-12T00:00:00</d:EntryDate>
+            <d:ImageUrl>Employees('4')/$value</d:ImageUrl>
+          </m:properties>
+        </entry>
+        <entry>
+          <id>
+            http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/Employees('6')
+          </id>
+          <title type="text">Susan Bay</title>
+          <updated>2010-12-01T00:00:00Z</updated>
+          <category term="RefScenario.Employee"
+            scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
+          <link href="Employees('6')" rel="edit" title="Employee" />
+          <link href="Employees('6')/$value" rel="edit-media"
+            type="application/octet-stream" />
+          <link href="Employees('6')/ne_Manager"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Manager"
+            title="ne_Manager" type="application/atom+xml; type=entry" />
+          <link href="Employees('6')/ne_Team"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Team"
+            title="ne_Team" type="application/atom+xml; type=entry" />
+          <link href="Employees('6')/ne_Room"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Room"
+            title="ne_Room" type="application/atom+xml; type=entry" />
+          <content type="application/octet-stream" src="Employees('6')/$value" />
+          <m:properties>
+            <d:EmployeeId>6</d:EmployeeId>
+            <d:EmployeeName>Susan Bay</d:EmployeeName>
+            <d:ManagerId>1</d:ManagerId>
+            <d:RoomId>2</d:RoomId>
+            <d:TeamId>3</d:TeamId>
+            <d:Location m:type="RefScenario.c_Location">
+              <d:City m:type="RefScenario.c_City">
+                <d:PostalCode>69190</d:PostalCode>
+                <d:CityName>Walldorf</d:CityName>
+              </d:City>
+              <d:Country>Germany</d:Country>
+            </d:Location>
+            <d:Age>29</d:Age>
+            <d:EntryDate>2010-12-01T00:00:00</d:EntryDate>
+            <d:ImageUrl>Employees('6')/$value</d:ImageUrl>
+          </m:properties>
+        </entry>
+      </feed>
+    </m:inline>
+  </link>
+  <link href="Rooms('2')/nr_Building"
+    rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/nr_Building"
+    title="nr_Building" type="application/atom+xml; type=entry" />
+  <content type="application/xml">
+    <m:properties>
+      <d:Id>2</d:Id>
+      <d:Name>Room 2</d:Name>
+      <d:Seats>5</d:Seats>
+      <d:Version>2</d:Version>
+    </m:properties>
+  </content>
+</entry>
+
+--changeset_f980-1cb6-94dd
+Content-Type: application/http
+Content-Transfer-Encoding: binary
+Content-ID: 1
+
+PUT Employees('1') HTTP/1.1
+Host: http://localhost/odata
+Connection: keep-alive
+Content-Type: application/atom+xml
+Accept: */*
+Accept-Encoding: gzip,deflate
+Accept-Language: de-DE,de;q=0.8,en-US;q=0.6,en;q=0.4
+
+<?xml version='1.0' encoding='utf-8'?>
+<entry xmlns="http://www.w3.org/2005/Atom"
+  xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
+  xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
+  xml:base="http://localhost:19000/abc/EntryXmlChangeTest/">
+  <id>http://localhost:19000/abc/EntryXmlChangeTest/Employees('9')</id>
+  <title type="text">Mister X</title>
+  <updated m:null='true'>Z</updated>
+  <category term="RefScenario.Employee"
+    scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
+  <content type="image/jpeg" src="Employees('1')/$value" />
+  <m:properties>
+    <d:EmployeeId>1</d:EmployeeId>
+    <d:EmployeeName>Mister X</d:EmployeeName>
+    <d:ManagerId>1</d:ManagerId>
+    <d:RoomId>2</d:RoomId>
+    <d:TeamId>1</d:TeamId>
+    <d:Location m:type="RefScenario.c_Location">
+      <d:City m:type="RefScenario.c_City">
+        <d:PostalCode>69190</d:PostalCode>
+        <d:CityName>Walldorf</d:CityName>
+      </d:City>
+      <d:Country>Germany</d:Country>
+    </d:Location>
+    <d:EntryDate m:null='true'></d:EntryDate>
+    <d:ImageUrl>Employees('1')/$value</d:ImageUrl>
+  </m:properties>
+</entry>
+--changeset_f980-1cb6-94dd--
+--batch_8194-cf13-1f56
+Content-Type: multipart/mixed; boundary=changeset_f980-1cb6-94dd
+
+--changeset_f980-1cb6-94dd
+Content-Type: application/http
+Content-Transfer-Encoding: binary
+Content-ID: 1
+
+POST Employees HTTP/1.1
+Host: http://localhost/odata
+Connection: keep-alive
+Content-Type: application/atom+xml
+Accept: */*
+Accept-Encoding: gzip,deflate
+Accept-Language: de-DE,de;q=0.8,en-US;q=0.6,en;q=0.4
+
+<entry xmlns="http://www.w3.org/2005/Atom"
+  xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
+  xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
+  xml:base="http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/"
+  m:etag="W/2">
+  <id>
+    http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/Rooms('2')
+  </id>
+  <title type="text">Room 2</title>
+  <updated>2013-04-03T10:53:26.021+02:00</updated>
+  <category term="RefScenario.Room"
+    scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
+  <link href="Rooms('2')" rel="edit" title="Room" />
+  <link href="Rooms('2')/nr_Employees"
+    rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/nr_Employees"
+    title="nr_Employees" type="application/atom+xml; type=feed">
+    <m:inline>
+      <feed xmlns="http://www.w3.org/2005/Atom"
+        xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
+        xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
+        xml:base="http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/">
+        <id>
+          http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/Employees
+        </id>
+        <title type="text">Employees</title>
+        <updated>2013-04-03T10:53:26.024+02:00</updated>
+        <author>
+          <name />
+        </author>
+        <link href="Employees" rel="self" title="Employees" />
+        <entry>
+          <id>
+            http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/Employees('2')
+          </id>
+          <title type="text">Frederic Fall</title>
+          <updated>2003-07-01T00:00:00Z</updated>
+          <category term="RefScenario.Employee"
+            scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
+          <link href="Employees('2')" rel="edit" title="Employee" />
+          <link href="Employees('2')/$value" rel="edit-media"
+            type="application/octet-stream" />
+          <link href="Employees('2')/ne_Manager"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Manager"
+            title="ne_Manager" type="application/atom+xml; type=entry" />
+          <link href="Employees('2')/ne_Team"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Team"
+            title="ne_Team" type="application/atom+xml; type=entry" />
+          <link href="Employees('2')/ne_Room"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Room"
+            title="ne_Room" type="application/atom+xml; type=entry" />
+          <content type="application/octet-stream" src="Employees('2')/$value" />
+          <m:properties>
+            <d:EmployeeId>2</d:EmployeeId>
+            <d:EmployeeName>Frederic Fall</d:EmployeeName>
+            <d:ManagerId>1</d:ManagerId>
+            <d:RoomId>2</d:RoomId>
+            <d:TeamId>1</d:TeamId>
+            <d:Location m:type="RefScenario.c_Location">
+              <d:City m:type="RefScenario.c_City">
+                <d:PostalCode>69190</d:PostalCode>
+                <d:CityName>Walldorf</d:CityName>
+              </d:City>
+              <d:Country>Germany</d:Country>
+            </d:Location>
+            <d:Age>32</d:Age>
+            <d:EntryDate>2003-07-01T00:00:00</d:EntryDate>
+            <d:ImageUrl>Employees('2')/$value</d:ImageUrl>
+          </m:properties>
+        </entry>
+        <entry>
+          <id>
+            http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/Employees('3')
+          </id>
+          <title type="text">Jonathan Smith</title>
+          <updated>2013-04-03T10:53:26.025+02:00</updated>
+          <category term="RefScenario.Employee"
+            scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
+          <link href="Employees('3')" rel="edit" title="Employee" />
+          <link href="Employees('3')/$value" rel="edit-media"
+            type="application/octet-stream" />
+          <link href="Employees('3')/ne_Manager"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Manager"
+            title="ne_Manager" type="application/atom+xml; type=entry" />
+          <link href="Employees('3')/ne_Team"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Team"
+            title="ne_Team" type="application/atom+xml; type=entry" />
+          <link href="Employees('3')/ne_Room"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Room"
+            title="ne_Room" type="application/atom+xml; type=entry" />
+          <content type="application/octet-stream" src="Employees('3')/$value" />
+          <m:properties>
+            <d:EmployeeId>3</d:EmployeeId>
+            <d:EmployeeName>Jonathan Smith</d:EmployeeName>
+            <d:ManagerId>1</d:ManagerId>
+            <d:RoomId>2</d:RoomId>
+            <d:TeamId>1</d:TeamId>
+            <d:Location m:type="RefScenario.c_Location">
+              <d:City m:type="RefScenario.c_City">
+                <d:PostalCode>69190</d:PostalCode>
+                <d:CityName>Walldorf</d:CityName>
+              </d:City>
+              <d:Country>Germany</d:Country>
+            </d:Location>
+            <d:Age>56</d:Age>
+            <d:EntryDate m:null="true" />
+            <d:ImageUrl>Employees('3')/$value</d:ImageUrl>
+          </m:properties>
+        </entry>
+        <entry>
+          <id>
+            http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/Employees('4')
+          </id>
+          <title type="text">Peter Burke</title>
+          <updated>2004-09-12T00:00:00Z</updated>
+          <category term="RefScenario.Employee"
+            scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
+          <link href="Employees('4')" rel="edit" title="Employee" />
+          <link href="Employees('4')/$value" rel="edit-media"
+            type="application/octet-stream" />
+          <link href="Employees('4')/ne_Manager"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Manager"
+            title="ne_Manager" type="application/atom+xml; type=entry" />
+          <link href="Employees('4')/ne_Team"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Team"
+            title="ne_Team" type="application/atom+xml; type=entry" />
+          <link href="Employees('4')/ne_Room"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Room"
+            title="ne_Room" type="application/atom+xml; type=entry" />
+          <content type="application/octet-stream" src="Employees('4')/$value" />
+          <m:properties>
+            <d:EmployeeId>4</d:EmployeeId>
+            <d:EmployeeName>Peter Burke</d:EmployeeName>
+            <d:ManagerId>3</d:ManagerId>
+            <d:RoomId>2</d:RoomId>
+            <d:TeamId>2</d:TeamId>
+            <d:Location m:type="RefScenario.c_Location">
+              <d:City m:type="RefScenario.c_City">
+                <d:PostalCode>69190</d:PostalCode>
+                <d:CityName>Walldorf</d:CityName>
+              </d:City>
+              <d:Country>Germany</d:Country>
+            </d:Location>
+            <d:Age>39</d:Age>
+            <d:EntryDate>2004-09-12T00:00:00</d:EntryDate>
+            <d:ImageUrl>Employees('4')/$value</d:ImageUrl>
+          </m:properties>
+        </entry>
+        <entry>
+          <id>
+            http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/Employees('6')
+          </id>
+          <title type="text">Susan Bay</title>
+          <updated>2010-12-01T00:00:00Z</updated>
+          <category term="RefScenario.Employee"
+            scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
+          <link href="Employees('6')" rel="edit" title="Employee" />
+          <link href="Employees('6')/$value" rel="edit-media"
+            type="application/octet-stream" />
+          <link href="Employees('6')/ne_Manager"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Manager"
+            title="ne_Manager" type="application/atom+xml; type=entry" />
+          <link href="Employees('6')/ne_Team"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Team"
+            title="ne_Team" type="application/atom+xml; type=entry" />
+          <link href="Employees('6')/ne_Room"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Room"
+            title="ne_Room" type="application/atom+xml; type=entry" />
+          <content type="application/octet-stream" src="Employees('6')/$value" />
+          <m:properties>
+            <d:EmployeeId>6</d:EmployeeId>
+            <d:EmployeeName>Susan Bay</d:EmployeeName>
+            <d:ManagerId>1</d:ManagerId>
+            <d:RoomId>2</d:RoomId>
+            <d:TeamId>3</d:TeamId>
+            <d:Location m:type="RefScenario.c_Location">
+              <d:City m:type="RefScenario.c_City">
+                <d:PostalCode>69190</d:PostalCode>
+                <d:CityName>Walldorf</d:CityName>
+              </d:City>
+              <d:Country>Germany</d:Country>
+            </d:Location>
+            <d:Age>29</d:Age>
+            <d:EntryDate>2010-12-01T00:00:00</d:EntryDate>
+            <d:ImageUrl>Employees('6')/$value</d:ImageUrl>
+          </m:properties>
+        </entry>
+      </feed>
+    </m:inline>
+  </link>
+  <link href="Rooms('2')/nr_Building"
+    rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/nr_Building"
+    title="nr_Building" type="application/atom+xml; type=entry" />
+  <content type="application/xml">
+    <m:properties>
+      <d:Id>2</d:Id>
+      <d:Name>Room 2</d:Name>
+      <d:Seats>5</d:Seats>
+      <d:Version>2</d:Version>
+    </m:properties>
+  </content>
+</entry>
+
+--changeset_f980-1cb6-94dd
+Content-Type: application/http
+Content-Transfer-Encoding: binary
+Content-ID: 1
+
+POST Employees HTTP/1.1
+Host: http://localhost/odata
+Connection: keep-alive
+Content-Type: application/atom+xml
+Accept: */*
+Accept-Encoding: gzip,deflate
+Accept-Language: de-DE,de;q=0.8,en-US;q=0.6,en;q=0.4
+
+<entry xmlns="http://www.w3.org/2005/Atom"
+  xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
+  xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
+  xml:base="http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/"
+  m:etag="W/2">
+  <id>
+    http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/Rooms('2')
+  </id>
+  <title type="text">Room 2</title>
+  <updated>2013-04-03T10:53:26.021+02:00</updated>
+  <category term="RefScenario.Room"
+    scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
+  <link href="Rooms('2')" rel="edit" title="Room" />
+  <link href="Rooms('2')/nr_Employees"
+    rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/nr_Employees"
+    title="nr_Employees" type="application/atom+xml; type=feed">
+    <m:inline>
+      <feed xmlns="http://www.w3.org/2005/Atom"
+        xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
+        xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
+        xml:base="http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/">
+        <id>
+          http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/Employees
+        </id>
+        <title type="text">Employees</title>
+        <updated>2013-04-03T10:53:26.024+02:00</updated>
+        <author>
+          <name />
+        </author>
+        <link href="Employees" rel="self" title="Employees" />
+        <entry>
+          <id>
+            http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/Employees('2')
+          </id>
+          <title type="text">Frederic Fall</title>
+          <updated>2003-07-01T00:00:00Z</updated>
+          <category term="RefScenario.Employee"
+            scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
+          <link href="Employees('2')" rel="edit" title="Employee" />
+          <link href="Employees('2')/$value" rel="edit-media"
+            type="application/octet-stream" />
+          <link href="Employees('2')/ne_Manager"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Manager"
+            title="ne_Manager" type="application/atom+xml; type=entry" />
+          <link href="Employees('2')/ne_Team"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Team"
+            title="ne_Team" type="application/atom+xml; type=entry" />
+          <link href="Employees('2')/ne_Room"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Room"
+            title="ne_Room" type="application/atom+xml; type=entry" />
+          <content type="application/octet-stream" src="Employees('2')/$value" />
+          <m:properties>
+            <d:EmployeeId>2</d:EmployeeId>
+            <d:EmployeeName>Frederic Fall</d:EmployeeName>
+            <d:ManagerId>1</d:ManagerId>
+            <d:RoomId>2</d:RoomId>
+            <d:TeamId>1</d:TeamId>
+            <d:Location m:type="RefScenario.c_Location">
+              <d:City m:type="RefScenario.c_City">
+                <d:PostalCode>69190</d:PostalCode>
+                <d:CityName>Walldorf</d:CityName>
+              </d:City>
+              <d:Country>Germany</d:Country>
+            </d:Location>
+            <d:Age>32</d:Age>
+            <d:EntryDate>2003-07-01T00:00:00</d:EntryDate>
+            <d:ImageUrl>Employees('2')/$value</d:ImageUrl>
+          </m:properties>
+        </entry>
+        <entry>
+          <id>
+            http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/Employees('3')
+          </id>
+          <title type="text">Jonathan Smith</title>
+          <updated>2013-04-03T10:53:26.025+02:00</updated>
+          <category term="RefScenario.Employee"
+            scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
+          <link href="Employees('3')" rel="edit" title="Employee" />
+          <link href="Employees('3')/$value" rel="edit-media"
+            type="application/octet-stream" />
+          <link href="Employees('3')/ne_Manager"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Manager"
+            title="ne_Manager" type="application/atom+xml; type=entry" />
+          <link href="Employees('3')/ne_Team"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Team"
+            title="ne_Team" type="application/atom+xml; type=entry" />
+          <link href="Employees('3')/ne_Room"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Room"
+            title="ne_Room" type="application/atom+xml; type=entry" />
+          <content type="application/octet-stream" src="Employees('3')/$value" />
+          <m:properties>
+            <d:EmployeeId>3</d:EmployeeId>
+            <d:EmployeeName>Jonathan Smith</d:EmployeeName>
+            <d:ManagerId>1</d:ManagerId>
+            <d:RoomId>2</d:RoomId>
+            <d:TeamId>1</d:TeamId>
+            <d:Location m:type="RefScenario.c_Location">
+              <d:City m:type="RefScenario.c_City">
+                <d:PostalCode>69190</d:PostalCode>
+                <d:CityName>Walldorf</d:CityName>
+              </d:City>
+              <d:Country>Germany</d:Country>
+            </d:Location>
+            <d:Age>56</d:Age>
+            <d:EntryDate m:null="true" />
+            <d:ImageUrl>Employees('3')/$value</d:ImageUrl>
+          </m:properties>
+        </entry>
+        <entry>
+          <id>
+            http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/Employees('4')
+          </id>
+          <title type="text">Peter Burke</title>
+          <updated>2004-09-12T00:00:00Z</updated>
+          <category term="RefScenario.Employee"
+            scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
+          <link href="Employees('4')" rel="edit" title="Employee" />
+          <link href="Employees('4')/$value" rel="edit-media"
+            type="application/octet-stream" />
+          <link href="Employees('4')/ne_Manager"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Manager"
+            title="ne_Manager" type="application/atom+xml; type=entry" />
+          <link href="Employees('4')/ne_Team"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Team"
+            title="ne_Team" type="application/atom+xml; type=entry" />
+          <link href="Employees('4')/ne_Room"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Room"
+            title="ne_Room" type="application/atom+xml; type=entry" />
+          <content type="application/octet-stream" src="Employees('4')/$value" />
+          <m:properties>
+            <d:EmployeeId>4</d:EmployeeId>
+            <d:EmployeeName>Peter Burke</d:EmployeeName>
+            <d:ManagerId>3</d:ManagerId>
+            <d:RoomId>2</d:RoomId>
+            <d:TeamId>2</d:TeamId>
+            <d:Location m:type="RefScenario.c_Location">
+              <d:City m:type="RefScenario.c_City">
+                <d:PostalCode>69190</d:PostalCode>
+                <d:CityName>Walldorf</d:CityName>
+              </d:City>
+              <d:Country>Germany</d:Country>
+            </d:Location>
+            <d:Age>39</d:Age>
+            <d:EntryDate>2004-09-12T00:00:00</d:EntryDate>
+            <d:ImageUrl>Employees('4')/$value</d:ImageUrl>
+          </m:properties>
+        </entry>
+        <entry>
+          <id>
+            http://localhost:8080/org.apache.olingo.odata2.ref.web/ReferenceScenario.svc/Employees('6')
+          </id>
+          <title type="text">Susan Bay</title>
+          <updated>2010-12-01T00:00:00Z</updated>
+          <category term="RefScenario.Employee"
+            scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
+          <link href="Employees('6')" rel="edit" title="Employee" />
+          <link href="Employees('6')/$value" rel="edit-media"
+            type="application/octet-stream" />
+          <link href="Employees('6')/ne_Manager"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Manager"
+            title="ne_Manager" type="application/atom+xml; type=entry" />
+          <link href="Employees('6')/ne_Team"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Team"
+            title="ne_Team" type="application/atom+xml; type=entry" />
+          <link href="Employees('6')/ne_Room"
+            rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/ne_Room"
+            title="ne_Room" type="application/atom+xml; type=entry" />
+          <content type="application/octet-stream" src="Employees('6')/$value" />
+          <m:properties>
+            <d:EmployeeId>6</d:EmployeeId>
+            <d:EmployeeName>Susan Bay</d:EmployeeName>
+            <d:ManagerId>1</d:ManagerId>
+            <d:RoomId>2</d:RoomId>
+            <d:TeamId>3</d:TeamId>
+            <d:Location m:type="RefScenario.c_Location">
+              <d:City m:type="RefScenario.c_City">
+                <d:PostalCode>69190</d:PostalCode>
+                <d:CityName>Walldorf</d:CityName>
+           

<TRUNCATED>

[15/22] olingo-odata4 git commit: Merge branch 'master' into olingo472

Posted by ch...@apache.org.
Merge branch 'master' into olingo472

Conflicts:
	lib/server-core/src/main/java/org/apache/olingo/server/core/ODataHandler.java
	lib/server-core/src/main/resources/server-core-exceptions-i18n.properties
	lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalProcessor.java


Project: http://git-wip-us.apache.org/repos/asf/olingo-odata4/repo
Commit: http://git-wip-us.apache.org/repos/asf/olingo-odata4/commit/4f820fe5
Tree: http://git-wip-us.apache.org/repos/asf/olingo-odata4/tree/4f820fe5
Diff: http://git-wip-us.apache.org/repos/asf/olingo-odata4/diff/4f820fe5

Branch: refs/heads/master
Commit: 4f820fe533ac6da8ce8a223dbd069c899ea9f307
Parents: ad177ac c2c8bf1
Author: Christian Amend <ch...@apache.org>
Authored: Thu Nov 13 17:27:36 2014 +0100
Committer: Christian Amend <ch...@apache.org>
Committed: Thu Nov 13 17:27:36 2014 +0100

----------------------------------------------------------------------
 .../org/apache/olingo/server/api/OData.java     |   9 +-
 .../processor/ComplexCollectionProcessor.java   |  46 +++
 .../server/api/processor/ComplexProcessor.java  |  45 +++
 .../CountEntityCollectionProcessor.java         |  45 +++
 .../processor/EntityCollectionProcessor.java    |  44 +++
 .../server/api/processor/EntityProcessor.java   |  16 +-
 .../api/processor/EntitySetProcessor.java       |  58 ---
 .../api/processor/ExceptionProcessor.java       |  10 +-
 .../server/api/processor/MetadataProcessor.java |  14 +-
 .../processor/PrimitiveCollectionProcessor.java |  46 +++
 .../api/processor/PrimitiveProcessor.java       |  60 +++
 .../server/api/processor/PropertyProcessor.java |  62 ---
 .../api/processor/ServiceDocumentProcessor.java |  14 +-
 .../serializer/ComplexSerializerOptions.java    |  86 ++++
 .../EntityCollectionSerializerOptions.java      |  97 +++++
 .../api/serializer/EntitySerializerOptions.java |  85 ++++
 .../api/serializer/FixedFormatSerializer.java   |  48 +++
 .../server/api/serializer/ODataSerializer.java  |  75 ++--
 .../api/serializer/ODataSerializerOptions.java  |  99 -----
 .../serializer/PrimitiveSerializerOptions.java  | 131 ++++++
 .../PrimitiveValueSerializerOptions.java        | 118 ++++++
 .../api/serializer/SerializerException.java     |   3 +-
 .../apache/olingo/server/core/ODataHandler.java | 162 ++++----
 .../apache/olingo/server/core/ODataImpl.java    |   9 +-
 .../serializer/FixedFormatSerializerImpl.java   |  59 +++
 .../core/serializer/ODataXmlSerializerImpl.java | 126 ------
 .../serializer/json/ODataJsonSerializer.java    | 250 ++++++++----
 .../serializer/utils/ContextURLBuilder.java     |   6 +-
 .../core/serializer/utils/ContextURLHelper.java |  26 +-
 .../serializer/xml/ODataXmlSerializerImpl.java  | 154 ++++++++
 .../server/core/uri/validator/UriValidator.java |   7 +-
 .../server-core-exceptions-i18n.properties      |   1 +
 .../server/core/ODataHttpHandlerImplTest.java   |   5 +-
 .../serializer/FixedFormatSerializerTest.java   |  57 +++
 .../serializer/utils/ContextURLBuilderTest.java |   5 +-
 .../olingo/server/tecsvc/TechnicalServlet.java  |   6 +-
 .../processor/TechnicalEntityProcessor.java     | 161 ++++++++
 .../TechnicalPrimitiveComplexProcessor.java     | 243 ++++++++++++
 .../tecsvc/processor/TechnicalProcessor.java    | 291 +-------------
 .../core/ODataHandlerExceptionHandlingTest.java | 210 ----------
 .../olingo/server/core/ODataHandlerTest.java    | 395 +++++++++----------
 .../json/ODataJsonSerializerTest.java           | 154 +++++---
 .../core/uri/validator/UriValidatorTest.java    |   1 -
 .../olingo/server/sample/data/DataProvider.java |   3 -
 .../server/sample/processor/CarsProcessor.java  |  81 ++--
 45 files changed, 2270 insertions(+), 1353 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/4f820fe5/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataHandler.java
----------------------------------------------------------------------
diff --cc lib/server-core/src/main/java/org/apache/olingo/server/core/ODataHandler.java
index af092cf,f72b6bd..9be403c
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataHandler.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataHandler.java
@@@ -35,10 -35,11 +35,13 @@@ import org.apache.olingo.server.api.ODa
  import org.apache.olingo.server.api.ODataResponse;
  import org.apache.olingo.server.api.ODataServerError;
  import org.apache.olingo.server.api.ServiceMetadata;
 +import org.apache.olingo.server.api.batch.BatchException;
 +import org.apache.olingo.server.api.processor.BatchProcessor;
+ import org.apache.olingo.server.api.processor.ComplexCollectionProcessor;
+ import org.apache.olingo.server.api.processor.ComplexProcessor;
+ import org.apache.olingo.server.api.processor.CountEntityCollectionProcessor;
  import org.apache.olingo.server.api.processor.DefaultProcessor;
- import org.apache.olingo.server.api.processor.EntitySetProcessor;
+ import org.apache.olingo.server.api.processor.EntityCollectionProcessor;
  import org.apache.olingo.server.api.processor.EntityProcessor;
  import org.apache.olingo.server.api.processor.ExceptionProcessor;
  import org.apache.olingo.server.api.processor.MetadataProcessor;

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/4f820fe5/lib/server-core/src/main/resources/server-core-exceptions-i18n.properties
----------------------------------------------------------------------
diff --cc lib/server-core/src/main/resources/server-core-exceptions-i18n.properties
index 7e58fc6,522c3a0..ece835d
--- a/lib/server-core/src/main/resources/server-core-exceptions-i18n.properties
+++ b/lib/server-core/src/main/resources/server-core-exceptions-i18n.properties
@@@ -94,24 -94,4 +94,25 @@@ SerializerException.UNSUPPORTED_PROPERT
  SerializerException.INCONSISTENT_PROPERTY_TYPE=An inconsistency has been detected in the type definition of property '%1$s'.
  SerializerException.MISSING_PROPERTY=The non-nullable property '%1$s' is missing.
  SerializerException.WRONG_PROPERTY_VALUE=The value '%2$s' is not valid for property '%1$s'.
+ SerializerException.WRONG_PRIMITIVE_VALUE=The value '%2$s' is not valid for the primitive type '%1$s' and the given facets.
 +
 +BatchException.INVALID_BOUNDARY=Invalid boundary at line '%1$s'.
 +BatchException.INVALID_CHANGESET_METHOD=Invalid method: a ChangeSet cannot contain retrieve requests at line '%1$s'.
 +BatchException.INVALID_CONTENT=Retrieve requests must not contain any body content '%1$s'.
 +BatchException.INVALID_CONTENT_LENGTH=Invalid content length: content length have to be an integer and positive at line '%1$s'.
 +BatchException.INVALID_CONTENT_TRANSFER_ENCODING=The Content-Transfer-Encoding should be binary: line '%1$s'.
 +BatchException.INVALID_CONTENT_TYPE=Content-Type should be '%1$s'.
 +BatchException.INVALID_HEADER=Invalid header: '%1$s' at line '%2$s'.
 +BatchException.INVALID_HTTP_VERSION=Invalid HTTP version: The version have to be HTTP/1.1 at line '%1$s'.
 +BatchException.INVALID_METHOD=Invalid HTTP method at line '%1$s'.
 +BatchException.INVALID_QUERY_OPERATION_METHOD=Invalid method: a query operation can only contain retrieve requests at line '%1$s'.
 +BatchException.INVALID_STATUS_LINE=Invalid HTTP status line at line '%1$s'.
 +BatchException.INVALID_URI=Invalid URI at line '%1$s'.
 +BatchException.FORBIDDEN_HEADER=Forbidden header at line '%1$s'.
 +BatchException.MISSING_BLANK_LINE=Missing blank line at line '%1$s'.
 +BatchException.MISSING_BOUNDARY_DELIMITER=Missing boundary delimiter at line '%1$s'.
 +BatchException.MISSING_CLOSE_DELIMITER=Missing close delimiter at line '%1$s'.
 +BatchException.MISSING_CONTENT_ID=Missing content-id at line '%1$s'.
 +BatchException.MISSING_CONTENT_TRANSFER_ENCODING=Missing content transfer encoding at line '%1$s'.
 +BatchException.MISSING_CONTENT_TYPE=Missing content-type at line '%1$s'.
 +BatchException.MISSING_MANDATORY_HEADER=Missing mandatory header at line '%1$s'.

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/4f820fe5/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalProcessor.java
----------------------------------------------------------------------
diff --cc lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalProcessor.java
index e5a0d44,62c0140..4fa3d36
--- a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalProcessor.java
+++ b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalProcessor.java
@@@ -18,10 -18,6 +18,7 @@@
   */
  package org.apache.olingo.server.tecsvc.processor;
  
- import java.io.ByteArrayInputStream;
- import java.io.IOException;
- import java.io.UnsupportedEncodingException;
- import java.util.LinkedList;
++
  import java.util.List;
  import java.util.Locale;
  


[20/22] olingo-odata4 git commit: [OLINGO-472] Batch Refactoring

Posted by ch...@apache.org.
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchParserCommon.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchParserCommon.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchParserCommon.java
new file mode 100644
index 0000000..851a57b
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchParserCommon.java
@@ -0,0 +1,221 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.deserializer.batch;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.olingo.commons.api.http.HttpContentType;
+import org.apache.olingo.server.api.batch.BatchException;
+import org.apache.olingo.server.core.deserializer.batch.BufferedReaderIncludingLineEndings.Line;
+
+public class BatchParserCommon {
+
+  private static final String REG_EX_BOUNDARY =
+      "([a-zA-Z0-9_\\-\\.'\\+]{1,70})|\"([a-zA-Z0-9_\\-\\.'\\+\\s\\" +
+          "(\\),/:=\\?]{1,69}[a-zA-Z0-9_\\-\\.'\\+\\(\\),/:=\\?])\"";
+  private static final Pattern PATTERN_LAST_CRLF = Pattern.compile("(.*)(\r\n){1}( *)", Pattern.DOTALL);
+  private static final Pattern PATTERN_HEADER_LINE = Pattern.compile("([a-zA-Z\\-]+):\\s?(.*)\\s*");
+  private static final String REG_EX_APPLICATION_HTTP = "application/http";
+  
+  public static final Pattern PATTERN_MULTIPART_BOUNDARY = Pattern.compile("multipart/mixed(.*)",
+      Pattern.CASE_INSENSITIVE);
+  public static final Pattern PATTERN_CONTENT_TYPE_APPLICATION_HTTP = Pattern.compile(REG_EX_APPLICATION_HTTP,
+      Pattern.CASE_INSENSITIVE);
+  public static final String BINARY_ENCODING = "binary";
+  public static final String HTTP_CONTENT_ID = "Content-Id";
+  public static final String HTTP_CONTENT_TRANSFER_ENCODING = "Content-Transfer-Encoding";
+  
+  public static final String HTTP_EXPECT = "Expect";
+  public static final String HTTP_FROM = "From";
+  public static final String HTTP_MAX_FORWARDS = "Max-Forwards";
+  public static final String HTTP_RANGE = "Range";
+  public static final String HTTP_TE = "TE";
+  
+  public static String getBoundary(final String contentType, final int line) throws BatchException {
+    if (contentType.toLowerCase(Locale.ENGLISH).startsWith("multipart/mixed")) {
+      final String[] parameter = contentType.split(";");
+
+      for (final String pair : parameter) {
+
+        final String[] attrValue = pair.split("=");
+        if (attrValue.length == 2 && "boundary".equals(attrValue[0].trim().toLowerCase(Locale.ENGLISH))) {
+          if (attrValue[1].matches(REG_EX_BOUNDARY)) {
+            return trimQuota(attrValue[1].trim());
+          } else {
+            throw new BatchException("Invalid boundary format", BatchException.MessageKeys.INVALID_BOUNDARY, "" + line);
+          }
+        }
+
+      }
+    }
+    throw new BatchException("Content type is not multipart mixed", 
+        BatchException.MessageKeys.INVALID_CONTENT_TYPE, HttpContentType.MULTIPART_MIXED);
+  }
+
+  public static String removeEndingSlash(String content) {
+    content = content.trim();
+    int lastSlashIndex = content.lastIndexOf('/');
+
+    return (lastSlashIndex == content.length() - 1) ? content.substring(0, content.length() - 1) : content;
+  }
+
+  private static String trimQuota(String boundary) {
+    if (boundary.matches("\".*\"")) {
+      boundary = boundary.replace("\"", "");
+    }
+
+    return boundary;
+  }
+
+  public static List<List<Line>> splitMessageByBoundary(final List<Line> message, final String boundary)
+      throws BatchException {
+    final List<List<Line>> messageParts = new LinkedList<List<Line>>();
+    List<Line> currentPart = new ArrayList<Line>();
+    boolean isEndReached = false;
+
+    final String quotedBoundary = Pattern.quote(boundary);
+    final Pattern boundaryDelimiterPattern = Pattern.compile("--" + quotedBoundary + "--[\\s ]*");
+    final Pattern boundaryPattern = Pattern.compile("--" + quotedBoundary + "[\\s ]*");
+
+    for (Line currentLine : message) {
+      if (boundaryDelimiterPattern.matcher(currentLine.toString()).matches()) {
+        removeEndingCRLFFromList(currentPart);
+        messageParts.add(currentPart);
+        isEndReached = true;
+      } else if (boundaryPattern.matcher(currentLine.toString()).matches()) {
+        removeEndingCRLFFromList(currentPart);
+        messageParts.add(currentPart);
+        currentPart = new LinkedList<Line>();
+      } else {
+        currentPart.add(currentLine);
+      }
+
+      if (isEndReached) {
+        break;
+      }
+    }
+
+    final int lineNumer = (message.size() > 0) ? message.get(0).getLineNumber() : 0;
+    // Remove preamble
+    if (messageParts.size() > 0) {
+      messageParts.remove(0);
+    }
+
+    if (!isEndReached) {
+      throw new BatchException("Missing close boundary delimiter", BatchException.MessageKeys.MISSING_CLOSE_DELIMITER,
+          "" + lineNumer);
+    }
+
+    return messageParts;
+  }
+
+  private static void removeEndingCRLFFromList(final List<Line> list) {
+    if (list.size() > 0) {
+      Line lastLine = list.remove(list.size() - 1);
+      list.add(removeEndingCRLF(lastLine));
+    }
+  }
+
+  public static Line removeEndingCRLF(final Line line) {
+    Pattern pattern = PATTERN_LAST_CRLF;
+    Matcher matcher = pattern.matcher(line.toString());
+
+    if (matcher.matches()) {
+      return new Line(matcher.group(1), line.getLineNumber());
+    } else {
+      return line;
+    }
+  }
+
+  public static Header consumeHeaders(final List<Line> remainingMessage) {
+    final int headerLineNumber = remainingMessage.size() != 0 ? remainingMessage.get(0).getLineNumber() : 0;
+    final Header headers = new Header(headerLineNumber);
+    final Iterator<Line> iter = remainingMessage.iterator();
+    Line currentLine;
+    boolean isHeader = true;
+
+    while (iter.hasNext() && isHeader) {
+      currentLine = iter.next();
+      final Matcher headerMatcher = PATTERN_HEADER_LINE.matcher(currentLine.toString());
+
+      if (headerMatcher.matches() && headerMatcher.groupCount() == 2) {
+        iter.remove();
+
+        String headerName = headerMatcher.group(1).trim();
+        String headerValue = headerMatcher.group(2).trim();
+
+        headers.addHeader(headerName, Header.splitValuesByComma(headerValue), currentLine.getLineNumber());
+      } else {
+        isHeader = false;
+      }
+    }
+
+    return headers;
+  }
+
+  public static void consumeBlankLine(final List<Line> remainingMessage, final boolean isStrict) throws BatchException {
+    //TODO is \r\n to strict?
+    if (remainingMessage.size() > 0 && remainingMessage.get(0).toString().matches("\\s*(\r\n|\n)\\s*")) {
+      remainingMessage.remove(0);
+    } else {
+      if (isStrict) {
+        final int lineNumber = (remainingMessage.size() > 0) ? remainingMessage.get(0).getLineNumber() : 0;
+        throw new BatchException("Missing blank line", BatchException.MessageKeys.MISSING_BLANK_LINE, "[None]", ""
+            + lineNumber);
+      }
+    }
+  }
+
+  public static InputStream convertLineListToInputStream(List<Line> messageList) {
+    final String message = lineListToString(messageList);
+
+    return new ByteArrayInputStream(message.getBytes());
+  }
+
+  private static String lineListToString(List<Line> messageList) {
+    final StringBuilder builder = new StringBuilder();
+
+    for (Line currentLine : messageList) {
+      builder.append(currentLine.toString());
+    }
+
+    return builder.toString();
+  }
+  
+  public static String trimLineListToLength(final List<Line> list, final int length) {
+    final String message = lineListToString(list);
+    final int lastIndex = Math.min(length, message.length());
+
+    return (lastIndex > 0) ? message.substring(0, lastIndex) : "";
+  }
+  
+  public static InputStream convertLineListToInputStream(List<Line> list, int length) {
+    final String message = trimLineListToLength(list, length);
+
+    return new ByteArrayInputStream(message.getBytes());
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchPart.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchPart.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchPart.java
new file mode 100644
index 0000000..9ee642d
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchPart.java
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.deserializer.batch;
+
+public interface BatchPart {
+  public Header getHeaders();
+
+  public boolean isStrict();
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchQueryOperation.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchQueryOperation.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchQueryOperation.java
new file mode 100644
index 0000000..faffd0f
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchQueryOperation.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.deserializer.batch;
+
+import java.util.List;
+
+import org.apache.olingo.server.api.batch.BatchException;
+import org.apache.olingo.server.core.deserializer.batch.BufferedReaderIncludingLineEndings.Line;
+
+public class BatchQueryOperation implements BatchPart {
+
+  protected final boolean isStrict;
+  protected Line httpStatusLine;
+  protected Header headers;
+  protected List<Line> body;
+  protected int bodySize;
+  protected List<Line> message;
+
+  public BatchQueryOperation(final List<Line> message, final boolean isStrict) {
+    this.isStrict = isStrict;
+    this.message = message;
+  }
+
+  public BatchQueryOperation parse() throws BatchException {
+    httpStatusLine = consumeHttpStatusLine(message);
+    headers = BatchParserCommon.consumeHeaders(message);
+    BatchParserCommon.consumeBlankLine(message, isStrict);
+    body = message;
+
+    return this;
+  }
+
+  protected Line consumeHttpStatusLine(final List<Line> message) throws BatchException {
+    if (message.size() > 0 && !message.get(0).toString().trim().equals("")) {
+      final Line method = message.get(0);
+      message.remove(0);
+
+      return method;
+    } else {
+      final int line = (message.size() > 0) ? message.get(0).getLineNumber() : 0;
+      throw new BatchException("Missing http request line", BatchException.MessageKeys.INVALID_STATUS_LINE, "" + line);
+    }
+  }
+
+  public Line getHttpStatusLine() {
+    return httpStatusLine;
+  }
+
+  public List<Line> getBody() {
+    return body;
+  }
+
+  public int getBodySize() {
+    return bodySize;
+  }
+
+  @Override
+  public Header getHeaders() {
+    return headers;
+  }
+
+  @Override
+  public boolean isStrict() {
+    return isStrict;
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchRequestPartImpl.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchRequestPartImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchRequestPartImpl.java
new file mode 100644
index 0000000..c68e130
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchRequestPartImpl.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.deserializer.batch;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.olingo.server.api.ODataRequest;
+import org.apache.olingo.server.api.deserializer.batch.BatchRequestPart;
+
+/**
+ * Has to be immutable!
+ *
+ */
+public class BatchRequestPartImpl implements BatchRequestPart {
+
+  private List<ODataRequest> requests = new ArrayList<ODataRequest>();
+  private boolean isChangeSet;
+  
+  public BatchRequestPartImpl(final boolean isChangeSet, final List<ODataRequest> requests) {
+    this.isChangeSet = isChangeSet;
+    this.requests = requests;
+  }
+
+  @Override
+  public boolean isChangeSet() {
+    return isChangeSet;
+  }
+
+  @Override
+  public List<ODataRequest> getRequests() {
+    return Collections.unmodifiableList(requests);
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchRequestTransformator.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchRequestTransformator.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchRequestTransformator.java
new file mode 100644
index 0000000..b169b9b
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchRequestTransformator.java
@@ -0,0 +1,184 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.deserializer.batch;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.olingo.commons.api.http.HttpHeader;
+import org.apache.olingo.commons.api.http.HttpMethod;
+import org.apache.olingo.server.api.ODataRequest;
+import org.apache.olingo.server.api.batch.BatchException;
+import org.apache.olingo.server.api.batch.BatchException.MessageKeys;
+import org.apache.olingo.server.api.deserializer.batch.BatchDeserializerResult;
+import org.apache.olingo.server.core.deserializer.batch.HttpRequestStatusLine.ODataURI;
+
+public class BatchRequestTransformator implements BatchTransformator {
+  private final String baseUri;
+  private final String rawServiceResolutionUri;
+
+  public BatchRequestTransformator(final String baseUri, final String serviceResolutionUri) {
+    this.baseUri = baseUri;
+    this.rawServiceResolutionUri = serviceResolutionUri;
+  }
+
+  @Override
+  public List<BatchDeserializerResult> transform(final BatchBodyPart bodyPart) throws BatchException {
+    final List<ODataRequest> requests = new LinkedList<ODataRequest>();
+    final List<BatchDeserializerResult> resultList = new ArrayList<BatchDeserializerResult>();
+
+    validateBodyPartHeader(bodyPart);
+
+    for (BatchQueryOperation queryOperation : bodyPart.getRequests()) {
+      requests.add(processQueryOperation(bodyPart, baseUri, queryOperation));
+    }
+
+    resultList.add(new BatchRequestPartImpl(bodyPart.isChangeSet(), requests));
+    return resultList;
+  }
+
+  private ODataRequest
+      processQueryOperation(BatchBodyPart bodyPart, String baseUri, BatchQueryOperation queryOperation)
+          throws BatchException {
+    if (bodyPart.isChangeSet()) {
+      BatchQueryOperation encapsulatedQueryOperation = ((BatchChangeSetPart) queryOperation).getRequest();
+      handleContentId(queryOperation, encapsulatedQueryOperation);
+      validateHeader(queryOperation, true);
+
+      return createRequest(encapsulatedQueryOperation, baseUri, bodyPart.isChangeSet());
+    } else {
+      return createRequest(queryOperation, baseUri, bodyPart.isChangeSet());
+    }
+  }
+
+  private void handleContentId(BatchQueryOperation changeRequestPart, BatchQueryOperation request)
+      throws BatchException {
+    final HeaderField contentIdChangeRequestPart = getContentId(changeRequestPart);
+    final HeaderField contentIdRequest = getContentId(request);
+
+    if (contentIdChangeRequestPart == null && contentIdRequest == null) {
+      throw new BatchException("Missing content id", MessageKeys.MISSING_CONTENT_ID, changeRequestPart.getHeaders()
+          .getLineNumber());
+    } else if (contentIdChangeRequestPart != null) {
+        request.getHeaders().replaceHeaderField(contentIdChangeRequestPart);
+    }
+  }
+
+  private HeaderField getContentId(final BatchQueryOperation queryOperation) throws BatchException {
+    final HeaderField contentTypeHeader = queryOperation.getHeaders().getHeaderField(BatchParserCommon.HTTP_CONTENT_ID);
+
+    if (contentTypeHeader != null) {
+      if (contentTypeHeader.getValues().size() == 1) {
+        return contentTypeHeader;
+      } else {
+        throw new BatchException("Invalid header", MessageKeys.INVALID_HEADER, contentTypeHeader.getLineNumber());
+      }
+    }
+
+    return null;
+  }
+
+  private ODataRequest createRequest(BatchQueryOperation operation, String baseUri, boolean isChangeSet)
+      throws BatchException {
+    final HttpRequestStatusLine statusLine =
+        new HttpRequestStatusLine(operation.getHttpStatusLine(), baseUri, rawServiceResolutionUri, operation
+            .getHeaders());
+    statusLine.validateHttpMethod(isChangeSet);
+    final ODataURI uri = statusLine.getUri();
+    
+    validateBody(statusLine, operation);
+    InputStream bodyStrean = getBodyStream(operation, statusLine);
+
+    validateForbiddenHeader(operation);
+    
+    final ODataRequest request = new ODataRequest();
+    request.setBody(bodyStrean);
+    request.setMethod(statusLine.getMethod());
+    request.setRawBaseUri(uri.getRawBaseUri());
+    request.setRawODataPath(uri.getRawODataPath());
+    request.setRawQueryPath(uri.getRawQueryPath());
+    request.setRawRequestUri(uri.getRawRequestUri());
+    request.setRawServiceResolutionUri(uri.getRawServiceResolutionUri());
+
+    for (final HeaderField field : operation.getHeaders()) {
+      request.addHeader(field.getFieldName(), field.getValues());
+    }
+
+    return request;
+  }
+
+  private void validateForbiddenHeader(BatchQueryOperation operation) throws BatchException {
+    final Header header = operation.getHeaders();
+
+    if (header.exists(HttpHeader.AUTHORIZATION) || header.exists(BatchParserCommon.HTTP_EXPECT)
+        || header.exists(BatchParserCommon.HTTP_FROM) || header.exists(BatchParserCommon.HTTP_MAX_FORWARDS)
+        || header.exists(BatchParserCommon.HTTP_RANGE) || header.exists(BatchParserCommon.HTTP_TE)) {
+      throw new BatchException("Forbidden header", MessageKeys.FORBIDDEN_HEADER, header.getLineNumber());
+    }
+  }
+
+  private InputStream getBodyStream(BatchQueryOperation operation, HttpRequestStatusLine statusLine)
+      throws BatchException {
+    if (statusLine.getMethod().equals(HttpMethod.GET)) {
+      return new ByteArrayInputStream(new byte[0]);
+    } else {
+      int contentLength = BatchTransformatorCommon.getContentLength(operation.getHeaders());
+
+      if (contentLength == -1) {
+        return BatchParserCommon.convertLineListToInputStream(operation.getBody());
+      } else {
+        return BatchParserCommon.convertLineListToInputStream(operation.getBody(), contentLength);
+      }
+    }
+  }
+
+  private void validateBody(HttpRequestStatusLine statusLine, BatchQueryOperation operation) throws BatchException {
+    if (statusLine.getMethod().equals(HttpMethod.GET) && isUnvalidGetRequestBody(operation)) {
+      throw new BatchException("Invalid request line", MessageKeys.INVALID_CONTENT, statusLine.getLineNumber());
+    }
+  }
+
+  private boolean isUnvalidGetRequestBody(final BatchQueryOperation operation) {
+    return (operation.getBody().size() > 1)
+        || (operation.getBody().size() == 1 && !"".equals(operation.getBody().get(0).toString().trim()));
+  }
+
+  private void validateHeader(BatchPart bodyPart, boolean isChangeSet) throws BatchException {
+    final Header headers = bodyPart.getHeaders();
+
+    BatchTransformatorCommon.validateContentType(headers, BatchParserCommon.PATTERN_CONTENT_TYPE_APPLICATION_HTTP);
+    if (isChangeSet) {
+      BatchTransformatorCommon.validateContentTransferEncoding(headers);
+    }
+  }
+
+  private void validateBodyPartHeader(BatchBodyPart bodyPart) throws BatchException {
+    final Header header = bodyPart.getHeaders();
+
+    if (bodyPart.isChangeSet()) {
+      BatchTransformatorCommon.validateContentType(header, BatchParserCommon.PATTERN_MULTIPART_BOUNDARY);
+    } else {
+      BatchTransformatorCommon.validateContentTransferEncoding(header);
+      BatchTransformatorCommon.validateContentType(header, BatchParserCommon.PATTERN_CONTENT_TYPE_APPLICATION_HTTP);
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchTransformator.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchTransformator.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchTransformator.java
new file mode 100644
index 0000000..462a2e2
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchTransformator.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.deserializer.batch;
+
+import java.util.List;
+
+import org.apache.olingo.server.api.batch.BatchException;
+import org.apache.olingo.server.api.deserializer.batch.BatchDeserializerResult;
+
+public interface BatchTransformator {
+  public List<BatchDeserializerResult> transform(BatchBodyPart bodyPart) throws BatchException;
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchTransformatorCommon.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchTransformatorCommon.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchTransformatorCommon.java
new file mode 100644
index 0000000..4738641
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BatchTransformatorCommon.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.deserializer.batch;
+
+import java.util.List;
+import java.util.regex.Pattern;
+
+import org.apache.olingo.commons.api.http.HttpContentType;
+import org.apache.olingo.commons.api.http.HttpHeader;
+import org.apache.olingo.server.api.batch.BatchException;
+import org.apache.olingo.server.api.batch.BatchException.MessageKeys;
+
+public class BatchTransformatorCommon {
+
+  public static void validateContentType(final Header headers, final Pattern pattern) throws BatchException {
+    List<String> contentTypes = headers.getHeaders(HttpHeader.CONTENT_TYPE);
+
+    if (contentTypes.size() == 0) {
+      throw new BatchException("Missing content type", MessageKeys.MISSING_CONTENT_TYPE, headers.getLineNumber());
+    }
+    if (!headers.isHeaderMatching(HttpHeader.CONTENT_TYPE, pattern)) {
+
+      throw new BatchException("Invalid content type", MessageKeys.INVALID_CONTENT_TYPE,
+          HttpContentType.MULTIPART_MIXED + " or " + HttpContentType.APPLICATION_HTTP);
+    }
+  }
+
+  public static void validateContentTransferEncoding(Header headers) throws BatchException {
+    final HeaderField contentTransferField = headers.getHeaderField(BatchParserCommon.HTTP_CONTENT_TRANSFER_ENCODING);
+
+    if (contentTransferField != null) {
+      final List<String> contentTransferValues = contentTransferField.getValues();
+      if (contentTransferValues.size() == 1) {
+        String encoding = contentTransferValues.get(0);
+
+        if (!BatchParserCommon.BINARY_ENCODING.equalsIgnoreCase(encoding)) {
+          throw new BatchException("Invalid content transfer encoding", MessageKeys.INVALID_CONTENT_TRANSFER_ENCODING,
+              headers.getLineNumber());
+        }
+      } else {
+        throw new BatchException("Invalid header", MessageKeys.INVALID_HEADER, headers.getLineNumber());
+      }
+    } else {
+      throw new BatchException("Missing mandatory content transfer encoding",
+          MessageKeys.MISSING_CONTENT_TRANSFER_ENCODING,
+          headers.getLineNumber());
+    }
+  }
+
+  public static int getContentLength(Header headers) throws BatchException {
+    final HeaderField contentLengthField = headers.getHeaderField(HttpHeader.CONTENT_LENGTH);
+
+    if (contentLengthField != null && contentLengthField.getValues().size() == 1) {
+      final List<String> contentLengthValues = contentLengthField.getValues();
+
+      try {
+        int contentLength = Integer.parseInt(contentLengthValues.get(0));
+
+        if (contentLength < 0) {
+          throw new BatchException("Invalid content length", MessageKeys.INVALID_CONTENT_LENGTH, contentLengthField
+              .getLineNumber());
+        }
+
+        return contentLength;
+      } catch (NumberFormatException e) {
+        throw new BatchException("Invalid header", MessageKeys.INVALID_HEADER, contentLengthField.getLineNumber());
+      }
+    }
+
+    return -1;
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BufferedReaderIncludingLineEndings.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BufferedReaderIncludingLineEndings.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BufferedReaderIncludingLineEndings.java
new file mode 100644
index 0000000..64b4bcb
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/BufferedReaderIncludingLineEndings.java
@@ -0,0 +1,286 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.deserializer.batch;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.List;
+
+public class BufferedReaderIncludingLineEndings extends Reader {
+  private static final char CR = '\r';
+  private static final char LF = '\n';
+  private static final int EOF = -1;
+  private static final int BUFFER_SIZE = 8192;
+  private Reader reader;
+  private char[] buffer;
+  private int offset = 0;
+  private int limit = 0;
+
+  public BufferedReaderIncludingLineEndings(final Reader reader) {
+    this(reader, BUFFER_SIZE);
+  }
+
+  public BufferedReaderIncludingLineEndings(final Reader reader, final int bufferSize) {
+    if (bufferSize <= 0) {
+      throw new IllegalArgumentException("Buffer size must be greater than zero.");
+    }
+
+    this.reader = reader;
+    buffer = new char[bufferSize];
+  }
+
+  @Override
+  public int read(final char[] charBuffer, final int bufferOffset, final int length) throws IOException {
+    if ((bufferOffset + length) > charBuffer.length) {
+      throw new IndexOutOfBoundsException("Buffer is too small");
+    }
+
+    if (length < 0 || bufferOffset < 0) {
+      throw new IndexOutOfBoundsException("Offset and length must be grater than zero");
+    }
+
+    // Check if buffer is filled. Return if EOF is reached
+    // Is buffer refill required
+    if (limit == offset || isEOF()) {
+      fillBuffer();
+
+      if (isEOF()) {
+        return EOF;
+      }
+    }
+
+    int bytesRead = 0;
+    int bytesToRead = length;
+    int currentOutputOffset = bufferOffset;
+
+    while (bytesToRead != 0) {
+      // Is buffer refill required?
+      if (limit == offset) {
+        fillBuffer();
+
+        if (isEOF()) {
+          bytesToRead = 0;
+        }
+      }
+
+      if (bytesToRead > 0) {
+        int readByte = Math.min(limit - offset, bytesToRead);
+        bytesRead += readByte;
+        bytesToRead -= readByte;
+
+        for (int i = 0; i < readByte; i++) {
+          charBuffer[currentOutputOffset++] = buffer[offset++];
+        }
+      }
+    }
+
+    return bytesRead;
+  }
+  
+  public List<String> toList() throws IOException {
+    final List<String> result = new ArrayList<String>();
+    String currentLine;
+    
+    while ((currentLine = readLine()) != null) {
+      result.add(currentLine);
+    }
+
+    return result;
+  }
+  
+  public List<Line> toLineList() throws IOException {
+    final List<Line> result = new ArrayList<Line>();
+    String currentLine;
+    int counter = 1;
+
+    while ((currentLine = readLine()) != null) {
+      result.add(new Line(currentLine, counter++));
+    }
+
+    return result;
+  }
+
+  public String readLine() throws IOException {
+    if (limit == EOF) {
+      return null;
+    }
+
+    final StringBuilder stringBuffer = new StringBuilder();
+    boolean foundLineEnd = false; // EOF will be considered as line ending
+
+    while (!foundLineEnd) {
+      // Is buffer refill required?
+      if (limit == offset) {
+        if (fillBuffer() == EOF) {
+          foundLineEnd = true;
+        }
+      }
+
+      if (!foundLineEnd) {
+        char currentChar = buffer[offset++];
+        stringBuffer.append(currentChar);
+
+        if (currentChar == LF) {
+          foundLineEnd = true;
+        } else if (currentChar == CR) {
+          foundLineEnd = true;
+
+          // Check next char. Consume \n if available
+          // Is buffer refill required?
+          if (limit == offset) {
+            fillBuffer();
+          }
+
+          // Check if there is at least one character
+          if (limit != EOF && buffer[offset] == LF) {
+            stringBuffer.append(LF);
+            offset++;
+          }
+        }
+      }
+    }
+
+    return (stringBuffer.length() == 0) ? null : stringBuffer.toString();
+  }
+
+  @Override
+  public void close() throws IOException {
+    reader.close();
+  }
+
+  @Override
+  public boolean ready() throws IOException {
+    // Not EOF and buffer refill is not required
+    return !isEOF() && !(limit == offset);
+  }
+
+  @Override
+  public void reset() throws IOException {
+    throw new IOException("Reset is not supported");
+  }
+
+  @Override
+  public void mark(final int readAheadLimit) throws IOException {
+    throw new IOException("Mark is not supported");
+  }
+
+  @Override
+  public boolean markSupported() {
+    return false;
+  }
+
+  @Override
+  public long skip(final long n) throws IOException {
+    if (n == 0) {
+      return 0;
+    } else if (n < 0) {
+      throw new IllegalArgumentException("skip value is negative");
+    } else {
+      long charactersToSkip = n;
+      long charactersSkiped = 0;
+
+      while (charactersToSkip != 0) {
+        // Is buffer refill required?
+        if (limit == offset) {
+          fillBuffer();
+
+          if (isEOF()) {
+            charactersToSkip = 0;
+          }
+        }
+
+        // Check if more characters are available
+        if (!isEOF()) {
+          int skipChars = (int) Math.min(limit - offset, charactersToSkip);
+
+          charactersSkiped += skipChars;
+          charactersToSkip -= skipChars;
+          offset += skipChars;
+        }
+      }
+
+      return charactersSkiped;
+    }
+  }
+
+  private boolean isEOF() {
+    return limit == EOF;
+  }
+
+  private int fillBuffer() throws IOException {
+    limit = reader.read(buffer, 0, buffer.length);
+    offset = 0;
+
+    return limit;
+  }
+
+  public static class Line {
+    private final int lineNumber;
+    private final String content;
+
+    public Line(final String content, final int lineNumber) {
+      this.content = content;
+      this.lineNumber = lineNumber;
+    }
+
+    public int getLineNumber() {
+      return lineNumber;
+    }
+
+    @Override
+    public String toString() {
+      return content;
+    }
+
+    @Override
+    public int hashCode() {
+      final int prime = 31;
+      int result = 1;
+      result = prime * result + ((content == null) ? 0 : content.hashCode());
+      result = prime * result + lineNumber;
+      return result;
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+      if (this == obj) {
+        return true;
+      }
+      if (obj == null) {
+        return false;
+      }
+      if (getClass() != obj.getClass()) {
+        return false;
+      }
+      Line other = (Line) obj;
+      if (content == null) {
+        if (other.content != null) {
+          return false;
+        }
+      } else if (!content.equals(other.content)) {
+        return false;
+      }
+      if (lineNumber != other.lineNumber) {
+        return false;
+      }
+      return true;
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/Header.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/Header.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/Header.java
new file mode 100644
index 0000000..a318201
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/Header.java
@@ -0,0 +1,181 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.deserializer.batch;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+public class Header implements Iterable<HeaderField> {
+  private final Map<String, HeaderField> headers = new HashMap<String, HeaderField>();
+  private int lineNumber;
+
+  public Header(final int lineNumer) {
+    lineNumber = lineNumer;
+  }
+
+  public void addHeader(final String name, final String value, final int lineNumber) {
+    final HeaderField headerField = getHeaderFieldOrDefault(name, lineNumber);
+    final List<String> headerValues = headerField.getValues();
+
+    if (!headerValues.contains(value)) {
+      headerValues.add(value);
+    }
+  }
+
+  public void addHeader(final String name, final List<String> values, final int lineNumber) {
+    final HeaderField headerField = getHeaderFieldOrDefault(name, lineNumber);
+    final List<String> headerValues = headerField.getValues();
+
+    for (final String value : values) {
+      if (!headerValues.contains(value)) {
+        headerValues.add(value);
+      }
+    }
+  }
+
+  public void replaceHeaderField(final HeaderField headerField) {
+    headers.put(headerField.getFieldName().toLowerCase(Locale.ENGLISH), headerField);
+  }
+
+  public boolean exists(final String name) {
+    final HeaderField field = headers.get(name.toLowerCase(Locale.ENGLISH));
+    
+    return field != null && field.getValues().size() != 0;
+  }
+
+  public boolean isHeaderMatching(final String name, final Pattern pattern) {
+    if (getHeaders(name).size() != 1) {
+      return false;
+    } else {
+      return pattern.matcher(getHeaders(name).get(0)).matches();
+    }
+  }
+
+  public void removeHeader(final String name) {
+    headers.remove(name.toLowerCase(Locale.ENGLISH));
+  }
+
+  public String getHeader(final String name) {
+    final HeaderField headerField = getHeaderField(name);
+
+    return (headerField == null) ? null : headerField.getValue();
+  }
+
+  public String getHeaderNotNull(final String name) {
+    final HeaderField headerField = getHeaderField(name);
+
+    return (headerField == null) ? "" : headerField.getValueNotNull();
+  }
+
+  public List<String> getHeaders(final String name) {
+    final HeaderField headerField = getHeaderField(name);
+
+    return (headerField == null) ? new ArrayList<String>() : headerField.getValues();
+  }
+
+  public HeaderField getHeaderField(final String name) {
+    return headers.get(name.toLowerCase(Locale.ENGLISH));
+  }
+
+  public int getLineNumber() {
+    return lineNumber;
+  }
+
+  public Map<String, String> toSingleMap() {
+    final Map<String, String> singleMap = new HashMap<String, String>();
+
+    for (final String key : headers.keySet()) {
+      HeaderField field = headers.get(key);
+      singleMap.put(field.getFieldName(), getHeader(key));
+    }
+
+    return singleMap;
+  }
+
+  public Map<String, List<String>> toMultiMap() {
+    final Map<String, List<String>> singleMap = new HashMap<String, List<String>>();
+
+    for (final String key : headers.keySet()) {
+      HeaderField field = headers.get(key);
+      singleMap.put(field.getFieldName(), field.getValues());
+    }
+
+    return singleMap;
+  }
+
+  private HeaderField getHeaderFieldOrDefault(final String name, final int lineNumber) {
+    HeaderField headerField = headers.get(name.toLowerCase(Locale.ENGLISH));
+
+    if (headerField == null) {
+      headerField = new HeaderField(name, lineNumber);
+      headers.put(name.toLowerCase(Locale.ENGLISH), headerField);
+    }
+
+    return headerField;
+  }
+
+  @Override
+  public Header clone() {
+    final Header newInstance = new Header(lineNumber);
+
+    for (final String key : headers.keySet()) {
+      newInstance.headers.put(key, headers.get(key).clone());
+    }
+
+    return newInstance;
+  }
+
+  @Override
+  public Iterator<HeaderField> iterator() {
+    return new Iterator<HeaderField>() {
+      Iterator<String> keyIterator = headers.keySet().iterator();
+
+      @Override
+      public boolean hasNext() {
+        return keyIterator.hasNext();
+      }
+
+      @Override
+      public HeaderField next() {
+        return headers.get(keyIterator.next());
+      }
+      
+      @Override
+      public void remove() {
+        throw new UnsupportedOperationException();
+      }
+    };
+  }
+
+  public static List<String> splitValuesByComma(final String headerValue) {
+    final List<String> singleValues = new ArrayList<String>();
+
+    String[] parts = headerValue.split(",");
+    for (final String value : parts) {
+      singleValues.add(value.trim());
+    }
+
+    return singleValues;
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/HeaderField.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/HeaderField.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/HeaderField.java
new file mode 100644
index 0000000..acd3231
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/HeaderField.java
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.deserializer.batch;
+
+import java.util.ArrayList;
+import java.util.List;
+
+  public class HeaderField implements Cloneable {
+    private final String fieldName;
+    private final List<String> values;
+    private final int lineNumber;
+
+    public HeaderField(final String fieldName, final int lineNumber) {
+      this(fieldName, new ArrayList<String>(), lineNumber);
+    }
+
+    public HeaderField(final String fieldName, final List<String> values, final int lineNumber) {
+      this.fieldName = fieldName;
+      this.values = values;
+      this.lineNumber = lineNumber;
+    }
+
+    public String getFieldName() {
+      return fieldName;
+    }
+
+    public List<String> getValues() {
+      return values;
+    }
+
+    public String getValue() {
+      final StringBuilder result = new StringBuilder();
+
+      for (final String value : values) {
+        result.append(value);
+        result.append(", ");
+      }
+
+      if (result.length() > 0) {
+        result.delete(result.length() - 2, result.length());
+      }
+
+      return result.toString();
+    }
+
+    public String getValueNotNull() {
+      final String value = getValue();
+
+      return (value == null) ? "" : value;
+    }
+
+    @Override
+    public HeaderField clone() {
+      List<String> newValues = new ArrayList<String>();
+      newValues.addAll(values);
+
+      return new HeaderField(fieldName, newValues, lineNumber);
+    }
+
+    public int getLineNumber() {
+      return lineNumber;
+    }
+
+    @Override
+    public int hashCode() {
+      final int prime = 31;
+      int result = 1;
+      result = prime * result + ((fieldName == null) ? 0 : fieldName.hashCode());
+      result = prime * result + lineNumber;
+      result = prime * result + ((values == null) ? 0 : values.hashCode());
+      return result;
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+      if (this == obj) {
+        return true;
+      }
+      if (obj == null) {
+        return false;
+      }
+      if (getClass() != obj.getClass()) {
+        return false;
+      }
+      HeaderField other = (HeaderField) obj;
+      if (fieldName == null) {
+        if (other.fieldName != null) {
+          return false;
+        }
+      } else if (!fieldName.equals(other.fieldName)) {
+        return false;
+      }
+      if (lineNumber != other.lineNumber) {
+        return false;
+      }
+      if (values == null) {
+        if (other.values != null) {
+          return false;
+        }
+      } else if (!values.equals(other.values)) {
+        return false;
+      }
+      return true;
+    }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/HttpRequestStatusLine.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/HttpRequestStatusLine.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/HttpRequestStatusLine.java
new file mode 100644
index 0000000..1384770
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/batch/HttpRequestStatusLine.java
@@ -0,0 +1,225 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.deserializer.batch;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.olingo.commons.api.http.HttpHeader;
+import org.apache.olingo.commons.api.http.HttpMethod;
+import org.apache.olingo.server.api.batch.BatchException;
+import org.apache.olingo.server.api.batch.BatchException.MessageKeys;
+import org.apache.olingo.server.core.deserializer.batch.BufferedReaderIncludingLineEndings.Line;
+
+public class HttpRequestStatusLine {
+  private static final Pattern PATTERN_RELATIVE_URI = Pattern.compile("([^/][^?]*)(?:\\?(.*))?");
+  private static final Pattern PATTERN_ABSOLUTE_URI_WITH_HOST = Pattern.compile("(/[^?]*)(?:\\?(.*))?");
+  private static final Pattern PATTERN_ABSOLUTE_URI = Pattern.compile("(http[s]?://[^?]*)(?:\\?(.*))?");
+
+  private static final Set<String> HTTP_BATCH_METHODS = new HashSet<String>(Arrays.asList(new String[] { "GET" }));
+  private static final Set<String> HTTP_CHANGE_SET_METHODS = new HashSet<String>(Arrays.asList(new String[] { "POST",
+      "PUT", "DELETE", "PATCH" }));
+  private static final String HTTP_VERSION = "HTTP/1.1";
+
+  final private Line statusLine;
+  final String requestBaseUri;
+
+  private HttpMethod method;
+  private String httpVersion;
+  private Header header;
+  private ODataURI uri;
+
+  public HttpRequestStatusLine(final Line httpStatusLine, final String baseUri, final String serviceResolutionUri,
+      final Header requestHeader)
+      throws BatchException {
+    statusLine = httpStatusLine;
+    requestBaseUri = baseUri;
+    header = requestHeader;
+
+    parse();
+  }
+
+  private void parse() throws BatchException {
+    final String[] parts = statusLine.toString().split(" ");
+
+    if (parts.length == 3) {
+      method = parseMethod(parts[0]);
+      uri = new ODataURI(parts[1], requestBaseUri, statusLine.getLineNumber(), header.getHeaders(HttpHeader.HOST));
+      httpVersion = parseHttpVersion(parts[2]);
+    } else {
+      throw new BatchException("Invalid status line", MessageKeys.INVALID_STATUS_LINE, statusLine.getLineNumber());
+    }
+  }
+
+  private HttpMethod parseMethod(final String method) throws BatchException {
+    try {
+      return HttpMethod.valueOf(method.trim());
+    } catch (IllegalArgumentException e) {
+      throw new BatchException("Illegal http method", MessageKeys.INVALID_METHOD, statusLine.getLineNumber());
+    }
+  }
+
+  private String parseHttpVersion(final String httpVersion) throws BatchException {
+    if (!HTTP_VERSION.equals(httpVersion.trim())) {
+      throw new BatchException("Invalid http version", MessageKeys.INVALID_HTTP_VERSION, statusLine.getLineNumber());
+    } else {
+      return HTTP_VERSION;
+    }
+  }
+
+  public void validateHttpMethod(boolean isChangeSet) throws BatchException {
+    Set<String> validMethods = (isChangeSet) ? HTTP_CHANGE_SET_METHODS : HTTP_BATCH_METHODS;
+
+    if (!validMethods.contains(getMethod().toString())) {
+      if (isChangeSet) {
+        throw new BatchException("Invalid change set method", MessageKeys.INVALID_CHANGESET_METHOD, statusLine
+            .getLineNumber());
+      } else {
+        throw new BatchException("Invalid query operation method", MessageKeys.INVALID_QUERY_OPERATION_METHOD,
+            statusLine.getLineNumber());
+      }
+    }
+  }
+
+  public HttpMethod getMethod() {
+    return method;
+  }
+
+  public String getHttpVersion() {
+    return httpVersion;
+  }
+
+  public int getLineNumber() {
+    return statusLine.getLineNumber();
+  }
+
+  public ODataURI getUri() {
+    return uri;
+  }
+
+  public static class ODataURI {
+    private String rawServiceResolutionUri;
+    private String rawQueryPath;
+    private String rawODataPath;
+    private String rawBaseUri;
+    private String rawRequestUri;
+    private final String requestBaseUri;
+    private final int lineNumber;
+
+    public ODataURI(final String rawUri, String requestBaseUri) throws BatchException {
+      this(rawUri, requestBaseUri, 0, new ArrayList<String>());
+    }
+
+    public ODataURI(final String rawUri, String requestBaseUri, int lineNumber, List<String> hostHeader)
+        throws BatchException {
+      this.lineNumber = lineNumber;
+      this.requestBaseUri = requestBaseUri;
+
+      final Matcher absoluteUriMatcher = PATTERN_ABSOLUTE_URI.matcher(rawUri);
+      final Matcher absoluteUriWtithHostMatcher = PATTERN_ABSOLUTE_URI_WITH_HOST.matcher(rawUri);
+      final Matcher relativeUriMatcher = PATTERN_RELATIVE_URI.matcher(rawUri);
+
+      if (absoluteUriMatcher.matches()) {
+        buildUri(absoluteUriMatcher.group(1), absoluteUriMatcher.group(2));
+
+      } else if (absoluteUriWtithHostMatcher.matches()) {
+        if (hostHeader != null && hostHeader.size() == 1) {
+          buildUri(hostHeader.get(0) + absoluteUriWtithHostMatcher.group(1), absoluteUriWtithHostMatcher.group(2));
+        } else {
+          throw new BatchException("Exactly one host header is required", MessageKeys.MISSING_MANDATORY_HEADER,
+              lineNumber);
+        }
+
+      } else if (relativeUriMatcher.matches()) {
+        buildUri(requestBaseUri + "/" + relativeUriMatcher.group(1), relativeUriMatcher.group(2));
+
+      } else {
+        throw new BatchException("Invalid uri", MessageKeys.INVALID_URI, lineNumber);
+      }
+    }
+
+    private void buildUri(final String resourceUri, final String queryOptions) throws BatchException {
+      if (!resourceUri.startsWith(requestBaseUri)) {
+        throw new BatchException("Host do not match", MessageKeys.INVALID_URI, lineNumber);
+      }
+
+      final int oDataPathIndex = resourceUri.indexOf(requestBaseUri);
+
+      rawBaseUri = requestBaseUri;
+      rawODataPath = resourceUri.substring(oDataPathIndex + requestBaseUri.length());
+      rawRequestUri = requestBaseUri + rawODataPath;
+
+      if (queryOptions != null) {
+        rawRequestUri += "?" + queryOptions;
+        rawQueryPath = queryOptions;
+      } else {
+        rawQueryPath = "";
+      }
+    }
+
+    public String getRawServiceResolutionUri() {
+      return rawServiceResolutionUri;
+    }
+
+    public void setRawServiceResolutionUri(String rawServiceResolutionUri) {
+      this.rawServiceResolutionUri = rawServiceResolutionUri;
+    }
+
+    public String getRawQueryPath() {
+      return rawQueryPath;
+    }
+
+    public void setRawQueryPath(String rawQueryPath) {
+      this.rawQueryPath = rawQueryPath;
+    }
+
+    public String getRawODataPath() {
+      return rawODataPath;
+    }
+
+    public void setRawODataPath(String rawODataPath) {
+      this.rawODataPath = rawODataPath;
+    }
+
+    public String getRawBaseUri() {
+      return rawBaseUri;
+    }
+
+    public void setRawBaseUri(String rawBaseUri) {
+      this.rawBaseUri = rawBaseUri;
+    }
+
+    public String getRawRequestUri() {
+      return rawRequestUri;
+    }
+
+    public void setRawRequestUri(String rawRequestUri) {
+      this.rawRequestUri = rawRequestUri;
+    }
+
+    public String getRequestBaseUri() {
+      return requestBaseUri;
+    }
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/BatchResponseSerializer.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/BatchResponseSerializer.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/BatchResponseSerializer.java
new file mode 100644
index 0000000..6f7255c
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/BatchResponseSerializer.java
@@ -0,0 +1,228 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.core.serializer;
+
+import java.io.BufferedWriter;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStreamWriter;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import org.apache.olingo.commons.api.http.HttpContentType;
+import org.apache.olingo.commons.api.http.HttpHeader;
+import org.apache.olingo.commons.api.http.HttpStatusCode;
+import org.apache.olingo.server.api.ODataResponse;
+import org.apache.olingo.server.api.batch.BatchException;
+import org.apache.olingo.server.api.batch.BatchException.MessageKeys;
+import org.apache.olingo.server.api.deserializer.batch.ODataResponsePart;
+import org.apache.olingo.server.core.deserializer.batch.BatchParserCommon;
+import org.apache.olingo.server.core.serializer.utils.CircleStreamBuffer;
+
+public class BatchResponseSerializer {
+  private static final int BUFFER_SIZE = 4096;
+  private static final String DOUBLE_DASH = "--";
+  private static final String COLON = ":";
+  private static final String SP = " ";
+  private static final String CRLF = "\r\n";
+
+  public void toODataResponse(final List<ODataResponsePart> batchResponse, final ODataResponse response)
+      throws IOException, BatchException {
+    final String boundary = generateBoundary("batch");
+
+    setStatusCode(response);
+    ResponseWriter writer = createBody(batchResponse, boundary);
+
+    response.setContent(writer.toInputStream());
+    setHttpHeader(response, writer, boundary);
+  }
+
+  private ResponseWriter createBody(final List<ODataResponsePart> batchResponses, final String boundary)
+      throws IOException, BatchException {
+    final ResponseWriter writer = new ResponseWriter();
+
+    for (final ODataResponsePart part : batchResponses) {
+      writer.append(getDashBoundary(boundary));
+
+      if (part.isChangeSet()) {
+        appendChangeSet(part, writer);
+      } else {
+        appendBodyPart(part.getResponses().get(0), writer, false);
+      }
+    }
+    writer.append(getCloseDelimiter(boundary));
+
+    return writer;
+  }
+
+  private void appendChangeSet(ODataResponsePart part, ResponseWriter writer) throws IOException, BatchException {
+    final String changeSetBoundary = generateBoundary("changeset");
+
+    appendChangeSetHeader(writer, changeSetBoundary);
+    writer.append(CRLF);
+
+    for (final ODataResponse response : part.getResponses()) {
+      writer.append(getDashBoundary(changeSetBoundary));
+      appendBodyPart(response, writer, true);
+    }
+
+    writer.append(getCloseDelimiter(changeSetBoundary));
+    writer.append(CRLF);
+  }
+
+  private void appendBodyPart(ODataResponse response, ResponseWriter writer, boolean isChangeSet) throws IOException,
+      BatchException {
+    byte[] body = getBody(response);
+    
+    appendBodyPartHeader(response, writer, isChangeSet);
+    writer.append(CRLF);
+
+    appendStatusLine(response, writer);
+    appendResponseHeader(response, body.length, writer);
+    writer.append(CRLF);
+
+    writer.append(body);
+    writer.append(CRLF);
+  }
+
+  private byte[] getBody(final ODataResponse response) throws IOException {
+    final InputStream content = response.getContent();
+    final ByteArrayOutputStream out = new ByteArrayOutputStream();
+    
+    if (content != null) {
+      byte[] buffer = new byte[BUFFER_SIZE];
+      int n;
+
+      while ((n = content.read(buffer, 0, buffer.length)) != -1) {
+        out.write(buffer, 0, n);
+      }
+      out.flush();
+      
+      return out.toByteArray();
+    } else {
+      return new byte[0];
+    }
+  }
+
+  private void appendChangeSetHeader(ResponseWriter writer, final String changeSetBoundary) throws IOException {
+    appendHeader(HttpHeader.CONTENT_TYPE, HttpContentType.MULTIPART_MIXED.toString() + "; boundary="
+        + changeSetBoundary, writer);
+  }
+
+  private void appendHeader(String name, String value, ResponseWriter writer) throws IOException {
+    writer.append(name)
+        .append(COLON)
+        .append(SP)
+        .append(value)
+        .append(CRLF);
+  }
+
+  private void appendStatusLine(ODataResponse response, ResponseWriter writer) throws IOException {
+    writer.append("HTTP/1.1")
+        .append(SP)
+        .append("" + response.getStatusCode())
+        .append(SP)
+        .append(HttpStatusCode.fromStatusCode(response.getStatusCode()).toString())
+        .append(CRLF);
+  }
+
+  private void appendResponseHeader(ODataResponse response, int contentLength, ResponseWriter writer)
+      throws IOException {
+    final Map<String, String> header = response.getHeaders();
+
+    for (final String key : header.keySet()) {
+      // Requests do never has a content id header
+      if (!key.equalsIgnoreCase(BatchParserCommon.HTTP_CONTENT_ID)) {
+        appendHeader(key, header.get(key), writer);
+      }
+    }
+
+    appendHeader(HttpHeader.CONTENT_LENGTH, "" + contentLength, writer);
+  }
+
+  private void appendBodyPartHeader(ODataResponse response, ResponseWriter writer, boolean isChangeSet)
+      throws BatchException, IOException {
+    appendHeader(HttpHeader.CONTENT_TYPE, HttpContentType.APPLICATION_HTTP, writer);
+    appendHeader(BatchParserCommon.HTTP_CONTENT_TRANSFER_ENCODING, BatchParserCommon.BINARY_ENCODING, writer);
+
+    if (isChangeSet) {
+      if (response.getHeaders().get(BatchParserCommon.HTTP_CONTENT_ID) != null) {
+        appendHeader(BatchParserCommon.HTTP_CONTENT_ID, response.getHeaders().get(BatchParserCommon.HTTP_CONTENT_ID),
+            writer);
+      } else {
+        throw new BatchException("Missing content id", MessageKeys.MISSING_CONTENT_ID, "");
+      }
+    }
+  }
+
+  private void setHttpHeader(ODataResponse response, ResponseWriter writer, final String boundary) {
+    response.setHeader(HttpHeader.CONTENT_TYPE, HttpContentType.MULTIPART_MIXED.toString() + "; boundary=" + boundary);
+    response.setHeader(HttpHeader.CONTENT_LENGTH, "" + writer.length());
+  }
+
+  private void setStatusCode(final ODataResponse response) {
+    response.setStatusCode(HttpStatusCode.ACCEPTED.getStatusCode());
+  }
+
+  private String getDashBoundary(String boundary) {
+    return DOUBLE_DASH + boundary + CRLF;
+  }
+
+  private String getCloseDelimiter(final String boundary) {
+    return DOUBLE_DASH + boundary + DOUBLE_DASH + CRLF;
+  }
+
+  private String generateBoundary(final String value) {
+    return value + "_" + UUID.randomUUID().toString();
+  }
+
+  private static class ResponseWriter {
+    private CircleStreamBuffer buffer = new CircleStreamBuffer();
+    private BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(buffer.getOutputStream()));
+    private int length = 0;
+
+    public ResponseWriter append(final String content) throws IOException {
+      length += content.length();
+      writer.write(content);
+      
+      return this;
+    }
+
+    public ResponseWriter append(final byte[] content) throws IOException {
+      length += content.length;
+      writer.flush();
+      buffer.getOutputStream().write(content, 0, content.length);
+      
+      return this;
+    }
+
+    public int length() {
+      return length;
+    }
+
+    public InputStream toInputStream() throws IOException {
+      writer.flush();
+      writer.close();
+
+      return buffer.getInputStream();
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/FixedFormatSerializerImpl.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/FixedFormatSerializerImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/FixedFormatSerializerImpl.java
index 01951ae..e165b75 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/FixedFormatSerializerImpl.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/serializer/FixedFormatSerializerImpl.java
@@ -19,11 +19,16 @@
 package org.apache.olingo.server.core.serializer;
 
 import java.io.ByteArrayInputStream;
+import java.io.IOException;
 import java.io.InputStream;
 import java.io.UnsupportedEncodingException;
+import java.util.List;
 
 import org.apache.olingo.commons.api.edm.EdmPrimitiveType;
 import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeException;
+import org.apache.olingo.server.api.ODataResponse;
+import org.apache.olingo.server.api.batch.BatchException;
+import org.apache.olingo.server.api.deserializer.batch.ODataResponsePart;
 import org.apache.olingo.server.api.serializer.FixedFormatSerializer;
 import org.apache.olingo.server.api.serializer.PrimitiveValueSerializerOptions;
 import org.apache.olingo.server.api.serializer.SerializerException;
@@ -56,4 +61,12 @@ public class FixedFormatSerializerImpl implements FixedFormatSerializer {
       throw new SerializerException("Encoding exception.", e, SerializerException.MessageKeys.IO_EXCEPTION);
     }
   }
+
+  //TODO: Signature
+  @Override
+  public void writeResponseParts(List<ODataResponsePart> batchResponses, ODataResponse response) throws BatchException,
+      IOException {
+    BatchResponseSerializer writer = new BatchResponseSerializer();
+    writer.toODataResponse(batchResponses, response);
+  }
 }


[11/22] olingo-odata4 git commit: Merge method removed

Posted by ch...@apache.org.
Merge method removed

Signed-off-by: Christian Amend <ch...@apache.org>


Project: http://git-wip-us.apache.org/repos/asf/olingo-odata4/repo
Commit: http://git-wip-us.apache.org/repos/asf/olingo-odata4/commit/5f4eb03d
Tree: http://git-wip-us.apache.org/repos/asf/olingo-odata4/tree/5f4eb03d
Diff: http://git-wip-us.apache.org/repos/asf/olingo-odata4/diff/5f4eb03d

Branch: refs/heads/master
Commit: 5f4eb03df3e980dd2c4593ddb900e15290ac057d
Parents: bb4b554
Author: Christian Holzer <c....@sap.com>
Authored: Thu Nov 6 17:48:33 2014 +0100
Committer: Christian Amend <ch...@apache.org>
Committed: Thu Nov 13 17:10:59 2014 +0100

----------------------------------------------------------------------
 .../server/core/batch/transformator/HttpRequestStatusLine.java  | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/5f4eb03d/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/HttpRequestStatusLine.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/HttpRequestStatusLine.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/HttpRequestStatusLine.java
index 416d593..c08bb2e 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/HttpRequestStatusLine.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/transformator/HttpRequestStatusLine.java
@@ -40,10 +40,7 @@ public class HttpRequestStatusLine {
 
   private static final Set<String> HTTP_BATCH_METHODS = new HashSet<String>(Arrays.asList(new String[] { "GET" }));
   private static final Set<String> HTTP_CHANGE_SET_METHODS = new HashSet<String>(Arrays.asList(new String[] { "POST",
-      "PUT", "DELETE", "MERGE", "PATCH" }));
-  // TODO Is Merge still supported?
-  // What`s New in OData 4: 2.7.2 Pruned: MERGE
-  // MERGE was used to do PATCH before PATCH existed. Now that we have PATCH, we no longer need MERGE. => No
+      "PUT", "DELETE", "PATCH" }));
   private static final String HTTP_VERSION = "HTTP/1.1";
 
   final private Line statusLine;


[22/22] olingo-odata4 git commit: [OLINGO-472] Batch Refactoring

Posted by ch...@apache.org.
[OLINGO-472] Batch Refactoring


Project: http://git-wip-us.apache.org/repos/asf/olingo-odata4/repo
Commit: http://git-wip-us.apache.org/repos/asf/olingo-odata4/commit/51acf8ae
Tree: http://git-wip-us.apache.org/repos/asf/olingo-odata4/tree/51acf8ae
Diff: http://git-wip-us.apache.org/repos/asf/olingo-odata4/diff/51acf8ae

Branch: refs/heads/master
Commit: 51acf8aebf4ccf0e982b31734c75460d4bdf106f
Parents: 4f820fe
Author: Christian Amend <ch...@apache.org>
Authored: Tue Nov 18 15:45:37 2014 +0100
Committer: Christian Amend <ch...@apache.org>
Committed: Tue Nov 18 15:45:37 2014 +0100

----------------------------------------------------------------------
 .../org/apache/olingo/server/api/OData.java     |   13 +-
 .../olingo/server/api/batch/BatchFacade.java    |   29 +
 .../olingo/server/api/batch/BatchOperation.java |   35 -
 .../server/api/batch/BatchParserResult.java     |   23 -
 .../server/api/batch/BatchRequestPart.java      |   42 -
 .../server/api/batch/ODataResponsePart.java     |   80 --
 .../deserializer/FixedFormatDeserializer.java   |   29 +
 .../batch/BatchDeserializerResult.java          |   23 +
 .../deserializer/batch/BatchRequestPart.java    |   42 +
 .../deserializer/batch/ODataResponsePart.java   |   80 ++
 .../server/api/processor/BatchProcessor.java    |   15 +-
 .../server/api/processor/DefaultProcessor.java  |   92 +-
 .../api/serializer/FixedFormatSerializer.java   |   16 +-
 .../apache/olingo/server/core/ODataHandler.java |    2 +-
 .../apache/olingo/server/core/ODataImpl.java    |   13 +-
 .../batch/handler/BatchChangeSetSorter.java     |  156 ---
 .../server/core/batch/handler/BatchHandler.java |   68 -
 .../core/batch/handler/BatchOperationImpl.java  |   73 -
 .../core/batch/handler/BatchPartHandler.java    |  150 --
 .../server/core/batch/parser/BatchBodyPart.java |  135 --
 .../core/batch/parser/BatchChangeSetPart.java   |   56 -
 .../server/core/batch/parser/BatchParser.java   |   88 --
 .../core/batch/parser/BatchParserCommon.java    |  221 ---
 .../server/core/batch/parser/BatchPart.java     |   25 -
 .../core/batch/parser/BatchQueryOperation.java  |   82 --
 .../core/batch/parser/BatchRequestPartImpl.java |   51 -
 .../BufferedReaderIncludingLineEndings.java     |  286 ----
 .../olingo/server/core/batch/parser/Header.java |  181 ---
 .../server/core/batch/parser/HeaderField.java   |  121 --
 .../BatchRequestTransformator.java              |  192 ---
 .../batch/transformator/BatchTransformator.java |   29 -
 .../transformator/BatchTransformatorCommon.java |   91 --
 .../transformator/HttpRequestStatusLine.java    |  226 ---
 .../core/batch/writer/BatchResponseWriter.java  |  228 ---
 .../core/batchhandler/BatchChangeSetSorter.java |  156 +++
 .../server/core/batchhandler/BatchHandler.java  |   69 +
 .../core/batchhandler/BatchOperationImpl.java   |   47 +
 .../core/batchhandler/BatchPartHandler.java     |  150 ++
 .../FixedFormatDeserializerImpl.java            |   43 +
 .../core/deserializer/batch/BatchBodyPart.java  |  135 ++
 .../deserializer/batch/BatchChangeSetPart.java  |   56 +
 .../core/deserializer/batch/BatchParser.java    |   88 ++
 .../deserializer/batch/BatchParserCommon.java   |  221 +++
 .../core/deserializer/batch/BatchPart.java      |   25 +
 .../deserializer/batch/BatchQueryOperation.java |   82 ++
 .../batch/BatchRequestPartImpl.java             |   51 +
 .../batch/BatchRequestTransformator.java        |  184 +++
 .../deserializer/batch/BatchTransformator.java  |   28 +
 .../batch/BatchTransformatorCommon.java         |   88 ++
 .../BufferedReaderIncludingLineEndings.java     |  286 ++++
 .../server/core/deserializer/batch/Header.java  |  181 +++
 .../core/deserializer/batch/HeaderField.java    |  121 ++
 .../batch/HttpRequestStatusLine.java            |  225 +++
 .../serializer/BatchResponseSerializer.java     |  228 +++
 .../serializer/FixedFormatSerializerImpl.java   |   13 +
 .../core/batch/BatchRequestParserTest.java      | 1326 ------------------
 .../olingo/server/core/batch/StringUtil.java    |   54 -
 .../batch/handler/BatchChangeSetSorterTest.java |  164 ---
 .../batch/handler/MockedBatchHandlerTest.java   |  664 ---------
 .../batch/parser/BatchParserCommonTest.java     |  230 ---
 .../BufferedReaderIncludingLineEndingsTest.java |  484 -------
 .../server/core/batch/parser/HeaderTest.java    |  179 ---
 .../batch/writer/BatchResponseWriterTest.java   |  179 ---
 .../batchhandler/BatchChangeSetSorterTest.java  |  165 +++
 .../batchhandler/MockedBatchHandlerTest.java    |  671 +++++++++
 .../deserializer/BatchParserCommonTest.java     |  230 +++
 .../deserializer/BatchRequestParserTest.java    | 1326 ++++++++++++++++++
 .../BufferedReaderIncludingLineEndingsTest.java |  484 +++++++
 .../server/core/deserializer/HeaderTest.java    |  179 +++
 .../server/core/deserializer/StringUtil.java    |   54 +
 .../serializer/BatchResponseWriterTest.java     |  180 +++
 .../olingo/server/tecsvc/TechnicalServlet.java  |   24 +-
 .../processor/TechnicalBatchProcessor.java      |  123 ++
 73 files changed, 6149 insertions(+), 6037 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-api/src/main/java/org/apache/olingo/server/api/OData.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/OData.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/OData.java
index 2a6eb1b..0a6f369 100644
--- a/lib/server-api/src/main/java/org/apache/olingo/server/api/OData.java
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/OData.java
@@ -18,16 +18,17 @@
  */
 package org.apache.olingo.server.api;
 
+import java.util.List;
+
 import org.apache.olingo.commons.api.ODataRuntimeException;
-import org.apache.olingo.server.api.edmx.EdmxReference;
 import org.apache.olingo.commons.api.format.ODataFormat;
+import org.apache.olingo.server.api.deserializer.FixedFormatDeserializer;
 import org.apache.olingo.server.api.edm.provider.EdmProvider;
+import org.apache.olingo.server.api.edmx.EdmxReference;
 import org.apache.olingo.server.api.serializer.FixedFormatSerializer;
 import org.apache.olingo.server.api.serializer.ODataSerializer;
 import org.apache.olingo.server.api.serializer.SerializerException;
 
-import java.util.List;
-
 /**
  * Root object for serving factory tasks and support loose coupling of implementation (core) from the API.
  * This is not a singleton (static variables) to avoid issues with synchronization, OSGi, hot deployment and so on.
@@ -69,6 +70,12 @@ public abstract class OData {
   public abstract FixedFormatSerializer createFixedFormatSerializer() throws SerializerException;
 
   /**
+   * Creates a new deserializer object for reading content in a fixed format, e.g., for binary input.
+   * Deserializers are used in Processor implementations.
+   */
+  public abstract FixedFormatDeserializer createFixedFormatDeserializer();
+  
+  /**
    * Creates a new ODataHttpHandler for handling OData requests in an HTTP context.
    *
    * @param serviceMetadata - metadata object required to handle an OData request

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchFacade.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchFacade.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchFacade.java
new file mode 100644
index 0000000..7e85136
--- /dev/null
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchFacade.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */package org.apache.olingo.server.api.batch;
+
+import org.apache.olingo.server.api.ODataRequest;
+import org.apache.olingo.server.api.ODataResponse;
+import org.apache.olingo.server.api.deserializer.batch.BatchRequestPart;
+import org.apache.olingo.server.api.deserializer.batch.ODataResponsePart;
+
+public interface BatchFacade {
+  public ODataResponse handleODataRequest(ODataRequest request, BatchRequestPart requestPart) throws BatchException;
+
+  public ODataResponsePart handleBatchRequest(BatchRequestPart request) throws BatchException;
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchOperation.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchOperation.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchOperation.java
deleted file mode 100644
index a9edef0..0000000
--- a/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchOperation.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */package org.apache.olingo.server.api.batch;
-
-import java.io.IOException;
-import java.util.List;
-
-import org.apache.olingo.server.api.ODataRequest;
-import org.apache.olingo.server.api.ODataResponse;
-
-public interface BatchOperation {
-  public List<BatchRequestPart> parseBatchRequest(ODataRequest request, boolean isStrict) throws BatchException;
-
-  public ODataResponse handleODataRequest(ODataRequest request, BatchRequestPart requestPart) throws BatchException;
-
-  public ODataResponsePart handleBatchRequest(BatchRequestPart request) throws BatchException;
-
-  public void writeResponseParts(List<ODataResponsePart> batchResponses, ODataResponse response) throws BatchException,
-      IOException;
-}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchParserResult.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchParserResult.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchParserResult.java
deleted file mode 100644
index 93bc34d..0000000
--- a/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchParserResult.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.olingo.server.api.batch;
-
-public interface BatchParserResult {
-
-}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchRequestPart.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchRequestPart.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchRequestPart.java
deleted file mode 100644
index ba5319f..0000000
--- a/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/BatchRequestPart.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.olingo.server.api.batch;
-
-import java.util.List;
-
-import org.apache.olingo.server.api.ODataRequest;
-
-/**
- * A BatchPart
- * <p> BatchPart represents a distinct MIME part of a Batch Request body. It can be ChangeSet or Query Operation
- */
-public interface BatchRequestPart extends BatchParserResult {
-
-  /**
-   * Get the info if a BatchPart is a ChangeSet
-   * @return true or false
-   */
-  public boolean isChangeSet();
-
-  /**
-   * Get requests. If a BatchPart is a Query Operation, the list contains one request.
-   * @return a list of {@link ODataRequest}
-   */
-  public List<ODataRequest> getRequests();
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/ODataResponsePart.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/ODataResponsePart.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/ODataResponsePart.java
deleted file mode 100644
index c9a914a..0000000
--- a/lib/server-api/src/main/java/org/apache/olingo/server/api/batch/ODataResponsePart.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.olingo.server.api.batch;
-
-import java.util.Arrays;
-import java.util.List;
-
-import org.apache.olingo.server.api.ODataResponse;
-
-public class ODataResponsePart {
-  private List<ODataResponse> responses;
-  private boolean isChangeSet;
-  
-  /**
-   * Creates a new ODataResponsePart.
-   * 
-   * An ODataResponsePart represents a collections of ODataResponses.
-   * A list of ODataResponseParts can be combined by the BatchSerializer to a single
-   * OData batch response.
-   *  
-   * @param responses     A list of {@link ODataResponse}
-   * @param isChangeSet   True this ODataResponsePart represents a change set, otherwise false
-   */
-  public ODataResponsePart(List<ODataResponse> responses, boolean isChangeSet) {
-    this.responses = responses;
-    this.isChangeSet = isChangeSet;
-  }
-  
-  /**
-   * Creates a new ODataResponsePart.
-   * 
-   * An ODataResponsePart represents a collections of ODataResponses.
-   * A list of ODataResponseParts can be combined by the BatchSerializer to a single
-   * OData batch response.
-   *  
-   * @param responses     A single {@link ODataResponse}
-   * @param isChangeSet   True this ODataResponsePart represents a change set, otherwise false
-   */
-  public ODataResponsePart(ODataResponse response, boolean isChangeSet) {
-    this.responses = Arrays.asList(new ODataResponse[] { response });
-    this.isChangeSet = isChangeSet;
-  }
-  
-  /**
-   * Returns true if the current instance represents a change set.
-   * 
-   * @return true or false
-   */
-  public List<ODataResponse> getResponses() {
-    return responses;
-  }
-  
-  /**
-   * Returns a collection of ODataResponses.
-   * Each collections contains at least one {@link ODataResponse}.
-   * 
-   * If this instance represents a change set, there are may many ODataResponses
-   *  
-   * @return a list of {@link ODataResponse}
-   */
-  public boolean isChangeSet() {
-    return isChangeSet;
-  }
-}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-api/src/main/java/org/apache/olingo/server/api/deserializer/FixedFormatDeserializer.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/deserializer/FixedFormatDeserializer.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/deserializer/FixedFormatDeserializer.java
new file mode 100644
index 0000000..fc8e2d9
--- /dev/null
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/deserializer/FixedFormatDeserializer.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.api.deserializer;
+
+import java.util.List;
+
+import org.apache.olingo.server.api.ODataRequest;
+import org.apache.olingo.server.api.batch.BatchException;
+import org.apache.olingo.server.api.deserializer.batch.BatchRequestPart;
+
+public interface FixedFormatDeserializer {
+  public List<BatchRequestPart> parseBatchRequest(ODataRequest request, boolean isStrict) throws BatchException;
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-api/src/main/java/org/apache/olingo/server/api/deserializer/batch/BatchDeserializerResult.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/deserializer/batch/BatchDeserializerResult.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/deserializer/batch/BatchDeserializerResult.java
new file mode 100644
index 0000000..d397e33
--- /dev/null
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/deserializer/batch/BatchDeserializerResult.java
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.api.deserializer.batch;
+
+public interface BatchDeserializerResult {
+
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-api/src/main/java/org/apache/olingo/server/api/deserializer/batch/BatchRequestPart.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/deserializer/batch/BatchRequestPart.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/deserializer/batch/BatchRequestPart.java
new file mode 100644
index 0000000..928b8a0
--- /dev/null
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/deserializer/batch/BatchRequestPart.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.api.deserializer.batch;
+
+import java.util.List;
+
+import org.apache.olingo.server.api.ODataRequest;
+
+/**
+ * A BatchPart
+ * <p> BatchPart represents a distinct MIME part of a Batch Request body. It can be ChangeSet or Query Operation
+ */
+public interface BatchRequestPart extends BatchDeserializerResult {
+
+  /**
+   * Get the info if a BatchPart is a ChangeSet
+   * @return true or false
+   */
+  public boolean isChangeSet();
+
+  /**
+   * Get requests. If a BatchPart is a Query Operation, the list contains one request.
+   * @return a list of {@link ODataRequest}
+   */
+  public List<ODataRequest> getRequests();
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-api/src/main/java/org/apache/olingo/server/api/deserializer/batch/ODataResponsePart.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/deserializer/batch/ODataResponsePart.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/deserializer/batch/ODataResponsePart.java
new file mode 100644
index 0000000..9a6ec9c
--- /dev/null
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/deserializer/batch/ODataResponsePart.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.olingo.server.api.deserializer.batch;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.olingo.server.api.ODataResponse;
+
+public class ODataResponsePart {
+  private List<ODataResponse> responses;
+  private boolean isChangeSet;
+  
+  /**
+   * Creates a new ODataResponsePart.
+   * 
+   * An ODataResponsePart represents a collections of ODataResponses.
+   * A list of ODataResponseParts can be combined by the BatchSerializer to a single
+   * OData batch response.
+   *  
+   * @param responses     A list of {@link ODataResponse}
+   * @param isChangeSet   True this ODataResponsePart represents a change set, otherwise false
+   */
+  public ODataResponsePart(List<ODataResponse> responses, boolean isChangeSet) {
+    this.responses = responses;
+    this.isChangeSet = isChangeSet;
+  }
+  
+  /**
+   * Creates a new ODataResponsePart.
+   * 
+   * An ODataResponsePart represents a collections of ODataResponses.
+   * A list of ODataResponseParts can be combined by the BatchSerializer to a single
+   * OData batch response.
+   *  
+   * @param responses     A single {@link ODataResponse}
+   * @param isChangeSet   True this ODataResponsePart represents a change set, otherwise false
+   */
+  public ODataResponsePart(ODataResponse response, boolean isChangeSet) {
+    this.responses = Arrays.asList(new ODataResponse[] { response });
+    this.isChangeSet = isChangeSet;
+  }
+  
+  /**
+   * Returns true if the current instance represents a change set.
+   * 
+   * @return true or false
+   */
+  public List<ODataResponse> getResponses() {
+    return responses;
+  }
+  
+  /**
+   * Returns a collection of ODataResponses.
+   * Each collections contains at least one {@link ODataResponse}.
+   * 
+   * If this instance represents a change set, there are may many ODataResponses
+   *  
+   * @return a list of {@link ODataResponse}
+   */
+  public boolean isChangeSet() {
+    return isChangeSet;
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/BatchProcessor.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/BatchProcessor.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/BatchProcessor.java
index 843fa28..5faece4 100644
--- a/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/BatchProcessor.java
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/BatchProcessor.java
@@ -22,13 +22,16 @@ import java.util.List;
 
 import org.apache.olingo.server.api.ODataRequest;
 import org.apache.olingo.server.api.ODataResponse;
-import org.apache.olingo.server.api.batch.BatchOperation;
-import org.apache.olingo.server.api.batch.BatchRequestPart;
-import org.apache.olingo.server.api.batch.ODataResponsePart;
+import org.apache.olingo.server.api.batch.BatchException;
+import org.apache.olingo.server.api.batch.BatchFacade;
+import org.apache.olingo.server.api.deserializer.batch.BatchRequestPart;
+import org.apache.olingo.server.api.deserializer.batch.ODataResponsePart;
+import org.apache.olingo.server.api.serializer.SerializerException;
 
 public interface BatchProcessor extends Processor {
-  void executeBatch(BatchOperation operation, ODataRequest request, ODataResponse response);
+  // TODO:Check exception signature
+  void executeBatch(BatchFacade facade, ODataRequest request, ODataResponse response)
+      throws SerializerException, BatchException;
 
-  ODataResponsePart executeChangeSet(BatchOperation operation, List<ODataRequest> requests,
-      BatchRequestPart requestPart);
+  ODataResponsePart executeChangeSet(BatchFacade facade, List<ODataRequest> requests, BatchRequestPart requestPart);
 }

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/DefaultProcessor.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/DefaultProcessor.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/DefaultProcessor.java
index c4493a1..0bc9ff1 100644
--- a/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/DefaultProcessor.java
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/DefaultProcessor.java
@@ -19,11 +19,7 @@
 package org.apache.olingo.server.api.processor;
 
 import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
 
-import org.apache.olingo.commons.api.ODataRuntimeException;
 import org.apache.olingo.commons.api.format.ContentType;
 import org.apache.olingo.commons.api.format.ODataFormat;
 import org.apache.olingo.commons.api.http.HttpHeader;
@@ -34,10 +30,6 @@ import org.apache.olingo.server.api.ODataRequest;
 import org.apache.olingo.server.api.ODataResponse;
 import org.apache.olingo.server.api.ODataServerError;
 import org.apache.olingo.server.api.ServiceMetadata;
-import org.apache.olingo.server.api.batch.BatchException;
-import org.apache.olingo.server.api.batch.BatchOperation;
-import org.apache.olingo.server.api.batch.BatchRequestPart;
-import org.apache.olingo.server.api.batch.ODataResponsePart;
 import org.apache.olingo.server.api.serializer.ODataSerializer;
 import org.apache.olingo.server.api.serializer.SerializerException;
 import org.apache.olingo.server.api.uri.UriInfo;
@@ -50,12 +42,7 @@ import org.apache.olingo.server.api.uri.UriInfo;
  * <p>This implementation is registered in the ODataHandler by default.
  * The default can be replaced by re-registering a custom implementation.</p>
  */
-public class DefaultProcessor implements MetadataProcessor, ServiceDocumentProcessor, ExceptionProcessor,
-    BatchProcessor {
-  
-  private static final String PREFERENCE_CONTINUE_ON_ERROR = "odata.continue-on-error";
-  private static final String PREFER_HEADER = "Prefer";
-  
+public class DefaultProcessor implements MetadataProcessor, ServiceDocumentProcessor, ExceptionProcessor  {
   private OData odata;
   private ServiceMetadata serviceMetadata;
 
@@ -101,81 +88,4 @@ public class DefaultProcessor implements MetadataProcessor, ServiceDocumentProce
       response.setHeader(HttpHeader.CONTENT_TYPE, ContentType.APPLICATION_JSON.toContentTypeString());
     }
   }
-
-  @Override
-  public void executeBatch(BatchOperation operation, ODataRequest request, ODataResponse response) {
-    boolean continueOnError = shouldContinueOnError(request);
-
-    try {
-      final List<BatchRequestPart> parts = operation.parseBatchRequest(request, true);
-      final List<ODataResponsePart> responseParts = new ArrayList<ODataResponsePart>();
-
-      for (BatchRequestPart part : parts) {
-        final ODataResponsePart responsePart = operation.handleBatchRequest(part);
-        responseParts.add(responsePart);    // Also add failed responses
-
-        if (responsePart.getResponses().get(0).getStatusCode() >= 400
-            && !continueOnError) {
-
-          // Perform some additions actions
-          // ...
-          
-          break; // Stop processing, but serialize all recent requests
-        }
-      }
-
-      operation.writeResponseParts(responseParts, response);  // Serialize responses
-    } catch (BatchException e) {
-      throw new ODataRuntimeException(e);
-    } catch (IOException e) {
-      throw new ODataRuntimeException(e);
-    }
-  }
-
-  private boolean shouldContinueOnError(ODataRequest request) {
-    final List<String> preferValues = request.getHeaders(PREFER_HEADER);
-
-    if (preferValues != null) {
-      for (final String preference : preferValues) {
-        if (PREFERENCE_CONTINUE_ON_ERROR.equals(preference)) {
-          return true;
-        }
-      }
-    }
-    return false;
-  }
-
-  @Override
-  public ODataResponsePart executeChangeSet(BatchOperation operation, List<ODataRequest> requests,
-      BatchRequestPart requestPart) {
-    List<ODataResponse> responses = new ArrayList<ODataResponse>();
-
-    for (ODataRequest request : requests) {
-      try {
-        final ODataResponse oDataResponse = operation.handleODataRequest(request, requestPart);
-
-        if (oDataResponse.getStatusCode() < 400) {
-          responses.add(oDataResponse);
-        } else {
-          // Rollback
-          // ...
-
-          // OData Version 4.0 Part 1: Protocol Plus Errata 01
-          // 11.7.4 Responding to a Batch Request
-          //
-          // When a request within a change set fails, the change set response is not represented using
-          // the multipart/mixed media type. Instead, a single response, using the application/http media type
-          // and a Content-Transfer-Encoding header with a value of binary, is returned that applies to all requests
-          // in the change set and MUST be formatted according to the Error Handling defined
-          // for the particular response format.
-
-          return new ODataResponsePart(oDataResponse, false);
-        }
-      } catch (BatchException e) {
-        throw new ODataRuntimeException(e);
-      }
-    }
-
-    return new ODataResponsePart(responses, true);
-  }
 }

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/FixedFormatSerializer.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/FixedFormatSerializer.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/FixedFormatSerializer.java
index 22416b3..b711651 100644
--- a/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/FixedFormatSerializer.java
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/serializer/FixedFormatSerializer.java
@@ -18,16 +18,21 @@
  */
 package org.apache.olingo.server.api.serializer;
 
+import java.io.IOException;
 import java.io.InputStream;
+import java.util.List;
 
 import org.apache.olingo.commons.api.edm.EdmPrimitiveType;
+import org.apache.olingo.server.api.ODataResponse;
+import org.apache.olingo.server.api.batch.BatchException;
+import org.apache.olingo.server.api.deserializer.batch.ODataResponsePart;
 
 /** OData serializer for fixed output formats. */
 public interface FixedFormatSerializer {
 
   /**
    * Writes binary output into an InputStream.
-   * @param binary the binary data 
+   * @param binary the binary data
    */
   InputStream binary(byte[] binary) throws SerializerException;
 
@@ -39,10 +44,15 @@ public interface FixedFormatSerializer {
 
   /**
    * Writes the raw value of a primitive-type instance into an InputStream.
-   * @param type    the primitive type
-   * @param value   the value
+   * @param type the primitive type
+   * @param value the value
    * @param options options for the serializer
    */
   InputStream primitiveValue(EdmPrimitiveType type, Object value, PrimitiveValueSerializerOptions options)
       throws SerializerException;
+
+  // TODO: Document
+  // TODO: Delete IOException
+  void writeResponseParts(List<ODataResponsePart> batchResponses, ODataResponse response) throws BatchException,
+      IOException;
 }

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataHandler.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataHandler.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataHandler.java
index 9be403c..4467778 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataHandler.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataHandler.java
@@ -58,7 +58,7 @@ import org.apache.olingo.server.api.uri.UriResourceEntitySet;
 import org.apache.olingo.server.api.uri.UriResourceNavigation;
 import org.apache.olingo.server.api.uri.UriResourcePartTyped;
 import org.apache.olingo.server.api.uri.UriResourceProperty;
-import org.apache.olingo.server.core.batch.handler.BatchHandler;
+import org.apache.olingo.server.core.batchhandler.BatchHandler;
 import org.apache.olingo.server.core.uri.parser.Parser;
 import org.apache.olingo.server.core.uri.parser.UriParserException;
 import org.apache.olingo.server.core.uri.parser.UriParserSemanticException;

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataImpl.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataImpl.java
index 12e3b14..a13281e 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataImpl.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataImpl.java
@@ -18,22 +18,24 @@
  */
 package org.apache.olingo.server.core;
 
+import java.util.List;
+
 import org.apache.olingo.commons.api.edm.constants.ODataServiceVersion;
-import org.apache.olingo.server.api.edmx.EdmxReference;
 import org.apache.olingo.commons.api.format.ODataFormat;
 import org.apache.olingo.server.api.OData;
 import org.apache.olingo.server.api.ODataHttpHandler;
 import org.apache.olingo.server.api.ServiceMetadata;
+import org.apache.olingo.server.api.deserializer.FixedFormatDeserializer;
 import org.apache.olingo.server.api.edm.provider.EdmProvider;
+import org.apache.olingo.server.api.edmx.EdmxReference;
 import org.apache.olingo.server.api.serializer.FixedFormatSerializer;
 import org.apache.olingo.server.api.serializer.ODataSerializer;
 import org.apache.olingo.server.api.serializer.SerializerException;
+import org.apache.olingo.server.core.deserializer.FixedFormatDeserializerImpl;
 import org.apache.olingo.server.core.serializer.FixedFormatSerializerImpl;
 import org.apache.olingo.server.core.serializer.json.ODataJsonSerializer;
 import org.apache.olingo.server.core.serializer.xml.ODataXmlSerializerImpl;
 
-import java.util.List;
-
 public class ODataImpl extends OData {
 
   @Override
@@ -70,4 +72,9 @@ public class ODataImpl extends OData {
   public ServiceMetadata createServiceMetadata(EdmProvider edmProvider, List<EdmxReference> references) {
     return new ServiceMetadataImpl(ODataServiceVersion.V40, edmProvider, references);
   }
+
+  @Override
+  public FixedFormatDeserializer createFixedFormatDeserializer() {
+    return new FixedFormatDeserializerImpl();
+  }
 }

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchChangeSetSorter.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchChangeSetSorter.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchChangeSetSorter.java
deleted file mode 100644
index 5e1c276..0000000
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchChangeSetSorter.java
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.olingo.server.core.batch.handler;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import org.apache.olingo.server.api.ODataRequest;
-import org.apache.olingo.server.api.batch.BatchException;
-import org.apache.olingo.server.api.batch.BatchException.MessageKeys;
-import org.apache.olingo.server.core.batch.parser.BatchParserCommon;
-
-public class BatchChangeSetSorter {
-  private static final String REG_EX_REFERENCE = "\\$(.*)(/.*)?";
-
-  final List<ODataRequest> orderdList = new ArrayList<ODataRequest>();
-
-  private static Pattern referencePattern = Pattern.compile(REG_EX_REFERENCE);
-  private Set<String> knownContentIds = new HashSet<String>();
-  private Map<String, List<ODataRequest>> requestReferenceMapping = new HashMap<String, List<ODataRequest>>();
-
-  public BatchChangeSetSorter(List<ODataRequest> requests) throws BatchException {
-    sort(requests);
-  }
-
-  public List<ODataRequest> getOrderdRequests() {
-    return orderdList;
-  }
-
-  private List<ODataRequest> sort(final List<ODataRequest> requests) throws BatchException {
-    extractUrlReference(requests);
-    
-    // Add requests without reference (roots)
-    final List<ODataRequest> requestsWithoutReferences = getRequestsWithoutReferences();
-    orderdList.addAll(requestsWithoutReferences);
-    addRequestsToKnownContentIds(requestsWithoutReferences);
-    
-    // Find all requests which can be processed (parent request has been processed)
-    // Compare level order
-    boolean areRequestsProcessed = true;
-    while (requestsToProcessAvailable() && areRequestsProcessed) {
-      areRequestsProcessed = processNextLevel();
-    }
-
-    // Check if there are some requests which are not connected to a tree
-    if (requestsToProcessAvailable()) {
-      throw new BatchException("Invalid content id", MessageKeys.INVALID_CONTENT_ID, 0);
-    }
-
-    return orderdList;
-  }
-
-  private boolean requestsToProcessAvailable() {
-    return requestReferenceMapping.keySet().size() != 0;
-  }
-
-  private boolean processNextLevel() {
-    final List<ODataRequest> addedRequests = getRemainingRequestsWithKownContentId();
-    addRequestsToKnownContentIds(addedRequests);
-    orderdList.addAll(addedRequests);
-
-    return addedRequests.size() != 0;
-  }
-
-  private List<ODataRequest> getRemainingRequestsWithKownContentId() {
-    List<ODataRequest> result = new ArrayList<ODataRequest>();
-
-    for (String contextId : knownContentIds) {
-      final List<ODataRequest> requestsToProcess = requestReferenceMapping.get(contextId);
-      if (requestsToProcess != null && requestsToProcess.size() != 0) {
-        result.addAll(requestsToProcess);
-        requestReferenceMapping.remove(contextId);
-      }
-    }
-
-    return result;
-  }
-
-  private List<ODataRequest> getRequestsWithoutReferences() {
-    List<ODataRequest> requests = requestReferenceMapping.get(null);
-    requestReferenceMapping.remove(null);
-    
-    requests = (requests == null) ? new ArrayList<ODataRequest>() : requests;
-    return requests;
-  }
-
-  private void addRequestsToKnownContentIds(List<ODataRequest> requestsWithoutReference) {
-    for (ODataRequest request : requestsWithoutReference) {
-      final String contentId = getContentIdFromHeader(request);
-      if (contentId != null) {
-        knownContentIds.add(contentId);
-      }
-    }
-  }
-
-  private String getContentIdFromHeader(ODataRequest request) {
-    return request.getHeader(BatchParserCommon.HTTP_CONTENT_ID);
-  }
-
-  private void extractUrlReference(List<ODataRequest> requests) {
-    for (ODataRequest request : requests) {
-      final String reference = getReferenceInURI(request);
-      addRequestToReferenceMapping(reference, request);
-    }
-  }
-
-  private void addRequestToReferenceMapping(final String reference, final ODataRequest request) {
-    List<ODataRequest> requestList = requestReferenceMapping.get(reference);
-    requestList = (requestList == null) ? new ArrayList<ODataRequest>() : requestList;
-
-    requestList.add(request);
-    requestReferenceMapping.put(reference, requestList);
-  }
-
-  public static String getReferenceInURI(ODataRequest request) {
-    Matcher matcher = referencePattern.matcher(removeSlash(removeSlash(request.getRawODataPath(), true), false));
-    return (matcher.matches()) ? matcher.group(1) : null;
-  }
-
-  private static String removeSlash(String rawODataPath, boolean first) {
-    final int indexOfSlash = rawODataPath.indexOf("/");
-    if(first) {
-      return (indexOfSlash == 0) ? rawODataPath.substring(1) : rawODataPath;
-    } else {
-      return (indexOfSlash != -1) ? rawODataPath.substring(0, indexOfSlash) : rawODataPath;
-    }
-  }
-
-  public static void replaceContentIdReference(ODataRequest request, String contentId, String resourceUri) {
-    final String newUri = request.getRawODataPath().replace("/$" + contentId, resourceUri);
-    request.setRawODataPath(newUri);
-    request.setRawRequestUri(request.getRawBaseUri() + "/" + newUri);
-  }
-}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchHandler.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchHandler.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchHandler.java
deleted file mode 100644
index df16c90..0000000
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchHandler.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.olingo.server.core.batch.handler;
-
-import org.apache.olingo.commons.api.http.HttpHeader;
-import org.apache.olingo.commons.api.http.HttpMethod;
-import org.apache.olingo.server.api.ODataRequest;
-import org.apache.olingo.server.api.ODataResponse;
-import org.apache.olingo.server.api.batch.BatchException;
-import org.apache.olingo.server.api.batch.BatchOperation;
-import org.apache.olingo.server.api.batch.BatchException.MessageKeys;
-import org.apache.olingo.server.api.processor.BatchProcessor;
-import org.apache.olingo.server.core.ODataHandler;
-import org.apache.olingo.server.core.batch.parser.BatchParserCommon;
-
-public class BatchHandler {
-  private final BatchProcessor batchProcessor;
-  private final ODataHandler oDataHandler;
-
-  public BatchHandler(final ODataHandler oDataHandler, final BatchProcessor batchProcessor) {
-
-    this.batchProcessor = batchProcessor;
-    this.oDataHandler = oDataHandler;
-  }
-
-  public void process(final ODataRequest request, final ODataResponse response, final boolean isStrict)
-      throws BatchException {
-    validateRequest(request);
-    
-    final BatchOperation operation = new BatchOperationImpl(oDataHandler, request, batchProcessor, isStrict);
-    batchProcessor.executeBatch(operation, request, response);
-  }
-
-  private void validateRequest(final ODataRequest request) throws BatchException {
-    validateHttpMethod(request);
-    validateContentType(request);
-  }
-
-  private void validateContentType(final ODataRequest request) throws BatchException {
-    final String contentType = request.getHeader(HttpHeader.CONTENT_TYPE);
-
-    if (contentType == null || !BatchParserCommon.PATTERN_MULTIPART_BOUNDARY.matcher(contentType).matches()) {
-      throw new BatchException("Invalid content type", MessageKeys.INVALID_CONTENT_TYPE, 0);
-    }
-  }
-
-  private void validateHttpMethod(final ODataRequest request) throws BatchException {
-    if (request.getMethod() != HttpMethod.POST) {
-      throw new BatchException("Invalid HTTP method", MessageKeys.INVALID_METHOD, 0);
-    }
-  }
-}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchOperationImpl.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchOperationImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchOperationImpl.java
deleted file mode 100644
index b5a4eb4..0000000
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchOperationImpl.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.olingo.server.core.batch.handler;
-
-import java.io.IOException;
-import java.util.List;
-
-import org.apache.olingo.commons.api.http.HttpHeader;
-import org.apache.olingo.server.api.ODataRequest;
-import org.apache.olingo.server.api.ODataResponse;
-import org.apache.olingo.server.api.batch.BatchException;
-import org.apache.olingo.server.api.batch.BatchOperation;
-import org.apache.olingo.server.api.batch.BatchRequestPart;
-import org.apache.olingo.server.api.batch.ODataResponsePart;
-import org.apache.olingo.server.api.processor.BatchProcessor;
-import org.apache.olingo.server.core.ODataHandler;
-import org.apache.olingo.server.core.batch.parser.BatchParser;
-import org.apache.olingo.server.core.batch.writer.BatchResponseWriter;
-
-public class BatchOperationImpl implements BatchOperation {
-  private final BatchPartHandler partHandler;
-  private final BatchResponseWriter writer;
-  private final BatchParser parser;
-
-  public BatchOperationImpl(ODataHandler oDataHandler, ODataRequest request, BatchProcessor batchProcessor,
-      final boolean isStrict) {
-    partHandler = new BatchPartHandler(oDataHandler, batchProcessor, this);
-    writer = new BatchResponseWriter();
-    parser = new BatchParser();
-  }
-
-  @Override
-  public List<BatchRequestPart> parseBatchRequest(ODataRequest request, boolean isStrict) throws BatchException {
-    return parser.parseBatchRequest(request.getBody(), getContentType(request), request.getRawBaseUri(),
-        request.getRawServiceResolutionUri(), isStrict);
-  }
-  
-  @Override
-  public ODataResponse handleODataRequest(ODataRequest request, BatchRequestPart requestPart) throws BatchException {
-    return partHandler.handleODataRequest(request, requestPart);
-  }
-  
-  @Override
-  public ODataResponsePart handleBatchRequest(BatchRequestPart request) throws BatchException {
-    return partHandler.handleBatchRequest(request);
-  }
-
-  @Override
-  public void writeResponseParts(List<ODataResponsePart> batchResponses, ODataResponse response) throws BatchException,
-      IOException {
-    writer.toODataResponse(batchResponses, response);
-  }
-
-  private String getContentType(ODataRequest request) {
-    return request.getHeader(HttpHeader.CONTENT_TYPE);
-  }
-}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchPartHandler.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchPartHandler.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchPartHandler.java
deleted file mode 100644
index 2147a2e..0000000
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/handler/BatchPartHandler.java
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.olingo.server.core.batch.handler;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import org.apache.olingo.commons.api.ODataRuntimeException;
-import org.apache.olingo.commons.api.http.HttpHeader;
-import org.apache.olingo.commons.api.http.HttpMethod;
-import org.apache.olingo.server.api.ODataRequest;
-import org.apache.olingo.server.api.ODataResponse;
-import org.apache.olingo.server.api.batch.BatchException;
-import org.apache.olingo.server.api.batch.BatchOperation;
-import org.apache.olingo.server.api.batch.BatchRequestPart;
-import org.apache.olingo.server.api.batch.ODataResponsePart;
-import org.apache.olingo.server.api.processor.BatchProcessor;
-import org.apache.olingo.server.core.ODataHandler;
-import org.apache.olingo.server.core.batch.parser.BatchParserCommon;
-import org.apache.olingo.server.core.batch.transformator.HttpRequestStatusLine.ODataURI;
-
-public class BatchPartHandler {
-
-  private ODataHandler oDataHandler;
-  private BatchProcessor batchProcessor;
-  private BatchOperation batchOperation;
-  private Map<BatchRequestPart, UriMapping> uriMapping = new HashMap<BatchRequestPart, UriMapping>();
-
-  public BatchPartHandler(final ODataHandler oDataHandler, final BatchProcessor processor,
-      final BatchOperation batchOperation) {
-    this.oDataHandler = oDataHandler;
-    this.batchProcessor = processor;
-    this.batchOperation = batchOperation;
-  }
-
-  public ODataResponse handleODataRequest(ODataRequest request, BatchRequestPart requestPart) throws BatchException {
-    final ODataResponse response;
-
-    if (requestPart.isChangeSet()) {
-      final UriMapping mapping = replaceReference(request, requestPart);
-
-      response = oDataHandler.process(request);
-
-      // Store resource URI
-      final String resourceUri = getODataPath(request, response);
-      final String contentId = request.getHeader(BatchParserCommon.HTTP_CONTENT_ID);
-
-      mapping.addMapping(contentId, resourceUri);
-    } else {
-      response = oDataHandler.process(request);
-    }
-
-    // Add content id to response
-    final String contentId = request.getHeader(BatchParserCommon.HTTP_CONTENT_ID);
-    if (contentId != null) {
-      response.setHeader(BatchParserCommon.HTTP_CONTENT_ID, contentId);
-    }
-
-    return response;
-  }
-
-  private String getODataPath(ODataRequest request, ODataResponse response) throws BatchException {
-    String resourceUri = null;
-
-    if (request.getMethod() == HttpMethod.POST) {
-      // Create entity
-      // The URI of the new resource will be generated by the server and published in the location header
-      ODataURI uri = new ODataURI(response.getHeaders().get(HttpHeader.LOCATION), request.getRawBaseUri());
-      resourceUri = uri.getRawODataPath();
-    } else {
-      // Update, Upsert (PUT, PATCH, Delete)
-      // These methods still addresses a given resource, so we use the URI given by the request
-      resourceUri = request.getRawODataPath();
-    }
-
-    return resourceUri;
-  }
-
-  private UriMapping replaceReference(ODataRequest request, BatchRequestPart requestPart) {
-    final UriMapping mapping = getUriMappingOrDefault(requestPart);
-    final String reference = BatchChangeSetSorter.getReferenceInURI(request);
-
-    if (reference != null) {
-      final String replacement = mapping.getUri(reference);
-
-      if (replacement != null) {
-        BatchChangeSetSorter.replaceContentIdReference(request, reference, replacement);
-      } else {
-        throw new ODataRuntimeException("Required Content-Id for reference \"" + reference + "\" not found.");
-      }
-    }
-
-    return mapping;
-  }
-
-  private UriMapping getUriMappingOrDefault(final BatchRequestPart requestPart) {
-    UriMapping mapping = uriMapping.get(requestPart);
-
-    if (mapping == null) {
-      mapping = new UriMapping();
-    }
-    uriMapping.put(requestPart, mapping);
-
-    return mapping;
-  }
-
-  public ODataResponsePart handleBatchRequest(BatchRequestPart request) throws BatchException {
-    if (request.isChangeSet()) {
-      return handleChangeSet(request);
-    } else {
-      final ODataResponse response = handleODataRequest(request.getRequests().get(0), request);
-      
-      return new ODataResponsePart(response, false);
-    }
-  }
-
-  private ODataResponsePart handleChangeSet(BatchRequestPart request) throws BatchException {
-    final BatchChangeSetSorter sorter = new BatchChangeSetSorter(request.getRequests());
-
-    return batchProcessor.executeChangeSet(batchOperation, sorter.getOrderdRequests(), request);
-  }
-
-  private static class UriMapping {
-    private Map<String, String> uriMapping = new HashMap<String, String>();
-
-    public void addMapping(final String contentId, final String uri) {
-      uriMapping.put(contentId, uri);
-    }
-
-    public String getUri(final String contentId) {
-      return uriMapping.get(contentId);
-    }
-  }
-}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchBodyPart.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchBodyPart.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchBodyPart.java
deleted file mode 100644
index 2b93783..0000000
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchBodyPart.java
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.olingo.server.core.batch.parser;
-
-import java.util.LinkedList;
-import java.util.List;
-
-import org.apache.olingo.commons.api.http.HttpHeader;
-import org.apache.olingo.server.api.batch.BatchException;
-import org.apache.olingo.server.core.batch.parser.BufferedReaderIncludingLineEndings.Line;
-
-public class BatchBodyPart implements BatchPart {
-  final private String boundary;
-  final private boolean isStrict;
-  final List<Line> remainingMessage = new LinkedList<Line>();
-
-  private Header headers;
-  private boolean isChangeSet;
-  private List<BatchQueryOperation> requests;
-
-  public BatchBodyPart(final List<Line> message, final String boundary, final boolean isStrict) {
-    this.boundary = boundary;
-    this.isStrict = isStrict;
-    remainingMessage.addAll(message);
-  }
-
-  public BatchBodyPart parse() throws BatchException {
-    headers = BatchParserCommon.consumeHeaders(remainingMessage);
-    BatchParserCommon.consumeBlankLine(remainingMessage, isStrict);
-    isChangeSet = isChangeSet(headers);
-    requests = consumeRequest(remainingMessage);
-
-    return this;
-  }
-
-  private boolean isChangeSet(final Header header) throws BatchException {
-    final List<String> contentTypes = headers.getHeaders(HttpHeader.CONTENT_TYPE);
-    boolean isChangeSet = false;
-
-    if (contentTypes.size() == 0) {
-      throw new BatchException("Missing content type", BatchException.MessageKeys.MISSING_CONTENT_TYPE, ""
-          + headers.getLineNumber());
-    }
-
-    for (String contentType : contentTypes) {
-      if (isContentTypeMultiPartMixed(contentType)) {
-        isChangeSet = true;
-      }
-    }
-
-    return isChangeSet;
-  }
-
-  private List<BatchQueryOperation> consumeRequest(final List<Line> remainingMessage) throws BatchException {
-    if (isChangeSet) {
-      return consumeChangeSet(remainingMessage);
-    } else {
-      return consumeQueryOperation(remainingMessage);
-    }
-  }
-
-  private List<BatchQueryOperation> consumeChangeSet(final List<Line> remainingMessage2) throws BatchException {
-    final List<List<Line>> changeRequests = splitChangeSet(remainingMessage);
-    final List<BatchQueryOperation> requestList = new LinkedList<BatchQueryOperation>();
-
-    for (List<Line> changeRequest : changeRequests) {
-      requestList.add(new BatchChangeSetPart(changeRequest, isStrict).parse());
-    }
-
-    return requestList;
-  }
-
-  private List<List<Line>> splitChangeSet(final List<Line> remainingMessage2) throws BatchException {
-
-    final HeaderField contentTypeField = headers.getHeaderField(HttpHeader.CONTENT_TYPE);
-    final String changeSetBoundary = BatchParserCommon.getBoundary(contentTypeField.getValueNotNull(),
-        contentTypeField.getLineNumber());
-    validateChangeSetBoundary(changeSetBoundary, headers);
-
-    return BatchParserCommon.splitMessageByBoundary(remainingMessage, changeSetBoundary);
-  }
-
-  private void validateChangeSetBoundary(final String changeSetBoundary, final Header header) throws BatchException {
-    if (changeSetBoundary.equals(boundary)) {
-      throw new BatchException("Change set boundary is equals to batch request boundary",
-          BatchException.MessageKeys.INVALID_BOUNDARY,
-          "" + header.getHeaderField(HttpHeader.CONTENT_TYPE).getLineNumber());
-    }
-  }
-
-  private List<BatchQueryOperation> consumeQueryOperation(final List<Line> remainingMessage) throws BatchException {
-    final List<BatchQueryOperation> requestList = new LinkedList<BatchQueryOperation>();
-    requestList.add(new BatchQueryOperation(remainingMessage, isStrict).parse());
-
-    return requestList;
-  }
-
-  private boolean isContentTypeMultiPartMixed(final String contentType) {
-    return BatchParserCommon.PATTERN_MULTIPART_BOUNDARY.matcher(contentType).matches();
-  }
-
-  @Override
-  public Header getHeaders() {
-    return headers;
-  }
-
-  @Override
-  public boolean isStrict() {
-    return isStrict;
-  }
-
-  public boolean isChangeSet() {
-    return isChangeSet;
-  }
-
-  public List<BatchQueryOperation> getRequests() {
-    return requests;
-  }
-}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchChangeSetPart.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchChangeSetPart.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchChangeSetPart.java
deleted file mode 100644
index aaa2660..0000000
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchChangeSetPart.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.olingo.server.core.batch.parser;
-
-import java.util.List;
-
-import org.apache.olingo.server.api.batch.BatchException;
-import org.apache.olingo.server.core.batch.parser.BufferedReaderIncludingLineEndings.Line;
-
-public class BatchChangeSetPart extends BatchQueryOperation {
-  private BatchQueryOperation request;
-
-  public BatchChangeSetPart(final List<Line> message, final boolean isStrict) throws BatchException {
-    super(message, isStrict);
-  }
-
-  @Override
-  public BatchChangeSetPart parse() throws BatchException {
-    headers = BatchParserCommon.consumeHeaders(message);
-    BatchParserCommon.consumeBlankLine(message, isStrict);
-
-    request = new BatchQueryOperation(message, isStrict).parse();
-
-    return this;
-  }
-
-  public BatchQueryOperation getRequest() {
-    return request;
-  }
-
-  @Override
-  public List<Line> getBody() {
-    return request.getBody();
-  }
-
-  @Override
-  public Line getHttpStatusLine() {
-    return request.getHttpStatusLine();
-  }
-}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchParser.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchParser.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchParser.java
deleted file mode 100644
index 3a89440..0000000
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchParser.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.olingo.server.core.batch.parser;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.util.LinkedList;
-import java.util.List;
-
-import org.apache.olingo.commons.api.ODataRuntimeException;
-import org.apache.olingo.server.api.batch.BatchException;
-import org.apache.olingo.server.api.batch.BatchParserResult;
-import org.apache.olingo.server.api.batch.BatchRequestPart;
-import org.apache.olingo.server.core.batch.parser.BufferedReaderIncludingLineEndings.Line;
-import org.apache.olingo.server.core.batch.transformator.BatchRequestTransformator;
-
-public class BatchParser {
-
-  private String contentTypeMime;
-  private String rawServiceResolutionUri;
-  private boolean isStrict;
-
-  @SuppressWarnings("unchecked")
-  public List<BatchRequestPart> parseBatchRequest(final InputStream in, final String contentType, final String baseUri,
-      final String serviceResolutionUri, final boolean isStrict) throws BatchException {
-    
-    contentTypeMime = contentType;
-    this.isStrict = isStrict;
-    this.rawServiceResolutionUri = serviceResolutionUri;
-
-    return (List<BatchRequestPart>) parse(in, new BatchRequestTransformator(baseUri, rawServiceResolutionUri));
-  }
-
-  private List<? extends BatchParserResult> parse(final InputStream in, final BatchRequestTransformator transformator)
-      throws BatchException {
-    try {
-      return parseBatch(in, transformator);
-    } catch (IOException e) {
-      throw new ODataRuntimeException(e);
-    } finally {
-      try {
-        in.close();
-      } catch (IOException e) {
-        throw new ODataRuntimeException(e);
-      }
-    }
-  }
-
-  private List<BatchParserResult> parseBatch(final InputStream in, final BatchRequestTransformator transformator)
-      throws IOException, BatchException {
-    final String boundary = BatchParserCommon.getBoundary(contentTypeMime, 1);
-    final List<BatchParserResult> resultList = new LinkedList<BatchParserResult>();
-    final List<List<Line>> bodyPartStrings = splitBodyParts(in, boundary);
-
-    for (List<Line> bodyPartString : bodyPartStrings) {
-      BatchBodyPart bodyPart = new BatchBodyPart(bodyPartString, boundary, isStrict).parse();
-      resultList.addAll(transformator.transform(bodyPart));
-    }
-
-    return resultList;
-  }
-
-  private List<List<Line>> splitBodyParts(final InputStream in, final String boundary) throws IOException,
-      BatchException {
-    final BufferedReaderIncludingLineEndings reader = new BufferedReaderIncludingLineEndings(new InputStreamReader(in));
-    final List<Line> message = reader.toLineList();
-    reader.close();
-
-    return BatchParserCommon.splitMessageByBoundary(message, boundary);
-  }
-}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchParserCommon.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchParserCommon.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchParserCommon.java
deleted file mode 100644
index b87f8ef..0000000
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchParserCommon.java
+++ /dev/null
@@ -1,221 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.olingo.server.core.batch.parser;
-
-import java.io.ByteArrayInputStream;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Locale;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import org.apache.olingo.commons.api.http.HttpContentType;
-import org.apache.olingo.server.api.batch.BatchException;
-import org.apache.olingo.server.core.batch.parser.BufferedReaderIncludingLineEndings.Line;
-
-public class BatchParserCommon {
-
-  private static final String REG_EX_BOUNDARY =
-      "([a-zA-Z0-9_\\-\\.'\\+]{1,70})|\"([a-zA-Z0-9_\\-\\.'\\+\\s\\" +
-          "(\\),/:=\\?]{1,69}[a-zA-Z0-9_\\-\\.'\\+\\(\\),/:=\\?])\"";
-  private static final Pattern PATTERN_LAST_CRLF = Pattern.compile("(.*)(\r\n){1}( *)", Pattern.DOTALL);
-  private static final Pattern PATTERN_HEADER_LINE = Pattern.compile("([a-zA-Z\\-]+):\\s?(.*)\\s*");
-  private static final String REG_EX_APPLICATION_HTTP = "application/http";
-  
-  public static final Pattern PATTERN_MULTIPART_BOUNDARY = Pattern.compile("multipart/mixed(.*)",
-      Pattern.CASE_INSENSITIVE);
-  public static final Pattern PATTERN_CONTENT_TYPE_APPLICATION_HTTP = Pattern.compile(REG_EX_APPLICATION_HTTP,
-      Pattern.CASE_INSENSITIVE);
-  public static final String BINARY_ENCODING = "binary";
-  public static final String HTTP_CONTENT_ID = "Content-Id";
-  public static final String HTTP_CONTENT_TRANSFER_ENCODING = "Content-Transfer-Encoding";
-  
-  public static final String HTTP_EXPECT = "Expect";
-  public static final String HTTP_FROM = "From";
-  public static final String HTTP_MAX_FORWARDS = "Max-Forwards";
-  public static final String HTTP_RANGE = "Range";
-  public static final String HTTP_TE = "TE";
-  
-  public static String getBoundary(final String contentType, final int line) throws BatchException {
-    if (contentType.toLowerCase(Locale.ENGLISH).startsWith("multipart/mixed")) {
-      final String[] parameter = contentType.split(";");
-
-      for (final String pair : parameter) {
-
-        final String[] attrValue = pair.split("=");
-        if (attrValue.length == 2 && "boundary".equals(attrValue[0].trim().toLowerCase(Locale.ENGLISH))) {
-          if (attrValue[1].matches(REG_EX_BOUNDARY)) {
-            return trimQuota(attrValue[1].trim());
-          } else {
-            throw new BatchException("Invalid boundary format", BatchException.MessageKeys.INVALID_BOUNDARY, "" + line);
-          }
-        }
-
-      }
-    }
-    throw new BatchException("Content type is not multipart mixed", 
-        BatchException.MessageKeys.INVALID_CONTENT_TYPE, HttpContentType.MULTIPART_MIXED);
-  }
-
-  public static String removeEndingSlash(String content) {
-    content = content.trim();
-    int lastSlashIndex = content.lastIndexOf('/');
-
-    return (lastSlashIndex == content.length() - 1) ? content.substring(0, content.length() - 1) : content;
-  }
-
-  private static String trimQuota(String boundary) {
-    if (boundary.matches("\".*\"")) {
-      boundary = boundary.replace("\"", "");
-    }
-
-    return boundary;
-  }
-
-  public static List<List<Line>> splitMessageByBoundary(final List<Line> message, final String boundary)
-      throws BatchException {
-    final List<List<Line>> messageParts = new LinkedList<List<Line>>();
-    List<Line> currentPart = new ArrayList<Line>();
-    boolean isEndReached = false;
-
-    final String quotedBoundary = Pattern.quote(boundary);
-    final Pattern boundaryDelimiterPattern = Pattern.compile("--" + quotedBoundary + "--[\\s ]*");
-    final Pattern boundaryPattern = Pattern.compile("--" + quotedBoundary + "[\\s ]*");
-
-    for (Line currentLine : message) {
-      if (boundaryDelimiterPattern.matcher(currentLine.toString()).matches()) {
-        removeEndingCRLFFromList(currentPart);
-        messageParts.add(currentPart);
-        isEndReached = true;
-      } else if (boundaryPattern.matcher(currentLine.toString()).matches()) {
-        removeEndingCRLFFromList(currentPart);
-        messageParts.add(currentPart);
-        currentPart = new LinkedList<Line>();
-      } else {
-        currentPart.add(currentLine);
-      }
-
-      if (isEndReached) {
-        break;
-      }
-    }
-
-    final int lineNumer = (message.size() > 0) ? message.get(0).getLineNumber() : 0;
-    // Remove preamble
-    if (messageParts.size() > 0) {
-      messageParts.remove(0);
-    }
-
-    if (!isEndReached) {
-      throw new BatchException("Missing close boundary delimiter", BatchException.MessageKeys.MISSING_CLOSE_DELIMITER,
-          "" + lineNumer);
-    }
-
-    return messageParts;
-  }
-
-  private static void removeEndingCRLFFromList(final List<Line> list) {
-    if (list.size() > 0) {
-      Line lastLine = list.remove(list.size() - 1);
-      list.add(removeEndingCRLF(lastLine));
-    }
-  }
-
-  public static Line removeEndingCRLF(final Line line) {
-    Pattern pattern = PATTERN_LAST_CRLF;
-    Matcher matcher = pattern.matcher(line.toString());
-
-    if (matcher.matches()) {
-      return new Line(matcher.group(1), line.getLineNumber());
-    } else {
-      return line;
-    }
-  }
-
-  public static Header consumeHeaders(final List<Line> remainingMessage) {
-    final int headerLineNumber = remainingMessage.size() != 0 ? remainingMessage.get(0).getLineNumber() : 0;
-    final Header headers = new Header(headerLineNumber);
-    final Iterator<Line> iter = remainingMessage.iterator();
-    Line currentLine;
-    boolean isHeader = true;
-
-    while (iter.hasNext() && isHeader) {
-      currentLine = iter.next();
-      final Matcher headerMatcher = PATTERN_HEADER_LINE.matcher(currentLine.toString());
-
-      if (headerMatcher.matches() && headerMatcher.groupCount() == 2) {
-        iter.remove();
-
-        String headerName = headerMatcher.group(1).trim();
-        String headerValue = headerMatcher.group(2).trim();
-
-        headers.addHeader(headerName, Header.splitValuesByComma(headerValue), currentLine.getLineNumber());
-      } else {
-        isHeader = false;
-      }
-    }
-
-    return headers;
-  }
-
-  public static void consumeBlankLine(final List<Line> remainingMessage, final boolean isStrict) throws BatchException {
-    //TODO is \r\n to strict?
-    if (remainingMessage.size() > 0 && remainingMessage.get(0).toString().matches("\\s*(\r\n|\n)\\s*")) {
-      remainingMessage.remove(0);
-    } else {
-      if (isStrict) {
-        final int lineNumber = (remainingMessage.size() > 0) ? remainingMessage.get(0).getLineNumber() : 0;
-        throw new BatchException("Missing blank line", BatchException.MessageKeys.MISSING_BLANK_LINE, "[None]", ""
-            + lineNumber);
-      }
-    }
-  }
-
-  public static InputStream convertLineListToInputStream(List<Line> messageList) {
-    final String message = lineListToString(messageList);
-
-    return new ByteArrayInputStream(message.getBytes());
-  }
-
-  private static String lineListToString(List<Line> messageList) {
-    final StringBuilder builder = new StringBuilder();
-
-    for (Line currentLine : messageList) {
-      builder.append(currentLine.toString());
-    }
-
-    return builder.toString();
-  }
-  
-  public static String trimLineListToLength(final List<Line> list, final int length) {
-    final String message = lineListToString(list);
-    final int lastIndex = Math.min(length, message.length());
-
-    return (lastIndex > 0) ? message.substring(0, lastIndex) : "";
-  }
-  
-  public static InputStream convertLineListToInputStream(List<Line> list, int length) {
-    final String message = trimLineListToLength(list, length);
-
-    return new ByteArrayInputStream(message.getBytes());
-  }
-}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchPart.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchPart.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchPart.java
deleted file mode 100644
index 104c152..0000000
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchPart.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.olingo.server.core.batch.parser;
-
-public interface BatchPart {
-  public Header getHeaders();
-
-  public boolean isStrict();
-}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchQueryOperation.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchQueryOperation.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchQueryOperation.java
deleted file mode 100644
index 6a5309e..0000000
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchQueryOperation.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.olingo.server.core.batch.parser;
-
-import java.util.List;
-
-import org.apache.olingo.server.api.batch.BatchException;
-import org.apache.olingo.server.core.batch.parser.BufferedReaderIncludingLineEndings.Line;
-
-public class BatchQueryOperation implements BatchPart {
-
-  protected final boolean isStrict;
-  protected Line httpStatusLine;
-  protected Header headers;
-  protected List<Line> body;
-  protected int bodySize;
-  protected List<Line> message;
-
-  public BatchQueryOperation(final List<Line> message, final boolean isStrict) {
-    this.isStrict = isStrict;
-    this.message = message;
-  }
-
-  public BatchQueryOperation parse() throws BatchException {
-    httpStatusLine = consumeHttpStatusLine(message);
-    headers = BatchParserCommon.consumeHeaders(message);
-    BatchParserCommon.consumeBlankLine(message, isStrict);
-    body = message;
-
-    return this;
-  }
-
-  protected Line consumeHttpStatusLine(final List<Line> message) throws BatchException {
-    if (message.size() > 0 && !message.get(0).toString().trim().equals("")) {
-      final Line method = message.get(0);
-      message.remove(0);
-
-      return method;
-    } else {
-      final int line = (message.size() > 0) ? message.get(0).getLineNumber() : 0;
-      throw new BatchException("Missing http request line", BatchException.MessageKeys.INVALID_STATUS_LINE, "" + line);
-    }
-  }
-
-  public Line getHttpStatusLine() {
-    return httpStatusLine;
-  }
-
-  public List<Line> getBody() {
-    return body;
-  }
-
-  public int getBodySize() {
-    return bodySize;
-  }
-
-  @Override
-  public Header getHeaders() {
-    return headers;
-  }
-
-  @Override
-  public boolean isStrict() {
-    return isStrict;
-  }
-}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/51acf8ae/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchRequestPartImpl.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchRequestPartImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchRequestPartImpl.java
deleted file mode 100644
index 9765b37..0000000
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/batch/parser/BatchRequestPartImpl.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.olingo.server.core.batch.parser;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-import org.apache.olingo.server.api.ODataRequest;
-import org.apache.olingo.server.api.batch.BatchRequestPart;
-
-/**
- * Has to be immutable!
- *
- */
-public class BatchRequestPartImpl implements BatchRequestPart {
-
-  private List<ODataRequest> requests = new ArrayList<ODataRequest>();
-  private boolean isChangeSet;
-  
-  public BatchRequestPartImpl(final boolean isChangeSet, final List<ODataRequest> requests) {
-    this.isChangeSet = isChangeSet;
-    this.requests = requests;
-  }
-
-  @Override
-  public boolean isChangeSet() {
-    return isChangeSet;
-  }
-
-  @Override
-  public List<ODataRequest> getRequests() {
-    return Collections.unmodifiableList(requests);
-  }
-}