From f4e5b52bb189219fec82d9f51a002152ecd5c926 Mon Sep 17 00:00:00 2001
From: Viktor Kovtun <viktor.kovtun@id.ethz.ch>
Date: Mon, 5 Aug 2019 15:58:51 +0200
Subject: [PATCH] SSDM-8405 Research Collection Integration.

---
 .../eln-lims/1/as/master-data/data-model.xls  | Bin 71168 -> 73728 bytes
 .../publication-api/plugin.properties         |   2 +
 .../publication-api/publication-api.py        |  93 +++++
 .../html/img/research-collection-icon.png     | Bin 0 -> 9943 bytes
 .../1/as/webapps/eln-lims/html/index.html     |   4 +
 .../html/js/controllers/MainController.js     |  15 +-
 .../eln-lims/html/js/server/ServerFacade.js   | 106 ++++-
 .../webapps/eln-lims/html/js/util/FormUtil.js |  27 +-
 .../html/js/views/Export/ExportTreeView.js    |   2 +-
 .../ResearchCollectionExportController.js     | 127 ++++++
 .../ResearchCollectionExportModel.js          |  23 ++
 .../ResearchCollectionExportView.js           |  75 ++++
 .../js/views/SideMenu/SideMenuWidgetView.js   |  10 +-
 .../{exports-api.py => exportsApi.py}         | 343 ++++++++--------
 .../exports-api/generalExports.py             |  87 ++++
 .../exports-api/plugin.properties             |   2 +-
 .../rc-exports-api/exportsApi.py              |   1 +
 .../rc-exports-api/plugin.properties          |   8 +
 .../rc-exports-api/rcExports.py               | 384 ++++++++++++++++++
 19 files changed, 1095 insertions(+), 214 deletions(-)
 create mode 100644 openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/services/publication-api/plugin.properties
 create mode 100644 openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/services/publication-api/publication-api.py
 create mode 100644 openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/img/research-collection-icon.png
 create mode 100644 openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ResearchCollectionExport/ResearchCollectionExportController.js
 create mode 100644 openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ResearchCollectionExport/ResearchCollectionExportModel.js
 create mode 100644 openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ResearchCollectionExport/ResearchCollectionExportView.js
 rename openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/exports-api/{exports-api.py => exportsApi.py} (80%)
 create mode 100644 openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/exports-api/generalExports.py
 create mode 120000 openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/rc-exports-api/exportsApi.py
 create mode 100644 openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/rc-exports-api/plugin.properties
 create mode 100644 openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/rc-exports-api/rcExports.py

diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/master-data/data-model.xls b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/master-data/data-model.xls
index 9f7724cae233674fc63335ad063636d9b015b00e..8d7784125bad3226f67a0cf35266de503b5a48cb 100644
GIT binary patch
delta 15583
zcmZu&X>?UpmOk$#1jzIPVJ1L;kO(SefN~y^mzO{&B*-8L3PJ)J8cD#RTFdgfT;0`O
z-KA}Nd)cdpU;U$3_v*o}j@_k}gfLXeu+)%%%t*=L5TGalTZLeM-@f<UbI$W7>%lww
z?6beK&;HIn`wn^W-*aE=%6)Y~;wQg+U`HbH^N!)kt%<T{lu|#&=a2Du7N6(vx&GaE
z-}U3;U(9(v@5OUJ81>qZ{LQ&POZJ}W_%`>A+yOmPA?f(f!M7%gPhP$e-wz~8j!vi6
zm-(9`k$O`^n+sk_Jl}Dp;P=U_o{lezH)k8@*j&<*9P}@B-O0?H%PTrg483Poa^y;X
z-0aqY?~bV&pwzmKmuFXx_C_kTU`cvjV_ogy#-{m;R@F7ln^#}Q@1?qD)x!D(O^X^A
zH!W<eUqt<<*KBBQUj0;S+r}r$>)JMKSli6+)R&&}g=^c}o@{-pZFB3|CkwTez&?gI
zv~6fvySaQ_+vf82r#)jTM>Iz(RbfqNdeg$$weuU7%V^kY+Wgq+jjhiK_>e5s#Sbm0
z&sA#iv+ZjE<5+3T)X!hs*w9$NFkh+0mbDw7YF*d5c5{jHT)ia==Z(r5nyFt@x3F;m
zSLBAQuBHX`^V5xsus-u@7uRQ28I(n1m1=8WyAi!ujSZ`xTH8`?V4p0B7NwAOS7mA!
z*M6tgcWirfMWT3olTv?=W%v*LKXpvUm^D8wtf>K)f}c$ZrGBzXsZSpR+cqinYfq`$
zUQz1r{!yvf|EAQh-coA9PNn|!L#4(Y0uw)0>VY#Iy&DUYiA^0R+yC|+FJINb2!0YN
zH7Hr4eu=Nyvj2{eb;QWtjFJ6MjO>4n3=<B4e`SP`4AaMCo_8krc8u(un6bZ((R=rM
zdetRr%lCvOsxwBmHIQ|@@W!Wu?&(y;iZf2D27J;g2Sf598Gv?!%2SDC9$JI&y&rP=
zkx?V?Z>Jii-ty=F!&r}g)WM1pjki@o)hIAEY$ee*G-~CbFBP?NVe59RNvA4NZv(L=
z`W>uZNaic9MjD@s_YwOYSZ+{vLVrMV5L!v}B~*W*bmhYA@`Uoc-+H-<L%Bd^=({p>
z6l5IwZZOn;<?WGEUxSRs-xY`RqE;TDu=T?}xbJ`P_E@h$tq9?_gz&Vt1&bzAft@WX
zSIt)1>K3v3FvbvL3zpMZ-6D(1KF*hMGV|?}`92b2b!sdRV4W6Nrvugru#{REf^|Az
zod65^2u1=Ju&n~NDg@iA!GN{Z!rBU~JB(*r1?<rfY^wvdRlwMH5HRb(jAeeyJ2!jN
z)HJnR-Ku_}Hpnk`X&0tan6-1a#mKhD$aciYkR!fl@3$gb>|sLq33usxB6N4;E|J~j
z=<SZt+at0XM{jS8?0u0f4)wesFMZ$$0~xX!X6*uZLzbN~_KC~|?un80#>n;~L&2as
z_&|*8qZrx27#Y&=_v{^xksS$T#E+K$XehL0$6{p1V`L{{WFKeANb@ep49&ZiDdvyW
zhz2)pm&Nlgi|1X=p6U{s|1}J819v$z?-H7$&!4_!BvQkg(DF919H4BoEN^oxZxhR?
z4#e^{$MQC@%s#q7>Kiw<ix<ry)^?3m3zxQAVA~zA?E=<<L3Crg1GZhjS`z-pTgG@e
zpk;M-XuB}GL(B$4cff2K19Xe01nL(tyF<(lk6Ju4rl3V1xGjwjJH>}FkTDWF4VH%n
zb7^33CuZHCilP>?r#8P6lQ4bU;TeVM1nDtf{TAkVlqF?a+~}F;czO?gndo?W4?4kt
z#7XW_eoog=4^^a`db?y^TrlPUbHeS033I~j7859a1Z+2^U!#UZtz5MD6IkI1hZuyd
zJhawCt$}DI*}7K%2cfCSv0DvB3pJojYqu&ui}Pg~O5+n8k9)-HQT~Ol$v4kd<&YC}
zk3>E%YUSY=(C6A7tYNLn^Y7g@HgVctzHPE+#_g4Hv`#B~5!(7_++JbKz_5==&aD0W
zG7G)-%ynk%KR{ow_ADwskmaQx%zomBe|`I4WXU?TsqtYH0OmpyfNt0^0qAxD(Cq}E
z8>?1>v4I!e@W}+A8$Ou;bgM7`o;chM7fk@VqX2NaOu}h=f@R-_$uyuA3b?#armVl4
zvS1%3l8S<|&k4#tXFGcPWOW%af-$=rupWS=)aekc#{%n-eS>lc(du!)dYnl1IAA?d
zB#FgSBh4%wc-AXmJPo)@do8eD3#``x>lLtDFoqN96|kzP#Tuj<ErRXG|HiTXf^`oz
zhexpe7TA6ZY`+7xU%)b<`TY*qe(`KZ2*zX1fE^I9GmsJMfQ5BH<=hR$U>OfMU<U-O
zJ_I}9EaL$IL!W<f=g8cYx;Nn${AR`-KM0jS66MOM#bsb0mw^YXnb|>^*?s;qznP4E
zP#%srDA3cR7W1ICc2J<%M{7KijkQB!?JQ*U^N{7|A!|(!iJMbH>xTrZ0dbHB9+IgK
zi#j>=nP?G=XT1SCEMT8PMzF&c*kKFoumg5jz;v2B>|l9^#j{x<7>fu4c0|BF3&D<9
zU`H&lBM#UR0h^66q~;L^?1*6fFvMbQV(o*Y0BcZpfSdH}s0DV^0y_$@1~oA>e-vPL
ziy_micobj_YH|q1qQ^LPOt5&3Bm3Z(1$N8=JLZ5L6R=4k^fAY?V**wYg0Y%1V8;dQ
zuF$dL7S?fl9~>7jedZq*Fx+pDWjyYH9Tzb4`8DscBGT2w3Cr{eaU&RgLPDY~pAcL6
zfIlH2nF1VMp|ZR)SExK3J{IkRszyyig^)=GmlPY+N>zr-j7s%-zFcFJ;cKN|^Xh2-
zzFm19uQ*P^R8iKY`0rw5pTx*c#mJbAi?YgU=9Z$YdLH}G`<@~xoIRP(V`N{%$ogVr
z=R{V6km4u2(m5|OUR>N^gcn5UDqe&v`-<Zafy{qt_gxRT+E=8<y=M7JWUk)VF;>3`
ztrqXOYq+c3A6WHQ?iuHCpPocJgOCOl!AUuYS>v6QDyRm<C$$(qQ)m26={h6mOM)aR
zXfSCps%d<JBkmJurc^DKhLrjwvi=FJdl(j&XV^{oaSDS>`Ed%|uEG6_fYGPGZd3oA
z!p^BdkM6U}k5k~UDL+o>@<ZG?1@70Vu{trHipmep@)YOhPv1MzOGjnLX={U>w(`Vj
zOu(EYr)9h4h0{5W37A^&G$2t6qK~UY{-*H>R_P2JPAi=!&S<O}WWzHS*cpJCYlAZa
zrt`!Z0n-`qjO^`v|HR(WR+c!c4a4qPD@&Ypvcy?fGM52oqb$Ktb2k~Xd=`cCGlwkP
zUlCb6^|`~A&n29iE!=iy9lj8@42Fzx_#!g@g=3!E(vYRkAxocF*JSB~lb&HqpTm|u
zcwK|R0eAY~wP8!2!<If_izZ8-LzX_78biv=U|cx|%R(Pd4z+XE(x0<Jdd}g`Ifp;z
zB&7OU;+(@DrW^xyUcfYe&RbaLm1k<?^A3N`JN!BC@aKHMAAry^rZNL|0bu6jxnO}^
zusC%=b1EovE;yvR;E;-0(U9t*EHBB%%ox!KEj#D@LE@}g^rbVwFQJ)IjgXN=-j|WV
zOAs02T!Na(GMB`lCe9^zVt8}O;msw7H<ujVTyl7GiM%O+seoda0^;~DemL67ESIeX
zylgS!vcrta4l^!0%((0@gQtcG))hEuGRqZ>^)NEP6$|W&!;C8qGp;zyxZ*H_$B_a1
z3SeM{Kfil4E^-*cuQUt7mYrF?a!9~K%#h&gC}up!3<<sgg1NcyjYEQO?YVgBTd1bg
zTnNd6ZzF?OAu%Ml3N1r|s}2dSIwZL2kl?CAf~yV*u7U)}I!OR=4X%;}5ecpaBtRc`
z);0WZ*6$i7V~(6_I#`I+H7i!v9AaE^h;dC5BiMD<9A@z3H(>n&rgKKW1=eqY^*hAq
zcZkvN5ToBAhTq&Xbc4wf*Ddqc#eA?f*ByRbhe31lT#xv1P~EL63Y02KR-*EprCyi9
zb0rE-)ADP3%O<e;gj-m2_qKB)kohn5X4hB)q{pqX@<djHfAJHRc!L59j%;us^NaUq
z*H}f;bCbVv|3t4OX5^3<+0YnSX^d=`$lTct7n$qDh(OQ1CqF92;OH3Hm>AjE7+IOf
z+{ug+nVw8|Pkwxi;si%AC7XrOGig92cwdg2h!HetAc3Yy1BpZuTKX(VLesE3=|MH6
z<}i{-1Ifr>4%AE<$bptg136+)p9MJy`z**w*cE3^!mc555_S!dli&@naY;bagPeq&
z26AK~x&AF5*=Ip6fH`+w2Im4+-zdoisHrk@0h+>A3GC(q)Kr<dKrvM&*-d*vl{o-T
znzLYl#^PBpzyccpFjHj?5HMY34iGS1WexyX3a$mYjN8V5<pGRm!F4zoOf*j{>1)wE
zSTcJ(Pb~3TluO0UW=Jq7iWxVYA;DlE7!nMYm}wH^+jH?$K2%fcewZf-@*{%<P%|Vb
zfR-Uaff&>zC~!zn;E<reAwhvdf&!4hItvOM5)=d^2-mOBp0g^%WXxGmsDp)A6<V<>
zbcj*t5Tj5NBRC5R9cC~?nP3$Om_7@NEU+RAtjHlokwc6khZsc;G5pfQL#?ZjV#|E7
zm=B^}?C_)5;YYE<52jSZkCG^yOsj?;L*P`z4^It|cxr+$+Zz9eLN%p+6!K$eWRUsS
z@S_x3h99M3PxGVH;YX>%k5V`W4#1YMyVM~_sbq;{)MqD&QimT*{AS9-BxL%o_%Lft
zhgl&Truh+YV3<RYVVWQ^tziy7c%m4v;R2@lG28+hZh;MV_%Yn!hd10I$Z&@sJgN-X
z2!NT&VT1)X!eYw^hb<!<wv2Gt!ZR<!n^wW@9~DKH2cMzKXuw8vQKKcgnk_sI%>>6l
zGo|K*_Quej|ISfQ?xu`&LdN6Kj4P8>)FCUg94oU{u}p_dqEhBWtjvj6nG>;b{+q{2
z6L<N$j!pFJ1U}A*!#F1l<D4+?j5f0!?}TBzHQVu47{)tc7$1azPdJ#z$47Bst;^%y
zn4chvI3Jok>?T;|Cs^hui20__-UP?}31Xdn2i3i5F6zTulB49#q`$Pc+`qqfsN9V#
z2W)=Uox+JCb8Ey&F|x@qvWghl6p^`QP-OylBeQ|4Vic!}Ouxerysj`UMs}mfbX^tb
zv6Wry-W=&M4q^Q|JrWw(EszaXE^xKT+?AOjG8cGejBHl6%tJYTs|a0?+hSyVOKEUc
znD|5>TgE&0*)k@kY}wtAdD%if{>ql!n<Zm~K+dG)Nq4yrlP_(N(BzFdcqT@fgxoQi
zWD=+?nIuTMlVHM>r=*nG1(RhLtc8rQlOuy9nK^hWpk)r83bCgTo(k+1bEl%hIe5GZ
zY#(#*RABp<TU-^+!Bc_FWG+%GoGeCmr$L*bikyPUn8IX=tWU6sr&w8R3bUA}$}x-!
zH%0cbE?K8I`*;eVP05OFV}Vr)m_B$aEwD-ptkOAnDg}#Gsbu6z0n;UGr6dhqvT{2b
z&#C}s4xTCttjbDBRbpP3u~o2c%GfHguFKe|y!Ohdap4+pPaBd<1DH8(rU5Jik_DAC
zce<I@jnGUfZZ59mjnGV~?$9PTrQyj<&@?=`Dd8p293QEYC>nxE!a&?C5D$i$Hw(ms
zsFef2htOgYXC5)*rpvgcq2_cMw=8Pqco?`mY%<wo)cawJX~xXE+L?DX%$WQA)egU_
z9e!6k^saX3UF{a$)e*nx1v8rQVg}&TiW!YZ(G0+|FZdo@gK=}t5Rx$jGlT&jgs#jG
ze!UR22AW`bP~{cUhv+0MAq?zHfn|Oq)JzL@rp4`<4&7!7n7$=EQ>>GUv`&T?>$6}T
z*WN!2&CjyT&$7(V67zgUPS0kE`KO~6C$IZB(Tr+FA6Mg6nLNiZna_#d_qh%7fXL|c
z<lWP>+87$V9i1uF7Pgq()9R_H#l93|G|DWWRxM%Qoic81*kU$F$%A-m-X(p7;W%df
z6dpJPR=m5Vlh-;_W%f_use*11usH1FDUedjq89reLW{SaW?+TOP+A1VMeuIrN<1><
z+m^S>Z?SqoJ%b0ME1^?nem#8UEh!cTxmgEoZHz1}GWVdZi;-nwWc4vJuOUV_J4QAq
zM%EZ3n;RpWCo=bdzdw*M|KTSrLFUH{Zi*Sa0J7{udZEZ%kVP>vYM8O%y{9EHiVr%9
zK{2&dWNsBlMi_&E9(j>1TOK1@LI1NAAC8ed5+hqBGUG7`%&BG6IxZ=;U=7Hf7Gjf2
zW((Fr9&AB5|7x-GGwLx6q6tzgt=gj&dA=!XG4tuoO5&x}<6&PNjHXm;)S{-|o}^<+
zUREJOMl|_0Nk8k~4B*XfB}-F^5Adj3FRH(fTGZ5gm()$kGc{^9h~~vmvq5ZXO%gh#
z_&|)Bvqh7Sj`|h;VqvzUN}i|WaTZnQh$^2Q1ueqs>_GOX)C2gJeT||?!UZjRowz4b
ziX3C#T+t+x(DKlvQJq$}RWfQl02#k|Pz~b8?UGS{1UVnP-w$~seuI}Qxod(K_SiRH
z#^{&-xO+_8o6wiRo$+8IO+fbMM0W|Bkn`sPF6iVIOXC36Elxy7dVx&&iKve!;0<Am
z+t4_$Fmiw^V;oqd9SG`QayW(i{DF6i9S^u{#)Bn}2TL#!<G>Qf0dC*4+8j>nL9wYx
z{h&DTB>IH^56Wq$Jz!)~c*Q92d?^MR<}Q_id|il<T#9iGc$YqCF@!0_2Z-!jCgU`X
zm&v%xQ6KB2x7Z^8nJiLx))owWNCxsrG0i+A1NjUsXmQ}HVT%bRrT#B!aiW^z%Vpfx
zQ6I-`3tKB>9G{vA|5pg$g{YHs<byH-u7HOryvrN3n3z(E%wXTcGEVdVVF6_H*~fI1
zlH)9BacY|XkBBD2gT6bVN(i3jRAo-fs6PWB`M(MfxWO&o2H_j8;>!kxSMmPz(^Xz@
zxbj~fv^+^4g@vJ64|i6J%sn;N#OO7P%w$yBYl#`VPV4dVSsz}H#Ta}%W^ika-g<4&
zvbP~d_C(fLxqY!wgsyjOS&H<e9jiIisxi3UT&;}ybHMTquScQ5ywZppow*VZu3Sng
zFh*fD3fy@j=jvtD&-HDbH5h0%=^8}XoF{7#V3Ty#z(hv<1po=Z2DN3K`pc-5ht@Bm
z)<Cpgj#`6MQ<SUL;Oesp^%U>}Gx=!#I8rS@>)EJPh}Lsas|c;<qgF9mKMh;Wa3ZZ<
ziCT6FZ-x`*P;8b$AGu|^6z0wFf_anaw;A<ut(uSLyPR0Fl;3}azAdUkHT!?_S(VqE
zDCTYu{X%H)sRMt9$cPq9A|<akb9@U{(ww&~&Lmp&BxDXP;T(cdtjc>r)E2B&3g1g=
z<X`{J&qsSL(Ht1o7R;em&`E5w{np8XP-7kZ2o55$+z{q5Xt;zS<5Th$GsiuSIR=Cw
z>p>XI12Y`2S~1WNrZpl=CoV=?{fnPZ^wtY_Z!kcuj~0S|r?y@eB8@2pOHHWti2}4b
z@wV9dtR>|}GEQxXoZ1jM#cgGrdV)@Q&Nko{3-TbLHb!1?n?+u!Hpi*9$SKC8&DjNQ
zuwZsU8)7$Cy#ZJG2Yqc|P@VEqeIq>X*fib_2w2`8tpN?T!=NcY+F{V7=5`oN$zK$b
zlI;;CIr;Wzo!YH+I;bY8*Kq)kN|a*?J?fwF=W~<%|LPl=Sm^KSyP<d!97U#6n-cuy
uU+<gu+J(ZYe*)Q`;`1|nn6sHj_;<<N-rUsO%G|)ObWFUkH}OvwEB_z5@Boqk

delta 12607
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
K-hcmS!T$q!u78UF

diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/services/publication-api/plugin.properties b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/services/publication-api/plugin.properties
new file mode 100644
index 00000000000..76d5aa52bab
--- /dev/null
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/services/publication-api/plugin.properties
@@ -0,0 +1,2 @@
+class = ch.ethz.sis.openbis.generic.server.asapi.v3.helper.service.JythonBasedCustomASServiceExecutor
+script-path = publication-api.py
\ No newline at end of file
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/services/publication-api/publication-api.py b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/services/publication-api/publication-api.py
new file mode 100644
index 00000000000..cc5c7e99fe2
--- /dev/null
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/services/publication-api/publication-api.py
@@ -0,0 +1,93 @@
+import traceback
+
+from ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset import DataSetKind
+from ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.create import DataSetCreation
+from ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.fetchoptions import DataSetFetchOptions
+from ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.id import DataSetPermId
+from ch.ethz.sis.openbis.generic.asapi.v3.dto.datastore.id import DataStorePermId
+from ch.ethz.sis.openbis.generic.asapi.v3.dto.entitytype import EntityKind
+from ch.ethz.sis.openbis.generic.asapi.v3.dto.entitytype.id import EntityTypePermId
+from ch.ethz.sis.openbis.generic.asapi.v3.dto.experiment.id import ExperimentIdentifier
+from ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.create import SampleCreation
+from ch.ethz.sis.openbis.generic.asapi.v3.dto.space.id import SpacePermId
+from ch.systemsx.cisd.common.logging import LogCategory
+from ch.systemsx.cisd.openbis.generic.client.web.client.exception import UserFailureException
+from java.util import ArrayList
+from org.apache.log4j import Logger
+
+operationLog = Logger.getLogger(str(LogCategory.OPERATION) + '.publication-api.py')
+
+
+def process(context, parameters):
+    method = parameters.get('method')
+
+    try:
+        if method == 'insertPublication':
+            result = insertPublication(context, parameters)
+        else:
+            raise UserFailureException('Unknown method: "%s"' % str(method if method is not None else 'None'))
+    except Exception as e:
+        operationLog.error('Exception at: ' + traceback.format_exc())
+        operationLog.error('Exception: ' + str(e))
+        result = {
+            'status': 'FAILED',
+            'error': str(e)
+        }
+    return result
+
+
+def insertPublication(context, parameters):
+    transaction = context.applicationService
+    sessionToken = transaction.loginAsSystem()
+
+    v3 = context.applicationService
+
+    name = parameters.get('name')
+    if name is None:
+        raise UserFailureException('name parameter missing')
+
+    sampleId = createPublicationSample(parameters, sessionToken, v3).get(0)
+    createDataSet(parameters, sessionToken, v3, sampleId)
+
+    return {
+        'status': 'OK',
+    }
+
+
+def createDataSet(parameters, sessionToken, v3, sampleId):
+    openBISRelatedIdentifiers = parameters.get('openBISRelatedIdentifiers').split(',')
+    identifiers = ArrayList(len(openBISRelatedIdentifiers))
+    for identifier in openBISRelatedIdentifiers:
+        identifiers.add(DataSetPermId(identifier))
+
+    dataSetIds = v3.getDataSets(sessionToken, identifiers, DataSetFetchOptions()).keys()
+    operationLog.debug('Found %d data sets.' % len(dataSetIds))
+
+    dataSetCreation = DataSetCreation()
+    dataSetCreation.setAutoGeneratedCode(True)
+    dataSetCreation.setTypeId(EntityTypePermId('PUBLICATION_DATA', EntityKind.DATA_SET))
+    dataSetCreation.setSampleId(sampleId)
+    dataSetCreation.setDataSetKind(DataSetKind.CONTAINER)
+    dataSetCreation.setComponentIds(dataSetIds)
+    dataSetCreation.setDataStoreId(DataStorePermId('STANDARD'))
+    v3.createDataSets(sessionToken, [dataSetCreation])
+
+
+def createPublicationSample(parameters, sessionToken, v3):
+    publicationOrganization = parameters.get('publicationOrganization')
+    publicationType = parameters.get('publicationType')  # The only valid value for now is "Public Repository"
+    publicationDescription = parameters.get('publicationDescription')  # Can be empty
+    publicationURL = parameters.get('publicationURL')
+    publicationIdentifier = parameters.get('publicationIdentifier')
+
+    sampleCreation = SampleCreation()
+    sampleCreation.setTypeId(EntityTypePermId('PUBLICATION'))
+    sampleCreation.setExperimentId(ExperimentIdentifier('/PUBLICATIONS/PUBLIC_REPOSITORIES/PUBLICATIONS_COLLECTION'))
+    sampleCreation.setSpaceId(SpacePermId('PUBLICATIONS'))
+    sampleCreation.setProperty('$NAME', 'TEST NAME')
+    sampleCreation.setProperty('$PUBLICATION.ORGANIZATION', publicationOrganization)
+    sampleCreation.setProperty('$PUBLICATION.TYPE', publicationType)
+    sampleCreation.setProperty('$PUBLICATION.IDENTIFIER', publicationIdentifier)
+    sampleCreation.setProperty('$PUBLICATION.URL', publicationURL)
+    sampleCreation.setProperty('$PUBLICATION.DESCRIPTION', publicationDescription)
+    return v3.createSamples(sessionToken, [sampleCreation])
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/img/research-collection-icon.png b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/img/research-collection-icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..67c8597c90c2d1b3274d4c9639ae83b25db711ce
GIT binary patch
literal 9943
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

literal 0
HcmV?d00001

diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/index.html b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/index.html
index 65763885d61..65c0c67df06 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/index.html
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/index.html
@@ -249,6 +249,10 @@
 	<script type="text/javascript" src="./js/views/Export/ExportTreeModel.js"></script>
 	<script type="text/javascript" src="./js/views/Export/ExportTreeView.js"></script>
 	
+	<script type="text/javascript" src="./js/views/ResearchCollectionExport/ResearchCollectionExportController.js"></script>
+	<script type="text/javascript" src="./js/views/ResearchCollectionExport/ResearchCollectionExportModel.js"></script>
+	<script type="text/javascript" src="./js/views/ResearchCollectionExport/ResearchCollectionExportView.js"></script>
+
 	<script type="text/javascript" src="./js/views/DrawingBoards/DrawingBoardsController.js"></script>
 	<script type="text/javascript" src="./js/views/DrawingBoards/DrawingBoardsModel.js"></script>
 	<script type="text/javascript" src="./js/views/DrawingBoards/DrawingBoardsView.js"></script>
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/controllers/MainController.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/controllers/MainController.js
index 09690f4f1d5..142198f6bab 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/controllers/MainController.js
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/controllers/MainController.js
@@ -376,12 +376,19 @@ function MainController(profile) {
 					break;
 				case "showExportTreePage":
 					document.title = "Export Builder";
-					var newView = new ExportTreeController(this);
-					var views = this._getNewViewModel(true, true, false);
-					newView.init(views);
-					this.currentView = newView;
+					var newExportView = new ExportTreeController(this);
+					var exportViews = this._getNewViewModel(true, true, false);
+					newExportView.init(exportViews);
+					this.currentView = newExportView;
 					//window.scrollTo(0,0);
 					break;
+				case "showResearchCollectionExportPage":
+					document.title = "Research Collection Export Builder";
+					var newResearchCollectionExportView = new ResearchCollectionExportController(this);
+					var researchCollectionExportViews = this._getNewViewModel(true, true, false);
+					newResearchCollectionExportView.init(researchCollectionExportViews);
+					this.currentView = newResearchCollectionExportView;
+					break;
 				case "showLabNotebookPage":
 					document.title = "Lab Notebook";
 					var newView = new LabNotebookController(this);
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/server/ServerFacade.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/server/ServerFacade.js
index 5f6bde3add7..b9f816840a1 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/server/ServerFacade.js
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/server/ServerFacade.js
@@ -302,7 +302,65 @@ function ServerFacade(openbisServer) {
 			"entities" : entities,
 			"metadataOnly" : metadataOnly,
 		}, callbackFunction, "exports-api");
-	}
+	};
+
+	//
+	// Research collection export
+	//
+	this.exportRc = function(entities, includeRoot, metadataOnly, submissionUrl, submissionType, userInformation, callbackFunction) {
+		this.asyncExportRc({
+			"method": "exportAll",
+			"includeRoot": includeRoot,
+			"entities": entities,
+			"metadataOnly": metadataOnly,
+			"submissionUrl": submissionUrl,
+			"submissionType": submissionType,
+            "userInformation": userInformation,
+			"originUrl": window.location.origin,
+			"sessionToken": this.openbisServer.getSession(),
+		}, callbackFunction, "rc-exports-api");
+	};
+
+	this.asyncExportRc = function(parameters, callbackFunction, serviceId) {
+		require(["as/dto/service/execute/ExecuteAggregationServiceOperation",
+				"as/dto/operation/AsynchronousOperationExecutionOptions", "as/dto/service/id/DssServicePermId",
+				"as/dto/datastore/id/DataStorePermId", "as/dto/service/execute/AggregationServiceExecutionOptions"],
+			function(ExecuteAggregationServiceOperation, AsynchronousOperationExecutionOptions, DssServicePermId, DataStorePermId,
+					 AggregationServiceExecutionOptions) {
+				var dataStoreId = new DataStorePermId("STANDARD");
+				var dssServicePermId = new DssServicePermId(serviceId, dataStoreId);
+				var options = new AggregationServiceExecutionOptions();
+
+				options.withParameter("sessionToken", parameters["sessionToken"]);
+
+				options.withParameter("entities", parameters["entities"]);
+				options.withParameter("includeRoot", parameters["includeRoot"]);
+				options.withParameter("metadataOnly", parameters["metadataOnly"]);
+				options.withParameter("method", parameters["method"]);
+				options.withParameter("originUrl", parameters["originUrl"]);
+				options.withParameter("submissionType", parameters["submissionType"]);
+				options.withParameter("submissionUrl", parameters["submissionUrl"]);
+				options.withParameter("entities", parameters["entities"]);
+				options.withParameter("userId", parameters["userInformation"]["id"]);
+				options.withParameter("userEmail", parameters["userInformation"]["email"]);
+				options.withParameter("userFirstName", parameters["userInformation"]["firstName"]);
+				options.withParameter("userLastName", parameters["userInformation"]["lastName"]);
+
+				var operation = new ExecuteAggregationServiceOperation(dssServicePermId, options);
+				mainController.openbisV3.executeOperations([operation], new AsynchronousOperationExecutionOptions()).done(function(results) {
+					callbackFunction(results.executionId.permId);
+				});
+			});
+	};
+
+	//
+	// Gets submission types
+	//
+	this.listSubmissionTypes = function(callbackFunction) {
+		this.customELNApi({
+			"method": "getSubmissionTypes",
+		}, callbackFunction, "rc-exports-api");
+	};
 	
 	//
 	// Metadata Related Functions
@@ -659,7 +717,6 @@ function ServerFacade(openbisServer) {
     this._callPasswordResetService = function(parameters, callbackFunction) {
         var _this = this;
         this.getOpenbisV3(function(openbisV3) {
-
             openbisV3.loginAsAnonymousUser().done(function(sessionToken) {
                 _this.openbisServer._internal.sessionToken = sessionToken;
 
@@ -682,6 +739,7 @@ function ServerFacade(openbisServer) {
     }
 
  	this.customELNApi = function(parameters, callbackFunction, service) {
+		var _this = this;
  		if(!service) {
  			service = "eln-lims-api";
  		}
@@ -693,24 +751,28 @@ function ServerFacade(openbisServer) {
  		
  		var dataStoreCode = profile.getDefaultDataStoreCode();
  		this.openbisServer.createReportFromAggregationService(dataStoreCode, service, parameters, function(data) {
- 			var error = null;
- 			var result = {};
- 			if(data.error) { //Error Case 1
- 				error = data.error.message;
- 			} else if (data.result.columns[1].title === "Error") { //Error Case 2
- 				error = data.result.rows[0][1].value;
- 			} else if (data.result.columns[0].title === "STATUS" && data.result.rows[0][0].value === "OK") { //Success Case
- 				result.message = data.result.rows[0][1].value;
- 				result.data = data.result.rows[0][2].value;
- 				if(result.data) {
- 					result.data = JSON.parse(result.data);
- 				}
- 			} else {
- 				error = "Unknown Error.";
- 			}
- 			callbackFunction(error, result);
+			_this.customELNApiCallbackHandler(data, callbackFunction);
  		});
-	}
+	};
+
+	this.customELNApiCallbackHandler = function(data, callbackFunction) {
+		var error = null;
+		var result = {};
+		if (data && data.error) { //Error Case 1
+			error = data.error.message;
+		} else if (data && data.result.columns[1].title === "Error") { //Error Case 2
+			error = data.result.rows[0][1].value;
+		} else if (data && data.result.columns[0].title === "STATUS" && data.result.rows[0][0].value === "OK") { //Success Case
+			result.message = data.result.rows[0][1].value;
+			result.data = data.result.rows[0][2].value;
+			if(result.data) {
+				result.data = JSON.parse(result.data);
+			}
+		} else {
+			error = "Unknown Error.";
+		}
+		callbackFunction(error, result);
+	};
 
 	this.customELNASAPI = function(parameters, callbackFunction) {
 		this.customASService(parameters, callbackFunction, "as-eln-lims-api");
@@ -2563,10 +2625,10 @@ function ServerFacade(openbisServer) {
 
 	this.getSessionInformation = function(callbackFunction) {
 		mainController.openbisV3.getSessionInformation().done(function(sessionInfo) {
-                callbackFunction(sessionInfo);
+			callbackFunction(sessionInfo);
         }).fail(function(result) {
-				Util.showFailedServerCallError(result);
-				callbackFunction(false);
+			Util.showFailedServerCallError(result);
+			callbackFunction(false);
 		});
 	}
 
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/util/FormUtil.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/util/FormUtil.js
index e501e670936..9658d70d42f 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/util/FormUtil.js
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/util/FormUtil.js
@@ -322,25 +322,28 @@ var FormUtil = new function() {
 		Select2Manager.add($dropdown);
 		return $dropdown;
 	}
-	
-	this.getPlainDropdown = function(mapVals, placeHolder) {
 
-		var $component = $("<select>", {class : 'form-control'});
-		if(placeHolder) {
-			$component.append($("<option>").attr('value', '').attr('selected', '').attr('disabled', '').text(placeHolder));
-		}
-		for(var mIdx = 0; mIdx < mapVals.length; mIdx++) {
+	this.setValuesToComponent = function ($component, mapVals) {
+		for (var mIdx = 0; mIdx < mapVals.length; mIdx++) {
 			var $option = $("<option>").attr('value', mapVals[mIdx].value).text(mapVals[mIdx].label);
-			if(mapVals[mIdx].disabled) {
+			if (mapVals[mIdx].disabled) {
 				$option.attr('disabled', '');
 			}
-			if(mapVals[mIdx].selected) {
+			if (mapVals[mIdx].selected) {
 				$option.attr('selected', '');
 			}
 			$component.append($option);
 		}
+	};
+
+	this.getPlainDropdown = function(mapVals, placeHolder) {
+		var $component = $("<select>", {class : 'form-control'});
+		if (placeHolder) {
+			$component.append($("<option>").attr('value', '').attr('selected', '').attr('disabled', '').text(placeHolder));
+		}
+		this.setValuesToComponent($component, mapVals);
 		return $component;
-	}
+	};
 	
 	this.getDataSetsDropDown = function(code, dataSetTypes) {
 		var $component = $("<select>", { class : 'form-control ' });
@@ -350,11 +353,11 @@ var FormUtil = new function() {
 		
 		$component.append($("<option>").attr('value', '').attr('selected', '').attr('disabled', '').text('Select a dataset type'));
 		
-		for(var i = 0; i < dataSetTypes.length; i++) {
+		for (var i = 0; i < dataSetTypes.length; i++) {
 			var datasetType = dataSetTypes[i];
 			var label = Util.getDisplayNameFromCode(datasetType.code);
 			var description = Util.getEmptyIfNull(datasetType.description);
-			if(description !== "") {
+			if (description !== "") {
 				label += " (" + description + ")";
 			}
 			
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/Export/ExportTreeView.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/Export/ExportTreeView.js
index f09bd1a1cf9..7bbbd3de8e2 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/Export/ExportTreeView.js
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/Export/ExportTreeView.js
@@ -39,7 +39,7 @@ function ExportTreeView(exportTreeController, exportTreeModel) {
                 'onClick': '$("form[name=\'exportTreeForm\']").submit()' });
         $header.append($exportButton);
 
-		var $infoBox = FormUtil.getInfoBox("You can select any parts of the accesible openBIS structure to export:", [
+		var $infoBox = FormUtil.getInfoBox("You can select any parts of the accessible openBIS structure to export:", [
 		                                   "If you select a tree node and do not expand it, everything below this node will be exported by default.",
 		                                   "To export selectively only parts of a tree, open the nodes and select what to export."
 		]);
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ResearchCollectionExport/ResearchCollectionExportController.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ResearchCollectionExport/ResearchCollectionExportController.js
new file mode 100644
index 00000000000..514b6a15d2d
--- /dev/null
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ResearchCollectionExport/ResearchCollectionExportController.js
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2011 ETH Zuerich, CISD
+ *
+ * Licensed 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.
+ */
+
+function ResearchCollectionExportController(parentController) {
+    var researchCollectionExportModel = new ResearchCollectionExportModel();
+    var researchCollectionExportView = new ResearchCollectionExportView(this, researchCollectionExportModel);
+
+    this.init = function(views) {
+        researchCollectionExportView.repaint(views);
+    };
+
+    this.initialiseSubmissionTypesDropdown = function(callback) {
+        Util.blockUI();
+        mainController.serverFacade.listSubmissionTypes(function(error, result) {
+            Util.unblockUI();
+            if (error) {
+                Util.showError(error);
+            } else {
+                researchCollectionExportModel.submissionTypes = result.data.map(function (resultItem) {
+                    return {
+                        value: resultItem.url,
+                        label: resultItem.title
+                    };
+                });
+                researchCollectionExportView.refreshSubmissionTypeDropdown();
+            }
+        });
+    };
+
+    this.exportSelected = function() {
+        var _this = this;
+        var selectedNodes = $(researchCollectionExportModel.tree).fancytree('getTree').getSelectedNodes();
+
+        var selectedOption = researchCollectionExportView.$submissionTypeDropdown.find(":selected");
+        var submissionUrl = selectedOption.val();
+        var submissionType = selectedOption.text();
+
+        var toExport = [];
+        for (var eIdx = 0; eIdx < selectedNodes.length; eIdx++) {
+            var node = selectedNodes[eIdx];
+            toExport.push({type: node.data.entityType, permId: node.key, expand: !node.expanded});
+        }
+
+        if (toExport.length === 0) {
+            Util.showInfo('First select something to export.');
+        } else if (!submissionUrl) {
+            Util.showInfo('First select submission type.');
+        } else {
+            Util.blockUI();
+            this.getUserInformation(function(userInformation) {
+                mainController.serverFacade.exportRc(toExport, true, false, submissionUrl, submissionType, userInformation,
+                        function(operationExecutionPermId) {
+                            _this.waitForOpExecutionResponse(operationExecutionPermId, function(error, result) {
+                                Util.unblockUI();
+                                if (result && result.data && result.data.url) {
+                                    var win = window.open(result.data.url, '_blank');
+                                    win.focus();
+                                    mainController.refreshView();
+                                } else {
+                                    if (error) {
+                                        Util.showError(error);
+                                    } else {
+                                        Util.showError('Returned result format is not correct.');
+                                    }
+                                }
+                            });
+                        });
+            });
+        }
+    };
+
+    this.waitForOpExecutionResponse = function(operationExecutionPermIdString, callbackFunction) {
+        var _this = this;
+        require(["as/dto/operation/id/OperationExecutionPermId",
+                "as/dto/operation/fetchoptions/OperationExecutionFetchOptions"],
+            function(OperationExecutionPermId, OperationExecutionFetchOptions) {
+                var operationExecutionPermId = new OperationExecutionPermId(operationExecutionPermIdString);
+                var fetchOptions = new OperationExecutionFetchOptions();
+                var fetchOptionsDetails = fetchOptions.withDetails();
+                fetchOptionsDetails.withResults();
+                fetchOptionsDetails.withError();
+                mainController.openbisV3.getOperationExecutions([operationExecutionPermId], fetchOptions).done(function(results) {
+                    var result = results[operationExecutionPermIdString];
+                    var v2Result = null;
+                    if (result && result.details && result.details.results) {
+                        v2Result = result.details.results[0];
+                    }
+
+                    if (result && result.state === 'FINISHED') {
+                        mainController.serverFacade.customELNApiCallbackHandler(v2Result, callbackFunction);
+                    } else if (!result || result.state === 'FAILED') {
+                        mainController.serverFacade.customELNApiCallbackHandler(v2Result, callbackFunction);
+                    } else {
+                        setTimeout(function() {
+                            _this.waitForOpExecutionResponse(operationExecutionPermIdString, callbackFunction);
+                        }, 3000);
+                    }
+                });
+            });
+    };
+
+    this.getUserInformation = function(callback) {
+        var userId = mainController.serverFacade.getUserId();
+        mainController.serverFacade.getSessionInformation(function(sessionInfo) {
+            var userInformation = {
+                firstName: sessionInfo.person.firstName,
+                lastName: sessionInfo.person.lastName,
+                email: sessionInfo.person.email,
+                id: userId,
+            };
+            callback(userInformation);
+        });
+    };
+}
\ No newline at end of file
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ResearchCollectionExport/ResearchCollectionExportModel.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ResearchCollectionExport/ResearchCollectionExportModel.js
new file mode 100644
index 00000000000..7099e83d875
--- /dev/null
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ResearchCollectionExport/ResearchCollectionExportModel.js
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2011 ETH Zuerich, CISD
+ *
+ * Licensed 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.
+ */
+
+function ResearchCollectionExportModel() {
+    this.submissionTypes = [];
+
+    this.addSubmissionType = function(submissionType) {
+        submissionTypes.push(submissionType);
+    };
+}
\ No newline at end of file
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ResearchCollectionExport/ResearchCollectionExportView.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ResearchCollectionExport/ResearchCollectionExportView.js
new file mode 100644
index 00000000000..7945f200cb3
--- /dev/null
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/ResearchCollectionExport/ResearchCollectionExportView.js
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2011 ETH Zuerich, CISD
+ *
+ * Licensed 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.
+ */
+
+function ResearchCollectionExportView(researchCollectionExportController, researchCollectionExportModel) {
+    var exportTreeView = new ExportTreeView(researchCollectionExportController, researchCollectionExportModel);
+
+    this.repaint = function(views) {
+        researchCollectionExportController.initialiseSubmissionTypesDropdown();
+
+        var $header = views.header;
+        var $container = views.content;
+
+        var $form = $("<div>");
+        var $formColumn = $("<form>", {
+            'name': 'rcExportForm',
+            'role': 'form',
+            'action': 'javascript:void(0);',
+            'onsubmit': 'mainController.currentView.exportSelected();'
+        });
+        $form.append($formColumn);
+
+        var $infoBox = FormUtil.getInfoBox('You can select any parts of the accessible openBIS structure to export:', [
+            'If you select a tree node and do not expand it, everything below this node will be exported by default.',
+            'To export selectively only parts of a tree, open the nodes and select what to export.'
+        ]);
+        $infoBox.css('border', 'none');
+        $container.append($infoBox);
+
+        var $tree = $('<div>', { 'id' : 'exportsTree' });
+        $formColumn.append($('<br>'));
+        $formColumn.append(FormUtil.getBox().append($tree));
+
+        $container.append($form);
+
+        this.paintSubmissionTypeDropdown($container);
+
+        researchCollectionExportModel.tree = TreeUtil.getCompleteTree($tree);
+
+        var $formTitle = $('<h2>').append('Research Collection Export Builder');
+        $header.append($formTitle);
+
+        var $exportButton = $('<input>', { 'type': 'submit', 'class': 'btn btn-primary', 'value': 'Export Selected',
+                'onClick': '$("form[name=\'rcExportForm\']").submit()'});
+        $header.append($exportButton);
+    };
+
+    this.paintSubmissionTypeDropdown = function($container) {
+        this.$submissionTypeDropdown = this.getSubmissionTypeDropdown();
+        var entityTypeDropdownFormGroup = FormUtil.getFieldForComponentWithLabel(this.$submissionTypeDropdown, 'Submission Type', null, true);
+        entityTypeDropdownFormGroup.css('width', '50%');
+        $container.append(entityTypeDropdownFormGroup);
+    };
+
+    this.getSubmissionTypeDropdown = function() {
+        return FormUtil.getDropdown(researchCollectionExportModel.submissionTypes, 'Select a submission type');
+    };
+
+    this.refreshSubmissionTypeDropdown = function() {
+        FormUtil.setValuesToComponent(this.$submissionTypeDropdown, researchCollectionExportModel.submissionTypes);
+    }
+
+}
\ No newline at end of file
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/SideMenu/SideMenuWidgetView.js b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/SideMenu/SideMenuWidgetView.js
index da2bc285601..41e50655fc3 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/SideMenu/SideMenuWidgetView.js
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/as/webapps/eln-lims/html/js/views/SideMenu/SideMenuWidgetView.js
@@ -267,7 +267,14 @@ function SideMenuWidgetView(sideMenuWidgetController, sideMenuWidgetModel) {
         
         if(profile.mainMenu.showExports) {
             var exportBuilderLink = _this.getLinkForNode("Export Builder", "EXPORT_BUILDER", "showExportTreePage", null);
-            treeModelUtils.push({ displayName: "Export Builder", title : exportBuilderLink, entityType: "EXPORT_BUILDER", key : "EXPORT_BUILDER", folder : false, lazy : false, view : "showExportTreePage", icon : "glyphicon glyphicon-export" });
+            treeModelUtils.push({ displayName: "Export Builder", title : exportBuilderLink, entityType: "EXPORT_BUILDER", key : "EXPORT_BUILDER",
+                    folder : false, lazy : false, view : "showExportTreePage", icon : "glyphicon glyphicon-export" });
+
+            var researchCollectionExportBuilderLink = _this.getLinkForNode("Research Collection Export Builder", "RESEARCH_COLLECTION_EXPORT_BUILDER",
+                    "showResearchCollectionExportPage", null);
+            treeModelUtils.push({ displayName: "Research Collection Export Builder", title: researchCollectionExportBuilderLink,
+                    entityType: "RESEARCH_COLLECTION_EXPORT_BUILDER", key: "RESEARCH_COLLECTION_EXPORT_BUILDER", folder: false, lazy: false,
+                    view: "showResearchCollectionExportPage" });
         }
         
         if(profile.mainMenu.showStorageManager) {
@@ -811,6 +818,7 @@ function SideMenuWidgetView(sideMenuWidgetController, sideMenuWidgetModel) {
             stock.setExpanded(true);
         }
         
+        setCustomIcon($tree, "RESEARCH_COLLECTION_EXPORT_BUILDER", "./img/research-collection-icon.png");
         setCustomIcon($tree, "JUPYTER_WORKSPACE", "./img/jupyter-icon.png");
         setCustomIcon($tree, "NEW_JUPYTER_NOTEBOOK", "./img/jupyter-icon.png");
     }
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/exports-api/exports-api.py b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/exports-api/exportsApi.py
similarity index 80%
rename from openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/exports-api/exports-api.py
rename to openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/exports-api/exportsApi.py
index 1494b24e95d..932c0fa3023 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/exports-api/exports-api.py
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/exports-api/exportsApi.py
@@ -13,42 +13,28 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-from collections import deque
-import time
 import jarray
-import threading
-
-#Java Core
-from java.io import ByteArrayInputStream
-from org.apache.commons.io import IOUtils
-from org.apache.commons.io import FileUtils
-
-from java.lang import String
-from java.lang import StringBuilder
-
+# To obtain the openBIS URL
+from ch.systemsx.cisd.openbis.dss.generic.server import DataStoreServer;
 from ch.systemsx.cisd.openbis.generic.client.web.client.exception import UserFailureException
-
-#Zip Format
+from collections import deque
+# Zip Format
 from java.io import File;
 from java.io import FileInputStream;
-from java.io import FileNotFoundException;
 from java.io import FileOutputStream;
+from java.lang import String
+from java.lang import StringBuilder
 from java.util.zip import ZipEntry;
 from java.util.zip import ZipOutputStream;
-from ch.systemsx.cisd.common.mail import EMailAddress;
+from org.apache.commons.io import FileUtils
+# Java Core
+from org.apache.commons.io import IOUtils
 
-#To obtain the openBIS URL
-from ch.systemsx.cisd.openbis.dss.generic.server import DataStoreServer;
-from ch.systemsx.cisd.openbis.dss.generic.server import SessionTokenManager
-from ch.systemsx.cisd.openbis.generic.shared.api.v1.dto import SearchCriteria
 OPENBISURL = DataStoreServer.getConfigParameters().getServerURL() + "/openbis/openbis";
 V3_DSS_BEAN = "data-store-server_INTERNAL";
 
 #V3 API - Metadata
-from ch.systemsx.cisd.common.spring import HttpInvokerUtils;
-from ch.ethz.sis.openbis.generic.asapi.v3 import IApplicationServerApi;
 
-from ch.ethz.sis.openbis.generic.asapi.v3.dto.property import DataType;
 from HTMLParser import HTMLParser
 
 from ch.ethz.sis.openbis.generic.asapi.v3.dto.space.search import SpaceSearchCriteria;
@@ -75,7 +61,6 @@ from ch.ethz.sis.openbis.generic.asapi.v3.dto.dataset.fetchoptions import DataSe
 from ch.ethz.sis.openbis.generic.asapi.v3.dto.property import DataType
 
 #V3 API - Files
-from ch.ethz.sis.openbis.generic.dssapi.v3 import IDataStoreServerApi;
 from ch.ethz.sis.openbis.generic.dssapi.v3.dto.datasetfile.search import DataSetFileSearchCriteria;
 from ch.ethz.sis.openbis.generic.dssapi.v3.dto.datasetfile.fetchoptions import DataSetFileFetchOptions;
 from ch.systemsx.cisd.openbis.dss.generic.shared import ServiceProvider;
@@ -94,7 +79,7 @@ from ch.systemsx.cisd.openbis.dss.client.api.v1 import DssComponentFactory
 #Logging
 from ch.systemsx.cisd.common.logging import LogCategory;
 from org.apache.log4j import Logger;
-operationLog = Logger.getLogger(str(LogCategory.OPERATION) + ".exports-api.py");
+operationLog = Logger.getLogger(str(LogCategory.OPERATION) + ".exportsApi.py");
 
 #AVI API - DTO
 from ch.ethz.sis.openbis.generic.asapi.v3.dto.project import Project
@@ -107,9 +92,6 @@ from ch.ethz.sis import DOCXBuilder
 
 #Images export for word
 from org.jsoup import Jsoup;
-from org.jsoup.nodes import Document;
-from org.jsoup.nodes import Element;
-from org.jsoup.select import Elements;
 
 class MLStripper(HTMLParser):
     def __init__(self):
@@ -125,32 +107,23 @@ def strip_tags(html):
     s.feed(html)
     return s.get_data()
 
-def process(tr, params, tableBuilder):
-    method = params.get("method");
-    isOk = False;
-    result = None;
-    
-    # Set user using the service
-    
-    tr.setUserId(userId);
-    if method == "exportAll":
-        isOk = expandAndexport(tr, params);
 
+def displayResult(isOk, tableBuilder, result=None):
     if isOk:
         tableBuilder.addHeader("STATUS");
         tableBuilder.addHeader("MESSAGE");
         tableBuilder.addHeader("RESULT");
         row = tableBuilder.addRow();
-        row.setCell("STATUS","OK");
+        row.setCell("STATUS", "OK");
         row.setCell("MESSAGE", "Operation Successful");
         row.setCell("RESULT", result);
-    else :
+    else:
         tableBuilder.addHeader("STATUS");
         tableBuilder.addHeader("MESSAGE");
         row = tableBuilder.addRow();
-        row.setCell("STATUS","FAIL");
+        row.setCell("STATUS", "FAIL");
         row.setCell("MESSAGE", "Operation Failed");
-        
+
 
 def addToExportWithoutRepeating(entitiesToExport, entityFound):
     found = False;
@@ -161,38 +134,56 @@ def addToExportWithoutRepeating(entitiesToExport, entityFound):
     if not found:
         entitiesToExport.append(entityFound);
 
-def expandAndexport(tr, params):
-    #Services used during the export process
-    # TO-DO Login on the services as ETL server but on behalf of the user that makes the call
+
+def validateDataSize(entitiesToExport, tr):
+    limitDataSizeInMegabytes = getConfigurationProperty(tr, 'limit-data-size-megabytes')
+    if limitDataSizeInMegabytes is None:
+        limitDataSizeInMegabytes = 500;
+    else:
+        limitDataSizeInMegabytes = int(limitDataSizeInMegabytes);
+    limitDataSizeInBytes = 1000000 * limitDataSizeInMegabytes;
+    estimatedSizeInBytes = 0;
+    for entityToExport in entitiesToExport:
+        if entityToExport["type"] == "FILE" and entityToExport["isDirectory"] == False:
+            estimatedSizeInBytes += entityToExport["length"];
+        elif entityToExport["type"] != "FILE":
+            estimatedSizeInBytes += 12000;  # AVG File Metadata size
+    estimatedSizeInMegabytes = estimatedSizeInBytes / 1000000;
+    operationLog.info(
+        "Size Limit check - limitDataSizeInBytes: " + str(limitDataSizeInBytes) + " > " + " estimatedSizeInBytes: " + str(estimatedSizeInBytes));
+    if estimatedSizeInBytes > limitDataSizeInBytes:
+        raise UserFailureException("The selected data is " + str(estimatedSizeInMegabytes) + " MB that is bigger than the configured limit of " + str(
+            limitDataSizeInMegabytes) + " MB");
+
+
+def findEntitiesToExport(params):
     sessionToken = params.get("sessionToken");
     v3 = ServiceProvider.getV3ApplicationService();
     v3d = ServiceProvider.getApplicationContext().getBean(V3_DSS_BEAN);
-    mailClient = tr.getGlobalState().getMailClient();
     metadataOnly = params.get("metadataOnly");
-    entitiesToExport = [];
     entitiesToExpand = deque([]);
-        
+    entitiesToExport = [];
     entities = params.get("entities");
-    includeRoot = params.get("includeRoot");
-    userEmail = v3.getSessionInformation(sessionToken).getPerson().getEmail();
+
     for entity in entities:
         entityAsPythonMap = { "type" : entity.get("type"), "permId" : entity.get("permId"), "expand" : entity.get("expand") };
-        addToExportWithoutRepeating(entitiesToExport, entityAsPythonMap);
+        entitiesToExport.append(entityAsPythonMap);
         if entity.get("expand"):
             entitiesToExpand.append(entityAsPythonMap);
-    
+
+    operationLog.info("Found %d entities to expand." % len(entitiesToExpand))
     while entitiesToExpand:
         entityToExpand = entitiesToExpand.popleft();
-        type =  entityToExpand["type"];
+        type = entityToExpand["type"];
         permId = entityToExpand["permId"];
         operationLog.info("Expanding type: " + str(type) + " permId: " + str(permId));
-        
+
         if type == "ROOT":
             criteria = SpaceSearchCriteria();
             results = v3.searchSpaces(sessionToken, criteria, SpaceFetchOptions());
             operationLog.info("Found: " + str(results.getTotalCount()) + " spaces");
             for space in results.getObjects():
-                entityFound = { "type" : "SPACE", "permId" : space.getCode() };
+                entityFound = {"type": "SPACE", "permId": space.getCode(), "registrationDate": space.getRegistrationDate()};
                 addToExportWithoutRepeating(entitiesToExport, entityFound);
                 entitiesToExpand.append(entityFound);
         if type == "SPACE":
@@ -201,7 +192,7 @@ def expandAndexport(tr, params):
             results = v3.searchProjects(sessionToken, criteria, ProjectFetchOptions());
             operationLog.info("Found: " + str(results.getTotalCount()) + " projects");
             for project in results.getObjects():
-                entityFound = { "type" : "PROJECT", "permId" : project.getPermId().getPermId() };
+                entityFound = {"type": "PROJECT", "permId": project.getPermId().getPermId(), "registrationDate": project.getRegistrationDate()};
                 addToExportWithoutRepeating(entitiesToExport, entityFound);
                 entitiesToExpand.append(entityFound);
         if type == "PROJECT":
@@ -210,7 +201,8 @@ def expandAndexport(tr, params):
             results = v3.searchExperiments(sessionToken, criteria, ExperimentFetchOptions());
             operationLog.info("Found: " + str(results.getTotalCount()) + " experiments");
             for experiment in results.getObjects():
-                entityFound = { "type" : "EXPERIMENT", "permId" : experiment.getPermId().getPermId() };
+                entityFound = {"type": "EXPERIMENT", "permId": experiment.getPermId().getPermId(),
+                               "registrationDate": experiment.getRegistrationDate()};
                 addToExportWithoutRepeating(entitiesToExport, entityFound);
                 entitiesToExpand.append(entityFound);
         if type == "EXPERIMENT":
@@ -218,29 +210,33 @@ def expandAndexport(tr, params):
             criteria.withExperiment().withPermId().thatEquals(permId);
             results = v3.searchSamples(sessionToken, criteria, SampleFetchOptions());
             operationLog.info("Found: " + str(results.getTotalCount()) + " samples");
-            
+
             dCriteria = DataSetSearchCriteria();
             dCriteria.withExperiment().withPermId().thatEquals(permId);
             dCriteria.withoutSample();
-            dResults = v3.searchDataSets(sessionToken, dCriteria, DataSetFetchOptions());
+            fetchOptions = DataSetFetchOptions()
+            fetchOptions.withDataStore()
+            dResults = v3.searchDataSets(sessionToken, dCriteria, fetchOptions);
             operationLog.info("Found: " + str(dResults.getTotalCount()) + " datasets");
             for dataset in dResults.getObjects():
-                entityFound = { "type" : "DATASET", "permId" : dataset.getPermId().getPermId() };
+                entityFound = {"type": "DATASET", "permId": dataset.getPermId().getPermId(), "registrationDate": dataset.getRegistrationDate()};
                 addToExportWithoutRepeating(entitiesToExport, entityFound);
                 entitiesToExpand.append(entityFound);
-            
+
             operationLog.info("Found: " + str(results.getTotalCount()) + " samples");
             for sample in results.getObjects():
-                entityFound = { "type" : "SAMPLE", "permId" : sample.getPermId().getPermId() };
+                entityFound = {"type": "SAMPLE", "permId": sample.getPermId().getPermId(), "registrationDate": sample.getRegistrationDate()};
                 addToExportWithoutRepeating(entitiesToExport, entityFound);
                 entitiesToExpand.append(entityFound);
         if type == "SAMPLE":
             criteria = DataSetSearchCriteria();
             criteria.withSample().withPermId().thatEquals(permId);
-            results = v3.searchDataSets(sessionToken, criteria, DataSetFetchOptions());
+            fetchOptions = DataSetFetchOptions()
+            fetchOptions.withDataStore()
+            results = v3.searchDataSets(sessionToken, criteria, fetchOptions);
             operationLog.info("Found: " + str(results.getTotalCount()) + " datasets");
             for dataset in results.getObjects():
-                entityFound = { "type" : "DATASET", "permId" : dataset.getPermId().getPermId() };
+                entityFound = {"type": "DATASET", "permId": dataset.getPermId().getPermId(), "registrationDate": dataset.getRegistrationDate()};
                 addToExportWithoutRepeating(entitiesToExport, entityFound);
                 entitiesToExpand.append(entityFound);
         if type == "DATASET" and not metadataOnly:
@@ -249,70 +245,51 @@ def expandAndexport(tr, params):
             results = v3d.searchFiles(sessionToken, criteria, DataSetFileFetchOptions());
             operationLog.info("Found: " + str(results.getTotalCount()) + " files");
             for file in results.getObjects():
-                entityFound = { "type" : "FILE", "permId" : permId, "path" : file.getPath(), "isDirectory" : file.isDirectory(), "length" : file.getFileLength() };
+                entityFound = {"type": "FILE", "permId": permId, "path": file.getPath(), "isDirectory": file.isDirectory(),
+                               "length": file.getFileLength()};
                 addToExportWithoutRepeating(entitiesToExport, entityFound);
-                    
-    
-    limitDataSizeInMegabytes = getConfigurationProperty(tr, 'limit-data-size-megabytes');
-    if limitDataSizeInMegabytes is None:
-        limitDataSizeInMegabytes = 500;
-    else:
-        limitDataSizeInMegabytes = int(limitDataSizeInMegabytes);
-    
-    limitDataSizeInBytes = 1000000 * limitDataSizeInMegabytes;
-    estimatedSizeInBytes = 0;
-    for entityToExport in entitiesToExport:
-        if entityToExport["type"] == "FILE" and entityToExport["isDirectory"] == False:
-            estimatedSizeInBytes += entityToExport["length"];
-        elif entityToExport["type"] != "FILE":
-            estimatedSizeInBytes += 12000; #AVG File Metadata size
-    estimatedSizeInMegabytes = estimatedSizeInBytes / 1000000;
-    
-    operationLog.info("Size Limit check - limitDataSizeInBytes: " + str(limitDataSizeInBytes) + " > " + " estimatedSizeInBytes: " + str(estimatedSizeInBytes));
-    if estimatedSizeInBytes > limitDataSizeInBytes:
-        raise UserFailureException("The selected data is " + str(estimatedSizeInMegabytes) + " MB that is bigger than the configured limit of " + str(limitDataSizeInMegabytes) + " MB");
-    
-    operationLog.info("Found " + str(len(entitiesToExport)) + " entities to export, export thread will start");
-    thread = threading.Thread(target=export, args=(sessionToken, entitiesToExport, includeRoot, userEmail, mailClient));
-    thread.daemon = True;
-    thread.start();
-    
-    return True;
+    return entitiesToExport
+
+
+# Removes temporal folder and zip
+def cleanUp(tempDirPath, tempZipFilePath):
+    FileUtils.forceDelete(File(tempDirPath));
+    FileUtils.forceDelete(File(tempZipFilePath));
+
+
+# Generates ZIP file and stores it in workspace
+def generateZipFile(entities, includeRoot, sessionToken, tempDirPath, tempZipFilePath):
+    # Create Zip File
+    fos = FileOutputStream(tempZipFilePath);
+    zos = ZipOutputStream(fos);
+
+    generateFilesInZip(zos, entities, includeRoot, sessionToken, tempDirPath)
 
-def export(sessionToken, entities, includeRoot, userEmail, mailClient):
-    #Services used during the export process
+    zos.close();
+    fos.close();
+
+
+def generateFilesInZip(zos, entities, includeRoot, sessionToken, tempDirPath):
+    # Services used during the export process
     v3 = ServiceProvider.getV3ApplicationService();
     v3d = ServiceProvider.getApplicationContext().getBean(V3_DSS_BEAN);
-    dssComponent = DssComponentFactory.tryCreate(sessionToken, OPENBISURL);
-    
     objectCache = {};
     objectMapper = GenericObjectMapper();
     objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
-    
-    #Create temporal folder
-    tempDirName = "export_" + str(time.time());
-    tempDirPathFile = File.createTempFile(tempDirName, None);
-    tempDirPathFile.delete();
-    tempDirPathFile.mkdir();
-    tempDirPath = tempDirPathFile.getCanonicalPath();
-    #To avoid empty directories on the zip file, it makes the first found entity the base directory
+    # To avoid empty directories on the zip file, it makes the first found entity the base directory
     baseDirToCut = None;
-    
-    #Create Zip File
-    tempZipFileName = tempDirName + ".zip";
-    tempZipFilePath = tempDirPath + ".zip";
-    fos = FileOutputStream(tempZipFilePath);
-    zos = ZipOutputStream(fos);
-            
+    fileMetadata = []
+    emptyZip = True
+
     for entity in entities:
-        type =  entity["type"];
+        type = entity["type"];
         permId = entity["permId"];
         operationLog.info("exporting type: " + str(type) + " permId: " + str(permId));
         entityObj = None;
         entityFilePath = None;
-        
+
         if type == "SPACE":
-            pass #Do nothing
+            pass  # Do nothing
         if type == "PROJECT":
             criteria = ProjectSearchCriteria();
             criteria.withPermId().thatEquals(permId);
@@ -333,7 +310,8 @@ def export(sessionToken, entities, includeRoot, userEmail, mailClient):
             fetchOps.withProperties();
             fetchOps.withTags();
             entityObj = v3.searchExperiments(sessionToken, criteria, fetchOps).getObjects().get(0);
-            entityFilePath = getFilePath(entityObj.getProject().getSpace().getCode(), entityObj.getProject().getCode(), entityObj.getCode(), None, None);
+            entityFilePath = getFilePath(entityObj.getProject().getSpace().getCode(), entityObj.getProject().getCode(), entityObj.getCode(), None,
+                                         None);
         if type == "SAMPLE":
             criteria = SampleSearchCriteria();
             criteria.withPermId().thatEquals(permId);
@@ -347,7 +325,9 @@ def export(sessionToken, entities, includeRoot, userEmail, mailClient):
             fetchOps.withParents().withProperties();
             fetchOps.withChildren().withProperties();
             entityObj = v3.searchSamples(sessionToken, criteria, fetchOps).getObjects().get(0);
-            entityFilePath = getFilePath(entityObj.getExperiment().getProject().getSpace().getCode(), entityObj.getExperiment().getProject().getCode(), entityObj.getExperiment().getCode(), entityObj.getCode(), None);
+            entityFilePath = getFilePath(entityObj.getExperiment().getProject().getSpace().getCode(),
+                                         entityObj.getExperiment().getProject().getCode(), entityObj.getExperiment().getCode(), entityObj.getCode(),
+                                         None);
         if type == "DATASET":
             criteria = DataSetSearchCriteria();
             criteria.withPermId().thatEquals(permId);
@@ -362,71 +342,84 @@ def export(sessionToken, entities, includeRoot, userEmail, mailClient):
             fetchOps.withParents().withProperties();
             fetchOps.withChildren().withProperties();
             entityObj = v3.searchDataSets(sessionToken, criteria, fetchOps).getObjects().get(0);
-            
+
             sampleCode = None
-            if(entityObj.getSample() is not None):
+            if (entityObj.getSample() is not None):
                 sampleCode = entityObj.getSample().getCode();
-            
-            entityFilePath = getFilePath(entityObj.getExperiment().getProject().getSpace().getCode(), entityObj.getExperiment().getProject().getCode(), entityObj.getExperiment().getCode(), sampleCode, entityObj.getCode());
+
+            entityFilePath = getFilePath(entityObj.getExperiment().getProject().getSpace().getCode(),
+                                         entityObj.getExperiment().getProject().getCode(), entityObj.getExperiment().getCode(), sampleCode,
+                                         entityObj.getCode());
         if type == "FILE" and not entity["isDirectory"]:
             datasetEntityObj = objectCache[entity["permId"]];
             sampleCode = None
-            if(datasetEntityObj.getSample() is not None):
+            if (datasetEntityObj.getSample() is not None):
                 sampleCode = datasetEntityObj.getSample().getCode();
-            
-            datasetEntityFilePath = getFilePath(datasetEntityObj.getExperiment().getProject().getSpace().getCode(), datasetEntityObj.getExperiment().getProject().getCode(), datasetEntityObj.getExperiment().getCode(), sampleCode, datasetEntityObj.getCode());
+
+            datasetEntityFilePath = getFilePath(datasetEntityObj.getExperiment().getProject().getSpace().getCode(),
+                                                datasetEntityObj.getExperiment().getProject().getCode(), datasetEntityObj.getExperiment().getCode(),
+                                                sampleCode, datasetEntityObj.getCode());
             filePath = datasetEntityFilePath + "/" + entity["path"];
-            
+
             if not includeRoot:
-                filePath = filePath[len(baseDirToCut):] #To avoid empty directories on the zip file, it makes the first found entity the base directory
-            
-            rawFileInputStream = DataSetFileDownloadReader(v3d.downloadFiles(sessionToken, [DataSetFilePermId(DataSetPermId(permId), entity["path"])], DataSetFileDownloadOptions())).read().getInputStream();
+                filePath = filePath[
+                           len(baseDirToCut):]  # To avoid empty directories on the zip file, it makes the first found entity the base directory
+
+            rawFileInputStream = DataSetFileDownloadReader(v3d.downloadFiles(sessionToken, [DataSetFilePermId(DataSetPermId(permId), entity["path"])],
+                                                                             DataSetFileDownloadOptions())).read().getInputStream();
             rawFile = File(tempDirPath + filePath + ".json");
             rawFile.getParentFile().mkdirs();
             IOUtils.copyLarge(rawFileInputStream, FileOutputStream(rawFile));
             addToZipFile(filePath, rawFile, zos);
-        
-        #To avoid empty directories on the zip file, it makes the first found entity the base directory
+            emptyZip = False
+
+        # To avoid empty directories on the zip file, it makes the first found entity the base directory
         if not includeRoot:
             if baseDirToCut is None and entityFilePath is not None:
                 baseDirToCut = entityFilePath[:entityFilePath.rfind('/')];
             if entityFilePath is not None:
                 entityFilePath = entityFilePath[len(baseDirToCut):]
         #
-        
+
         if entityObj is not None:
             objectCache[permId] = entityObj;
-        
-        operationLog.info("--> Entity type: " + type + " permId: " + permId + " obj: " + str(entityObj is not None) + " path: " + str(entityFilePath) + " before files.");
+
+        operationLog.info("--> Entity type: " + type + " permId: " + permId + " obj: " + str(entityObj is not None) + " path: " + str(
+            entityFilePath) + " before files.");
         if entityObj is not None and entityFilePath is not None:
-            #JSON
+            # JSON
             entityJson = String(objectMapper.writeValueAsString(entityObj));
-            addFile(tempDirPath, entityFilePath, "json", entityJson.getBytes(), zos);
-            #TEXT
+            fileMetadatum = addFile(tempDirPath, entityFilePath, "json", entityJson.getBytes(), zos);
+            fileMetadata.append(fileMetadatum)
+            emptyZip = False
+            # TEXT
             entityTXT = String(getTXT(entityObj, v3, sessionToken, False));
-            addFile(tempDirPath, entityFilePath, "txt", entityTXT.getBytes(), zos);
-            #DOCX
+            fileMetadatum = addFile(tempDirPath, entityFilePath, "txt", entityTXT.getBytes(), zos);
+            fileMetadata.append(fileMetadatum)
+            # DOCX
             entityDOCX = getDOCX(entityObj, v3, sessionToken, False);
-            addFile(tempDirPath, entityFilePath, "docx", entityDOCX, zos);
-            #HTML
+            fileMetadatum = addFile(tempDirPath, entityFilePath, "docx", entityDOCX, zos);
+            fileMetadata.append(fileMetadatum)
+            # HTML
             entityHTML = getDOCX(entityObj, v3, sessionToken, True);
-            addFile(tempDirPath, entityFilePath, "html", entityHTML, zos);
+            fileMetadatum = addFile(tempDirPath, entityFilePath, "html", entityHTML, zos);
+            fileMetadata.append(fileMetadatum)
             operationLog.info("--> Entity type: " + type + " permId: " + permId + " post html.");
-            
-    zos.close();
-    fos.close();
-    
-    #Store on workspace to be able to generate a download link
+    if emptyZip:
+        raise IOError('Nothing added to ZIP file.')
+    return fileMetadata
+
+
+def generateDownloadUrl(sessionToken, tempZipFileName, tempZipFilePath):
+    dssComponent = DssComponentFactory.tryCreate(sessionToken, OPENBISURL);
+
+    # Store on workspace to be able to generate a download link
     operationLog.info("Zip file can be found on the temporal directory: " + tempZipFilePath);
     dssComponent.putFileToSessionWorkspace(tempZipFileName, FileInputStream(File(tempZipFilePath)));
     tempZipFileWorkspaceURL = DataStoreServer.getConfigParameters().getDownloadURL() + "/datastore_server/session_workspace_file_download?sessionID=" + sessionToken + "&filePath=" + tempZipFileName;
     operationLog.info("Zip file can be downloaded from the workspace: " + tempZipFileWorkspaceURL);
-    #Send Email
-    sendMail(mailClient, userEmail, tempZipFileWorkspaceURL);
-    #Remove temporal folder and zip
-    FileUtils.forceDelete(File(tempDirPath));
-    FileUtils.forceDelete(File(tempZipFilePath));
-    return True
+    return tempZipFileWorkspaceURL
+
 
 def getDOCX(entityObj, v3, sessionToken, isHTML):
     docxBuilder = DOCXBuilder();
@@ -600,12 +593,25 @@ def getTXT(entityObj, v3, sessionToken, isRichText):
     return txtBuilder.toString();
 
 def addFile(tempDirPath, entityFilePath, extension, fileContent, zos):
-    entityFile = File(tempDirPath + entityFilePath + "." + extension);
+    entityFileNameWithExtension = entityFilePath + "." + extension
+    entityFile = File(tempDirPath + entityFileNameWithExtension);
     entityFile.getParentFile().mkdirs();
     IOUtils.write(fileContent, FileOutputStream(entityFile));
-    addToZipFile(entityFilePath + "." + extension, entityFile, zos);
+    addToZipFile(entityFileNameWithExtension, entityFile, zos);
     FileUtils.forceDelete(entityFile);
 
+    extensionToMimeType = {
+        'json': 'application/json',
+        'txt': 'text/plain',
+        'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+        'html': 'text/html',
+    }
+
+    return {
+        'fileName': entityFileNameWithExtension[1:],
+        'mimeType': extensionToMimeType.get(extension, 'application/octet-stream')
+    }
+
 def getFilePath(spaceCode, projCode, expCode, sampCode, dataCode):
     fileName = "";
     if spaceCode is not None:
@@ -621,27 +627,18 @@ def getFilePath(spaceCode, projCode, expCode, sampCode, dataCode):
     return fileName;
 
 def addToZipFile(path, file, zos):
-        fis = FileInputStream(file);
-        zipEntry = ZipEntry(path[1:]); # Making paths relative to make them compatible with Windows zip implementation
-        zos.putNextEntry(zipEntry);
-
-        bytes = jarray.zeros(1024, "b");
+    fis = FileInputStream(file);
+    zipEntry = ZipEntry(path[1:]); # Making paths relative to make them compatible with Windows zip implementation
+    zos.putNextEntry(zipEntry);
+
+    bytes = jarray.zeros(1024, "b");
+    length = fis.read(bytes);
+    while length >= 0:
+        zos.write(bytes, 0, length);
         length = fis.read(bytes);
-        while length >= 0:
-            zos.write(bytes, 0, length);
-            length = fis.read(bytes);
-        
-        zos.closeEntry();
-        fis.close();
-
-def sendMail(mailClient, userEmail, downloadURL):
-    replyTo = None;
-    fromAddress = None;
-    recipient1 = EMailAddress(userEmail);
-    topic = "Export Ready";
-    message = "Download a zip file with your exported data at: " + downloadURL;
-    mailClient.sendEmailMessage(topic, message, replyTo, fromAddress, recipient1);
-    operationLog.info("--- MAIL ---" + " Recipient: " + userEmail + " Topic: " + topic + " Message: " + message);
+
+    zos.closeEntry();
+    fis.close();
 
 def getConfigurationProperty(transaction, propertyName):
     threadProperties = transaction.getGlobalState().getThreadParameters().getThreadProperties();
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/exports-api/generalExports.py b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/exports-api/generalExports.py
new file mode 100644
index 00000000000..4a5845d984b
--- /dev/null
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/exports-api/generalExports.py
@@ -0,0 +1,87 @@
+#
+# Copyright 2016 ETH Zuerich, Scientific IT Services
+#
+# Licensed 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.
+#
+
+import threading
+import time
+from ch.systemsx.cisd.common.logging import LogCategory
+from ch.systemsx.cisd.common.mail import EMailAddress
+from ch.systemsx.cisd.openbis.dss.generic.shared import ServiceProvider
+from java.io import File
+from org.apache.log4j import Logger
+
+from exportsApi import generateZipFile, cleanUp, displayResult, findEntitiesToExport, validateDataSize, generateDownloadUrl
+
+operationLog = Logger.getLogger(str(LogCategory.OPERATION) + ".generalExports.py")
+
+def process(tr, params, tableBuilder):
+    method = params.get("method")
+    isOk = False
+
+    # Set user using the service
+
+    tr.setUserId(userId)
+    if method == "exportAll":
+        isOk = expandAndExport(tr, params)
+
+    displayResult(isOk, tableBuilder)
+
+def expandAndExport(tr, params):
+    #Services used during the export process
+    # TO-DO Login on the services as ETL server but on behalf of the user that makes the call
+    sessionToken = params.get("sessionToken")
+    v3 = ServiceProvider.getV3ApplicationService()
+    userEmail = v3.getSessionInformation(sessionToken).getPerson().getEmail()
+
+    entitiesToExport = findEntitiesToExport(params)
+    validateDataSize(entitiesToExport, tr)
+
+    mailClient = tr.getGlobalState().getMailClient()
+    includeRoot = params.get("includeRoot")
+
+    operationLog.info("Found " + str(len(entitiesToExport)) + " entities to export, export thread will start")
+    thread = threading.Thread(target=export, args=(sessionToken, entitiesToExport, includeRoot, userEmail, mailClient))
+    thread.daemon = True
+    thread.start()
+
+    return True
+
+def export(sessionToken, entities, includeRoot, userEmail, mailClient):
+    #Create temporal folder
+    tempDirName = "export_" + str(time.time())
+    tempDirPathFile = File.createTempFile(tempDirName, None)
+    tempDirPathFile.delete()
+    tempDirPathFile.mkdir()
+    tempDirPath = tempDirPathFile.getCanonicalPath()
+    tempZipFileName = tempDirName + ".zip"
+    tempZipFilePath = tempDirPath + ".zip"
+
+    generateZipFile(entities, includeRoot, sessionToken, tempDirPath, tempZipFilePath)
+    tempZipFileWorkspaceURL = generateDownloadUrl(sessionToken, tempZipFileName, tempZipFilePath)
+
+    #Send Email
+    sendMail(mailClient, userEmail, tempZipFileWorkspaceURL)
+
+    cleanUp(tempDirPath, tempZipFilePath)
+    return True
+
+def sendMail(mailClient, userEmail, downloadURL):
+    replyTo = None
+    fromAddress = None
+    recipient1 = EMailAddress(userEmail)
+    topic = "Export Ready"
+    message = "Download a zip file with your exported data at: " + downloadURL
+    mailClient.sendEmailMessage(topic, message, replyTo, fromAddress, recipient1)
+    operationLog.info("--- MAIL ---" + " Recipient: " + userEmail + " Topic: " + topic + " Message: " + message)
\ No newline at end of file
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/exports-api/plugin.properties b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/exports-api/plugin.properties
index 223af21c0cf..e44c1b3b476 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/exports-api/plugin.properties
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/exports-api/plugin.properties
@@ -1,4 +1,4 @@
 label = Exports API
 class = ch.systemsx.cisd.openbis.dss.generic.server.plugins.jython.JythonIngestionService
-script-path = exports-api.py
+script-path = generalExports.py
 limit-data-size-megabytes = 10000
\ No newline at end of file
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/rc-exports-api/exportsApi.py b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/rc-exports-api/exportsApi.py
new file mode 120000
index 00000000000..ccb8a49feb3
--- /dev/null
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/rc-exports-api/exportsApi.py
@@ -0,0 +1 @@
+../exports-api/exportsApi.py
\ No newline at end of file
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/rc-exports-api/plugin.properties b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/rc-exports-api/plugin.properties
new file mode 100644
index 00000000000..eabafa539e8
--- /dev/null
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/rc-exports-api/plugin.properties
@@ -0,0 +1,8 @@
+label = Research Collection Exports API
+class = ch.systemsx.cisd.openbis.dss.generic.server.plugins.jython.JythonIngestionService
+script-path = rcExports.py
+limit-data-size-megabytes = 4000
+service-document-url=https://test.research-collection.ethz.ch/swordv2/servicedocument
+realm=SWORD2
+user=youruser
+password=yourpassword
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/rc-exports-api/rcExports.py b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/rc-exports-api/rcExports.py
new file mode 100644
index 00000000000..e354f38dcdd
--- /dev/null
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/rc-exports-api/rcExports.py
@@ -0,0 +1,384 @@
+#
+# Copyright 2016 ETH Zuerich, Scientific IT Services
+#
+# Licensed 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.
+#
+import json
+import os
+import traceback
+import xml.etree.ElementTree as ET
+
+import datetime
+import time
+from ch.systemsx.cisd.common.logging import LogCategory
+from java.io import File
+from java.io import FileOutputStream
+from java.net import URI
+from java.nio.file import Paths
+from java.text import SimpleDateFormat
+from java.util import UUID
+from java.util.zip import ZipOutputStream
+from org.apache.commons.io import FileUtils
+from org.apache.log4j import Logger
+from org.eclipse.jetty.client import HttpClient
+from org.eclipse.jetty.client.util import BasicAuthentication
+from org.eclipse.jetty.http import HttpMethod
+from org.eclipse.jetty.util.ssl import SslContextFactory
+
+from exportsApi import displayResult, findEntitiesToExport, validateDataSize, getConfigurationProperty, generateFilesInZip, addToZipFile, cleanUp
+
+operationLog = Logger.getLogger(str(LogCategory.OPERATION) + '.rcExports.py')
+
+
+def process(tr, params, tableBuilder):
+    method = params.get('method')
+
+    # Set user using the service
+    tr.setUserId(userId)
+
+    if method == 'exportAll':
+        resultUrl = expandAndExport(tr, params)
+        displayResult(resultUrl is not None, tableBuilder, '{"url": "' + resultUrl + '"}' if resultUrl is not None else None)
+    elif method == 'getSubmissionTypes':
+        collectionUrls = getSubmissionTypes(tr)
+        displayResult(collectionUrls is not None, tableBuilder, collectionUrls)
+
+
+def getSubmissionTypes(tr):
+    url = getConfigurationProperty(tr, 'service-document-url')
+
+    httpClient = None
+    try:
+        httpClient = authenticateUserJava(url, tr)
+        httpClient.setFollowRedirects(True)
+        httpClient.start()
+
+        collections = fetchServiceDocument(url, httpClient)
+    finally:
+        if httpClient is not None:
+            httpClient.stop()
+    return collections
+
+
+def expandAndExport(tr, params):
+    entitiesToExport = findEntitiesToExport(params)
+    validateDataSize(entitiesToExport, tr)
+
+    userInformation = {
+        'firstName': params.get('userFirstName'),
+        'lastName': params.get('userLastName'),
+        'email': params.get('userEmail'),
+    }
+
+    operationLog.info('Found ' + str(len(entitiesToExport)) + ' entities to export')
+    return export(entities=entitiesToExport, tr=tr, params=params, userInformation=userInformation)
+
+
+def export(entities, tr, params, userInformation):
+    #Create temporal folder
+    timeNow = time.time()
+
+    exportDirName = 'export_' + str(timeNow)
+    exportDir = File.createTempFile(exportDirName, None)
+    exportDirPath = exportDir.getCanonicalPath()
+    exportDir.delete()
+    exportDir.mkdir()
+
+    contentZipFileName = 'content.zip'
+    contentDirName = 'content_' + str(timeNow)
+    contentDir = File.createTempFile(contentDirName, None, exportDir)
+    contentDirPath = contentDir.getCanonicalPath()
+    contentDir.delete()
+    contentDir.mkdir()
+
+    contentZipFilePath = exportDirPath + '/' + contentZipFileName
+
+    exportZipFilePath = exportDirPath + '.zip'
+    exportZipFileName = exportDirName + '.zip'
+
+    generateInternalZipFile(entities, params, contentDirPath, contentZipFilePath)
+    FileUtils.forceDelete(File(contentDirPath))
+
+    generateExternalZipFile(params=params, exportDirPath=exportDirPath, contentZipFilePath=contentZipFilePath, contentZipFileName=contentZipFileName,
+                            exportZipFileName=exportZipFilePath, userInformation=userInformation, entities=entities)
+    resultUrl = sendToDSpace(params=params, tr=tr, tempZipFileName=exportZipFileName, tempZipFilePath=exportZipFilePath)
+    cleanUp(exportDirPath, exportZipFilePath)
+    return resultUrl
+
+
+def sendToDSpace(params, tr, tempZipFileName, tempZipFilePath):
+    depositUrl = str(params.get('submissionUrl'))
+
+    headers = {
+        'In-Progress': 'true',
+        'Content-Disposition': 'filename=' + tempZipFileName,
+        'Content-Type': 'application/zip',
+        'Content-Length': os.stat(tempZipFilePath).st_size,
+        'Content-Transfer-Encoding': 'binary',
+        'Packaging': 'http://purl.org/net/sword/package/METSDSpaceSIP',
+        'On-Behalf-Of': str(params.get('userId')),
+    }
+
+    httpClient = None
+    try:
+        httpClient = authenticateUserJava(depositUrl, tr)
+
+        httpClient.setFollowRedirects(True)
+        httpClient.start()
+
+        request = httpClient.newRequest(depositUrl)
+        for key, value in headers.iteritems():
+            request.header(key, str(value))
+        response = request.method(HttpMethod.POST).file(Paths.get(tempZipFilePath), 'application/zip').send()
+        status = response.getStatus()
+        if status >= 300:
+            reason = response.getReason()
+            raise ValueError('Unsuccessful response from the server: %s %s' % (status, reason))
+
+        xmlText = response.getContentAsString().encode('utf-8')
+        xmlRoot = ET.fromstring(xmlText)
+        linkElement = xmlRoot.find('xmlns:link[@rel="alternate"]', namespaces=dict(xmlns='http://www.w3.org/2005/Atom'))
+        if linkElement is None:
+            raise ValueError('No redirection URL is found in the response.')
+
+        href = linkElement.attrib['href']
+
+        return href
+    except Exception as e:
+        operationLog.error('Exception at: ' + traceback.format_exc())
+        operationLog.error('Exception: ' + str(e))
+        raise e
+    finally:
+        if httpClient is not None:
+            httpClient.stop()
+
+
+def authenticateUserJava(url, tr):
+    sslContextFactory = SslContextFactory()
+    httpClient = HttpClient(sslContextFactory)
+    uri = URI(url)
+    user = getConfigurationProperty(tr, 'user')
+    password = getConfigurationProperty(tr, 'password')
+    realm = getConfigurationProperty(tr, 'realm')
+    auth = httpClient.getAuthenticationStore()
+    auth.addAuthentication(BasicAuthentication(uri, realm, user, password))
+    return httpClient
+
+
+def fetchServiceDocument(url, httpClient):
+    response = httpClient.newRequest(url).method(HttpMethod.GET).send()
+
+    xmlText = response.getContentAsString().encode('utf-8')
+    xmlRoot = ET.fromstring(xmlText)
+    collections = xmlRoot.findall('./xmlns:workspace/xmlns:collection[@href]', namespaces=dict(xmlns='http://www.w3.org/2007/app'))
+
+    def collectionToDictionaryMapper(collection):
+        return {
+            'title': collection.find('./atom:title', namespaces=dict(atom='http://www.w3.org/2005/Atom')).text,
+            'url': collection.attrib['href'],
+        }
+
+    return json.dumps(map(collectionToDictionaryMapper, collections))
+
+
+def generateInternalZipFile(entities, params, tempDirPath, tempZipFilePath):
+    # Generates ZIP file with selected item for export
+
+    sessionToken = params.get('sessionToken')
+    includeRoot = params.get('includeRoot')
+
+    fos = None
+    zos = None
+    try:
+        fos = FileOutputStream(tempZipFilePath)
+        zos = ZipOutputStream(fos)
+
+        fileMetadata = generateFilesInZip(zos, entities, includeRoot, sessionToken, tempDirPath)
+    finally:
+        if zos is not None:
+            zos.close()
+        if fos is not None:
+            fos.close()
+
+    return fileMetadata
+
+
+def generateExternalZipFile(params, exportDirPath, contentZipFilePath, contentZipFileName, exportZipFileName, userInformation, entities):
+    # Generates ZIP file which will go to the research collection server
+
+    originUrl=params.get('originUrl')
+    submissionType = str(params.get('submissionType'))
+
+    fileMetadata = [
+        {
+            'fileName': contentZipFileName,
+            'mimeType': 'application/zip'
+        }
+    ]
+
+    fos = None
+    zos = None
+    try:
+        fos = FileOutputStream(exportZipFileName)
+        zos = ZipOutputStream(fos)
+
+        addToZipFile(' ' + contentZipFileName, File(contentZipFilePath), zos)
+
+        generateXML(zipOutputStream=zos, fileMetadata=fileMetadata, exportDirPath=exportDirPath, submissionType=submissionType,
+                    userInformation=userInformation, entities=entities, originUrl=originUrl)
+    except Exception as e:
+        operationLog.error('Exception at: ' + traceback.format_exc())
+        operationLog.error('Exception: ' + str(e))
+        raise e
+    finally:
+        if zos is not None:
+            zos.close()
+        if fos is not None:
+            fos.close()
+
+
+def generateXML(zipOutputStream, fileMetadata, exportDirPath, submissionType, userInformation, entities, originUrl):
+    ns = {
+        'mets': 'http://www.loc.gov/METS/',
+        'xlink': 'http://www.w3.org/1999/xlink',
+        'dim': 'http://www.dspace.org/xmlns/dspace/dim'
+    }
+
+    entityPermIds = map(lambda entity: entity['permId'], entities)
+    permIdsStr = reduce(lambda str, permId: str + ',' + permId, entityPermIds)
+
+    withRegistrationDates = filter(lambda entity: 'registrationDate' in entity, entities)
+    registrationDates = map(lambda entity: entity['registrationDate'], withRegistrationDates)
+
+    if len(registrationDates) > 0:
+        minDateStr = javaDateToStr(min(registrationDates, key=lambda date: date.getTime()))
+        maxDateStr = javaDateToStr(max(registrationDates, key=lambda date: date.getTime()))
+    else:
+        minDateStr = None
+        maxDateStr = None
+
+    metsNS = ns['mets']
+    xlinkNS = ns['xlink']
+    dimNS = ns['dim']
+    ET.register_namespace('mets', metsNS)
+    ET.register_namespace('xlink', xlinkNS)
+    ET.register_namespace('dim', dimNS)
+
+    root = ET.Element(ET.QName(metsNS, 'METS'))
+    root.set('LABEL', 'DSpace Item')
+    root.set('ID', UUID.randomUUID().toString())
+
+    dmdSec = ET.SubElement(root, ET.QName(metsNS, 'dmdSec'))
+    dmdSec.set('GROUPID', 'group_dmd_0')
+    dmdSec.set('ID', 'dmd_1')
+
+    mdWrap = ET.SubElement(dmdSec, ET.QName(metsNS, 'mdWrap'))
+    mdWrap.set('MDTYPE', 'OTHER')
+    mdWrap.set('OTHERMDTYPE', 'DIM')
+
+    xmlData = ET.SubElement(mdWrap, ET.QName(metsNS, 'xmlData'))
+
+    dim = ET.SubElement(xmlData, ET.QName(dimNS, 'dim'))
+    dim.set('dspaceType', 'ITEM')
+
+    titleField = ET.SubElement(dim, ET.QName(dimNS, 'field'))
+    titleField.set('mdschema', 'dc')
+    titleField.set('element', 'title')
+    titleField.text = str(time.time())
+
+    typeField = ET.SubElement(dim, ET.QName(dimNS, 'field'))
+    typeField.set('mdschema', 'dc')
+    typeField.set('element', 'type')
+    typeField.text = submissionType
+
+    userIdField = ET.SubElement(dim, ET.QName(dimNS, 'field'))
+    userIdField.set('mdschema', 'ethz')
+    userIdField.set('element', 'identifier')
+    userIdField.set('qualifier', 'openbis')
+    userIdField.text = permIdsStr
+
+    userInfoField = ET.SubElement(dim, ET.QName(dimNS, 'field'))
+    userInfoField.set('mdschema', 'dc')
+    userInfoField.set('element', 'contributor')
+    userInfoField.set('qualifier', 'author')
+    userInfoField.text = userInformation['lastName'] + ', ' + userInformation['firstName']
+
+    publicationDateField = ET.SubElement(dim, ET.QName(dimNS, 'field'))
+    publicationDateField.set('mdschema', 'dc')
+    publicationDateField.set('element', 'date')
+    publicationDateField.set('qualifier', 'issued')
+    publicationDateField.text = datetime.date.today().strftime('%Y-%m-%d')
+
+    elnLimsURLPattern = '/openbis-test/webapp/eln-lims/?menuUniqueId=null&viewName='
+
+    for entity in entities:
+        type = entity['type']
+        viewName = ''
+        if type == 'SPACE':
+            viewName = 'showSpacePage'
+        if type == 'PROJECT':
+            viewName = 'showProjectPageFromPermId'
+        if type == 'EXPERIMENT':
+            viewName = 'showExperimentPageFromPermId'
+        if type == 'SAMPLE':
+            viewName = 'showViewSamplePageFromPermId'
+        if type == 'DATASET':
+            viewName = 'showViewDataSetPageFromPermId'
+        if type != 'FILE':
+            urlField = ET.SubElement(dim, ET.QName(dimNS, 'field'))
+            urlField.set('mdschema', 'ethz')
+            urlField.set('element', 'identifier')
+            urlField.set('qualifier', 'url')
+            urlField.text = originUrl + elnLimsURLPattern + viewName + '&viewData=' + entity['permId']
+
+    if minDateStr is not None and maxDateStr is not None:
+        creationDateField = ET.SubElement(dim, ET.QName(dimNS, 'field'))
+        creationDateField.set('mdschema', 'dc')
+        creationDateField.set('element', 'date')
+        creationDateField.set('qualifier', 'created')
+        creationDateField.text = minDateStr + '/' + maxDateStr if minDateStr != maxDateStr else minDateStr
+
+    fileSec = ET.SubElement(root, ET.QName(metsNS, 'fileSec'))
+    fileGrp = ET.SubElement(fileSec, ET.QName(metsNS, 'fileGrp'))
+    fileGrp.set('USE', 'CONTENT')
+
+    i = 0
+    for fileMetadatum in fileMetadata:
+        i += 1
+        file = ET.SubElement(fileGrp, ET.QName(metsNS, 'file'))
+        file.set('ID', 'file_' + str(i))
+        fLocat = ET.SubElement(file, ET.QName(metsNS, 'FLocat'))
+        fLocat.set('LOCTYPE', 'URL')
+        fLocat.set('MIMETYPE', fileMetadatum.get('mimeType'))
+        fLocat.set('RETENTIONPERIOD', '10 years')
+        fLocat.set(ET.QName(xlinkNS, 'href'), fileMetadatum.get('fileName'))
+
+    structMap = ET.SubElement(root, ET.QName(metsNS, 'structMap'))
+    structMap.set('LABEL', 'DSpace')
+    structMap.set('TYPE', 'LOGICAL')
+    div1 = ET.SubElement(structMap, ET.QName(metsNS, 'div'))
+    div1.set('DMDID', 'dmd_1')
+    div1.set('TYPE', 'DSpace Item')
+
+    xmlFileName = 'mets.xml'
+    xmlFilePath = exportDirPath + '/' + xmlFileName
+    ET.ElementTree(root).write(xmlFilePath)
+
+    xmlFile = File(xmlFilePath)
+    addToZipFile(' ' + xmlFileName, xmlFile, zipOutputStream)
+    # Space is added to the file name because the method chops out the first character
+
+
+def javaDateToStr(javaDate):
+    dateFormat = SimpleDateFormat('yyyy-MM-dd')
+    return dateFormat.format(javaDate)
-- 
GitLab