Index: lams_common/build.xml
===================================================================
diff -u -r5e828bbc5f2a11fa8b55f616b1eb0e4c0489c68a -r6e4ed3724bd76354c2ee43c88979385c4a162a0e
--- lams_common/build.xml (.../build.xml) (revision 5e828bbc5f2a11fa8b55f616b1eb0e4c0489c68a)
+++ lams_common/build.xml (.../build.xml) (revision 6e4ed3724bd76354c2ee43c88979385c4a162a0e)
@@ -398,6 +398,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Index: lams_common/conf/hibernate/mappings/org/lamsfoundation/lams/events/Event.hbm.xml
===================================================================
diff -u
--- lams_common/conf/hibernate/mappings/org/lamsfoundation/lams/events/Event.hbm.xml (revision 0)
+++ lams_common/conf/hibernate/mappings/org/lamsfoundation/lams/events/Event.hbm.xml (revision 6e4ed3724bd76354c2ee43c88979385c4a162a0e)
@@ -0,0 +1,128 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Index: lams_common/conf/hibernate/mappings/org/lamsfoundation/lams/events/Subscription.hbm.xml
===================================================================
diff -u
--- lams_common/conf/hibernate/mappings/org/lamsfoundation/lams/events/Subscription.hbm.xml (revision 0)
+++ lams_common/conf/hibernate/mappings/org/lamsfoundation/lams/events/Subscription.hbm.xml (revision 6e4ed3724bd76354c2ee43c88979385c4a162a0e)
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Index: lams_common/conf/language/lams/ApplicationResources.properties
===================================================================
diff -u -rdb3b153522421c748da5d7a67df4fcdf0652cbc7 -r6e4ed3724bd76354c2ee43c88979385c4a162a0e
--- lams_common/conf/language/lams/ApplicationResources.properties (.../ApplicationResources.properties) (revision db3b153522421c748da5d7a67df4fcdf0652cbc7)
+++ lams_common/conf/language/lams/ApplicationResources.properties (.../ApplicationResources.properties) (revision 6e4ed3724bd76354c2ee43c88979385c4a162a0e)
@@ -50,5 +50,8 @@
validation.error.toolBranchingMustHaveDefaultBranch =A Learner's Output Branching Activity must have a Default Branch.
validation.error.toolBranchingMustHaveAnInputToolActivity =A Learner's Output Branching Activity must have an Input Tool.
+mail.resend.abandon.subject =LAMS: Message delivery failed
+mail.resend.abandon.body1=LAMS was resending a message to users, but eventually it gave up. The message was:\n
+mail.resend.abandon.body2=\nUsers' login names:\n
#======= End labels: Exported 43 labels for en AU =====
Index: lams_common/db/model/lams_11.clay
===================================================================
diff -u -r8a5855489e3dc94d46a3750bbaaa985f2e3a6ab3 -r6e4ed3724bd76354c2ee43c88979385c4a162a0e
--- lams_common/db/model/lams_11.clay (.../lams_11.clay) (revision 8a5855489e3dc94d46a3750bbaaa985f2e3a6ab3)
+++ lams_common/db/model/lams_11.clay (.../lams_11.clay) (revision 6e4ed3724bd76354c2ee43c88979385c4a162a0e)
@@ -1,18 +1,18 @@
-
-
+
+
-
+
-
+
-
+
-
+
@@ -25,7 +25,7 @@
-
+
@@ -38,26 +38,26 @@
-
+
-
+
-
+
-
+
@@ -66,17 +66,17 @@
-
+
-
+
-
+
@@ -89,27 +89,27 @@
-
+
-
+
-
+
-
+
@@ -122,27 +122,27 @@
-
+
-
+
-
+
-
+
@@ -155,27 +155,27 @@
-
+
-
+
-
+
-
+
@@ -188,7 +188,7 @@
-
+
@@ -201,26 +201,26 @@
-
+
-
+
-
+
-
+
@@ -233,7 +233,7 @@
-
+
@@ -246,7 +246,7 @@
-
+
@@ -259,7 +259,7 @@
-
+
@@ -272,7 +272,7 @@
-
+
@@ -285,7 +285,7 @@
-
+
@@ -298,7 +298,7 @@
-
+
@@ -311,7 +311,7 @@
-
+
@@ -324,7 +324,7 @@
-
+
@@ -337,7 +337,7 @@
-
+
@@ -350,7 +350,7 @@
-
+
@@ -363,7 +363,7 @@
-
+
@@ -376,7 +376,7 @@
-
+
@@ -389,13 +389,13 @@
-
+
-
+
@@ -408,7 +408,7 @@
-
+
@@ -421,7 +421,7 @@
-
+
@@ -434,13 +434,13 @@
-
+
-
+
@@ -453,7 +453,7 @@
-
+
@@ -466,7 +466,7 @@
-
+
@@ -479,7 +479,7 @@
-
+
@@ -492,7 +492,7 @@
-
+
@@ -505,7 +505,7 @@
-
+
@@ -518,7 +518,7 @@
-
+
@@ -531,26 +531,26 @@
-
+
-
+
-
+
-
+
@@ -563,7 +563,7 @@
-
+
@@ -576,7 +576,7 @@
-
+
@@ -589,14 +589,14 @@
-
+
-
+
@@ -609,7 +609,7 @@
-
+
@@ -622,7 +622,7 @@
-
+
@@ -635,7 +635,7 @@
-
+
@@ -648,7 +648,7 @@
-
+
@@ -661,7 +661,7 @@
-
+
@@ -674,7 +674,7 @@
-
+
@@ -687,7 +687,7 @@
-
+
@@ -700,7 +700,7 @@
-
+
@@ -714,90 +714,90 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -806,33 +806,33 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -845,27 +845,27 @@
-
+
-
+
-
+
-
+
@@ -878,7 +878,7 @@
-
+
@@ -891,20 +891,20 @@
-
+
-
+
-
+
@@ -917,7 +917,7 @@
-
+
@@ -930,7 +930,7 @@
-
+
@@ -943,7 +943,7 @@
-
+
@@ -956,13 +956,13 @@
-
+
-
+
@@ -975,25 +975,25 @@
-
+
-
+
-
+
-
+
@@ -1006,13 +1006,13 @@
-
+
-
+
@@ -1032,7 +1032,7 @@
-
+
@@ -1045,7 +1045,7 @@
-
+
@@ -1058,7 +1058,7 @@
-
+
@@ -1071,26 +1071,26 @@
-
+
-
+
-
+
-
+
@@ -1103,7 +1103,7 @@
-
+
@@ -1116,7 +1116,7 @@
-
+
@@ -1130,41 +1130,41 @@
-
+
-
+
-
+
-
+
-
+
-
+
@@ -1173,25 +1173,25 @@
-
+
-
+
-
+
-
+
-
+
@@ -1204,14 +1204,14 @@
-
+
-
+
@@ -1224,7 +1224,7 @@
-
+
@@ -1237,7 +1237,7 @@
-
+
@@ -1251,13 +1251,13 @@
-
+
-
+
@@ -1266,17 +1266,17 @@
-
+
-
+
-
+
@@ -1289,7 +1289,7 @@
-
+
@@ -1302,7 +1302,7 @@
-
+
@@ -1315,7 +1315,7 @@
-
+
@@ -1328,7 +1328,7 @@
-
+
@@ -1341,7 +1341,7 @@
-
+
@@ -1354,7 +1354,7 @@
-
+
@@ -1368,13 +1368,13 @@
-
+
-
+
@@ -1383,17 +1383,17 @@
-
+
-
+
-
+
@@ -1406,20 +1406,20 @@
-
+
-
+
-
+
@@ -1432,26 +1432,26 @@
-
+
-
+
-
+
-
+
@@ -1464,7 +1464,7 @@
-
+
@@ -1477,20 +1477,20 @@
-
+
-
+
-
+
@@ -1503,7 +1503,7 @@
-
+
@@ -1516,7 +1516,7 @@
-
+
@@ -1529,13 +1529,13 @@
-
+
-
+
@@ -1548,7 +1548,7 @@
-
+
@@ -1562,33 +1562,33 @@
-
+
-
+
-
+
-
+
-
+
@@ -1597,25 +1597,25 @@
-
+
-
+
-
+
-
+
-
+
@@ -1628,28 +1628,28 @@
-
+
-
+
-
+
-
+
@@ -1662,7 +1662,7 @@
-
+
@@ -1675,13 +1675,13 @@
-
+
-
+
@@ -1694,7 +1694,7 @@
-
+
@@ -1707,7 +1707,7 @@
-
+
@@ -1720,7 +1720,7 @@
-
+
@@ -1733,7 +1733,7 @@
-
+
@@ -1746,7 +1746,7 @@
-
+
@@ -1759,7 +1759,7 @@
-
+
@@ -1772,7 +1772,7 @@
-
+
@@ -1785,54 +1785,54 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -1841,25 +1841,25 @@
-
+
-
+
-
+
-
+
-
+
@@ -1872,39 +1872,39 @@
-
+
-
+
-
+
-
+
-
+
-
+
@@ -1917,44 +1917,44 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -1967,126 +1967,126 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -2099,13 +2099,13 @@
-
+
-
+
@@ -2118,7 +2118,7 @@
-
+
@@ -2131,7 +2131,7 @@
-
+
@@ -2144,7 +2144,7 @@
-
+
@@ -2157,14 +2157,14 @@
-
+
-
+
@@ -2177,7 +2177,7 @@
-
+
@@ -2190,7 +2190,7 @@
-
+
@@ -2203,13 +2203,13 @@
-
+
-
+
@@ -2223,41 +2223,41 @@
-
+
-
+
-
+
-
+
-
+
-
+
@@ -2266,33 +2266,33 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -2305,7 +2305,7 @@
-
+
@@ -2318,7 +2318,7 @@
-
+
@@ -2332,20 +2332,20 @@
-
+
-
+
-
+
@@ -2354,21 +2354,21 @@
-
+
-
+
-
+
-
+
@@ -2381,7 +2381,7 @@
-
+
@@ -2394,7 +2394,7 @@
-
+
@@ -2408,20 +2408,20 @@
-
+
-
+
-
+
@@ -2430,21 +2430,21 @@
-
+
-
+
-
+
-
+
@@ -2457,7 +2457,7 @@
-
+
@@ -2470,7 +2470,7 @@
-
+
@@ -2483,28 +2483,28 @@
-
+
-
+
-
+
-
+
@@ -2514,11 +2514,11 @@
-
+
-
+
@@ -2531,7 +2531,7 @@
-
+
@@ -2544,14 +2544,14 @@
-
+
-
+
@@ -2564,19 +2564,19 @@
-
+
-
+
-
+
@@ -2590,20 +2590,20 @@
-
+
-
+
-
+
@@ -2612,17 +2612,17 @@
-
+
-
+
-
+
@@ -2635,7 +2635,7 @@
-
+
@@ -2648,7 +2648,7 @@
-
+
@@ -2661,26 +2661,26 @@
-
+
-
+
-
+
-
+
@@ -2693,7 +2693,7 @@
-
+
@@ -2706,7 +2706,7 @@
-
+
@@ -2719,31 +2719,31 @@
-
+
-
+
-
+
-
+
-
+
@@ -2756,7 +2756,7 @@
-
+
@@ -2769,7 +2769,7 @@
-
+
@@ -2782,7 +2782,7 @@
-
+
@@ -2796,41 +2796,41 @@
-
+
-
+
-
+
-
+
-
+
-
+
@@ -2840,11 +2840,11 @@
-
+
-
+
@@ -2857,7 +2857,7 @@
-
+
@@ -2870,7 +2870,7 @@
-
+
@@ -2883,7 +2883,7 @@
-
+
@@ -2896,7 +2896,7 @@
-
+
@@ -2909,19 +2909,19 @@
-
+
-
+
-
+
@@ -2934,7 +2934,7 @@
-
+
@@ -2947,7 +2947,7 @@
-
+
@@ -2960,7 +2960,7 @@
-
+
@@ -2974,41 +2974,41 @@
-
+
-
+
-
+
-
+
-
+
-
+
@@ -3017,18 +3017,18 @@
-
+
-
+
-
+
@@ -3048,21 +3048,21 @@
-
+
-
+
-
+
@@ -3083,7 +3083,7 @@
-
+
@@ -3096,7 +3096,7 @@
-
+
@@ -3109,7 +3109,7 @@
-
+
@@ -3122,7 +3122,7 @@
-
+
@@ -3135,7 +3135,7 @@
-
+
@@ -3148,112 +3148,112 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -3267,29 +3267,29 @@
-
+
-
+
-
+
-
+
-
+
@@ -3299,11 +3299,11 @@
-
+
-
+
@@ -3316,7 +3316,7 @@
-
+
@@ -3329,7 +3329,7 @@
-
+
@@ -3342,7 +3342,7 @@
-
+
@@ -3355,7 +3355,7 @@
-
+
@@ -3368,13 +3368,13 @@
-
+
-
+
@@ -3387,7 +3387,7 @@
-
+
@@ -3400,61 +3400,61 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -3464,11 +3464,11 @@
-
+
-
+
@@ -3481,7 +3481,7 @@
-
+
@@ -3495,21 +3495,21 @@
-
+
-
+
-
+
@@ -3519,11 +3519,11 @@
-
+
-
+
@@ -3536,7 +3536,7 @@
-
+
@@ -3550,21 +3550,21 @@
-
+
-
+
-
+
@@ -3574,11 +3574,11 @@
-
+
-
+
@@ -3591,27 +3591,27 @@
-
+
-
+
-
+
-
+
@@ -3624,7 +3624,7 @@
-
+
@@ -3638,21 +3638,21 @@
-
+
-
+
-
+
@@ -3662,11 +3662,11 @@
-
+
-
+
@@ -3679,27 +3679,27 @@
-
+
-
+
-
+
-
+
@@ -3712,7 +3712,7 @@
-
+
@@ -3726,13 +3726,13 @@
-
+
-
+
@@ -3742,11 +3742,11 @@
-
+
-
+
@@ -3759,7 +3759,7 @@
-
+
@@ -3773,19 +3773,19 @@
-
+
-
+
-
+
@@ -3795,11 +3795,11 @@
-
+
-
+
@@ -3812,7 +3812,7 @@
-
+
@@ -3826,19 +3826,19 @@
-
+
-
+
-
+
@@ -3848,11 +3848,11 @@
-
+
-
+
@@ -3865,27 +3865,27 @@
-
+
-
+
-
+
-
+
@@ -3898,28 +3898,28 @@
-
+
-
+
-
+
-
+
@@ -3932,27 +3932,27 @@
-
+
-
+
-
+
-
+
@@ -3965,27 +3965,27 @@
-
+
-
+
-
+
-
+
@@ -3998,15 +3998,15 @@
-
+
-
+
@@ -4021,7 +4021,7 @@
-
+
@@ -4034,15 +4034,15 @@
-
+
-
+
@@ -4056,7 +4056,7 @@
-
+
@@ -4069,34 +4069,34 @@
-
+
-
+
-
+
-
+
-
+
@@ -4109,7 +4109,7 @@
-
+
@@ -4122,7 +4122,7 @@
-
+
@@ -4136,20 +4136,20 @@
-
+
-
+
-
+
@@ -4166,7 +4166,7 @@
-
+
@@ -4179,7 +4179,7 @@
-
+
@@ -4192,7 +4192,7 @@
-
+
@@ -4205,13 +4205,13 @@
-
+
-
+
@@ -4225,13 +4225,13 @@
-
+
-
+
@@ -4241,11 +4241,11 @@
-
+
-
+
@@ -4258,7 +4258,7 @@
-
+
@@ -4271,21 +4271,21 @@
-
+
-
+
-
+
@@ -4299,13 +4299,13 @@
-
+
-
+
@@ -4319,7 +4319,7 @@
-
+
@@ -4332,7 +4332,7 @@
-
+
@@ -4345,27 +4345,27 @@
-
+
-
+
-
+
-
+
@@ -4378,7 +4378,7 @@
-
+
@@ -4392,13 +4392,13 @@
-
+
-
+
@@ -4408,11 +4408,11 @@
-
+
-
+
@@ -4425,27 +4425,27 @@
-
+
-
+
-
+
-
+
@@ -4458,27 +4458,27 @@
-
+
-
+
-
+
-
+
@@ -4491,7 +4491,7 @@
-
+
@@ -4504,7 +4504,7 @@
-
+
@@ -4517,13 +4517,13 @@
-
+
-
+
@@ -4536,21 +4536,21 @@
-
+
-
+
-
+
@@ -4560,11 +4560,11 @@
-
+
-
+
@@ -4577,27 +4577,27 @@
-
+
-
+
-
+
-
+
@@ -4610,7 +4610,7 @@
-
+
@@ -4623,33 +4623,33 @@
-
+
-
+
-
+
-
+
-
+
@@ -4662,7 +4662,7 @@
-
+
@@ -4675,7 +4675,7 @@
-
+
@@ -4688,41 +4688,41 @@
-
+
-
+
-
+
-
+
-
+
-
+
@@ -4732,11 +4732,11 @@
-
+
-
+
@@ -4749,27 +4749,27 @@
-
+
-
+
-
+
-
+
@@ -4782,21 +4782,21 @@
-
+
-
+
-
+
@@ -4809,7 +4809,7 @@
-
+
@@ -4822,21 +4822,21 @@
-
+
-
+
-
+
@@ -4846,11 +4846,11 @@
-
+
-
+
@@ -4863,7 +4863,7 @@
-
+
@@ -4876,28 +4876,28 @@
-
+
-
+
-
+
-
+
@@ -4911,13 +4911,13 @@
-
+
-
+
@@ -4933,7 +4933,7 @@
-
+
@@ -4946,7 +4946,7 @@
-
+
@@ -4960,13 +4960,13 @@
-
+
-
+
@@ -4980,7 +4980,7 @@
-
+
@@ -5006,92 +5006,92 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -5105,7 +5105,7 @@
-
+
@@ -5118,29 +5118,29 @@
-
+
-
+
-
+
-
+
@@ -5150,7 +5150,7 @@
-
+
@@ -5163,7 +5163,7 @@
-
+
@@ -5176,7 +5176,7 @@
-
+
@@ -5190,20 +5190,20 @@
-
+
-
+
-
+
@@ -5213,11 +5213,11 @@
-
+
-
+
@@ -5230,27 +5230,27 @@
-
+
-
+
-
+
-
+
@@ -5263,7 +5263,7 @@
-
+
@@ -5276,7 +5276,7 @@
-
+
@@ -5290,20 +5290,20 @@
-
+
-
+
-
+
@@ -5313,11 +5313,11 @@
-
+
-
+
@@ -5330,55 +5330,55 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -5391,22 +5391,22 @@
-
+
-
+
-
+
@@ -5418,42 +5418,42 @@
-
+
-
+
-
+
-
+
-
+
-
+
@@ -5467,7 +5467,7 @@
-
+
@@ -5479,7 +5479,7 @@
-
+
@@ -5492,7 +5492,7 @@
-
+
@@ -5505,7 +5505,7 @@
-
+
@@ -5518,7 +5518,7 @@
-
+
@@ -5531,7 +5531,7 @@
-
+
@@ -5544,7 +5544,7 @@
-
+
@@ -5558,34 +5558,34 @@
-
+
-
+
-
+
-
+
-
+
@@ -5599,7 +5599,7 @@
-
+
@@ -5612,7 +5612,7 @@
-
+
@@ -5626,25 +5626,25 @@
-
+
-
+
-
+
-
+
@@ -5654,11 +5654,11 @@
-
+
-
+
@@ -5671,7 +5671,7 @@
-
+
@@ -5684,7 +5684,7 @@
-
+
@@ -5697,62 +5697,62 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -5765,7 +5765,7 @@
-
+
@@ -5779,13 +5779,13 @@
-
+
-
+
@@ -5795,11 +5795,11 @@
-
+
-
+
@@ -5812,7 +5812,7 @@
-
+
@@ -5825,32 +5825,235 @@
-
+
-
+
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Index: lams_common/db/sql/create_lams_11_tables.sql
===================================================================
diff -u -r98059cd516068af9c71148dbc4141f961e194d6d -r6e4ed3724bd76354c2ee43c88979385c4a162a0e
--- lams_common/db/sql/create_lams_11_tables.sql (.../create_lams_11_tables.sql) (revision 98059cd516068af9c71148dbc4141f961e194d6d)
+++ lams_common/db/sql/create_lams_11_tables.sql (.../create_lams_11_tables.sql) (revision 6e4ed3724bd76354c2ee43c88979385c4a162a0e)
@@ -973,6 +973,35 @@
)TYPE=InnoDB;
CREATE UNIQUE INDEX IX_lams_psswd_rqst_key ON lams_password_request (request_key ASC);
+CREATE TABLE lams_events (
+ uid BIGINT NOT NULL UNIQUE auto_increment
+ , scope VARCHAR(255) NOT NULL
+ , name VARCHAR(255) NOT NULL
+ , event_session_id BIGINT
+ , triggered TINYINT
+ , default_subject VARCHAR(255)
+ , default_message TEXT
+ , subject VARCHAR(255)
+ , message TEXT
+ , fail_time DATETIME
+ , INDEX (scope,name,event_session_id)
+ , PRIMARY KEY (uid)
+)TYPE=InnoDB;
+CREATE TABLE lams_event_subscriptions (
+ uid BIGINT NOT NULL UNIQUE auto_increment
+ , user_id BIGINT
+ , event_uid BIGINT
+ , delivery_method_id TINYINT UNSIGNED
+ , periodicity BIGINT
+ , last_operation_time DATETIME
+ , last_operation_message TEXT
+ , PRIMARY KEY (uid)
+ , CONSTRAINT EventSubscriptionsToUsers FOREIGN KEY (user_id)
+ REFERENCES lams_user (user_id) ON DELETE CASCADE ON UPDATE CASCADE
+ , INDEX (event_uid)
+ , CONSTRAINT EventSubscriptionsToEvent FOREIGN KEY (event_uid)
+ REFERENCES lams_events (uid) ON DELETE CASCADE ON UPDATE CASCADE
+)TYPE=InnoDB;
\ No newline at end of file
Index: lams_common/db/sql/drop_lams_11_tables.sql
===================================================================
diff -u -raff8cacb06ef0ae5ee1c3ac0559b805e7e1a5958 -r6e4ed3724bd76354c2ee43c88979385c4a162a0e
--- lams_common/db/sql/drop_lams_11_tables.sql (.../drop_lams_11_tables.sql) (revision aff8cacb06ef0ae5ee1c3ac0559b805e7e1a5958)
+++ lams_common/db/sql/drop_lams_11_tables.sql (.../drop_lams_11_tables.sql) (revision 6e4ed3724bd76354c2ee43c88979385c4a162a0e)
@@ -16,6 +16,8 @@
DROP TABLE IF EXISTS lams_css_property;
DROP TABLE IF EXISTS lams_css_style;
DROP TABLE IF EXISTS lams_css_theme_ve;
+DROP TABLE IF EXISTS lams_event_subscriptions;
+DROP TABLE IF EXISTS lams_events;
DROP TABLE IF EXISTS lams_gate_activity_level;
DROP TABLE IF EXISTS lams_group;
DROP TABLE IF EXISTS lams_grouping;
Index: lams_common/design/EventsNotificationServiceDiagram.gif
===================================================================
diff -u
Binary files differ
Index: lams_common/src/java/org/lamsfoundation/lams/commonContext.xml
===================================================================
diff -u -re9d226f1680751f53da50848214c85df0e1f06fb -r6e4ed3724bd76354c2ee43c88979385c4a162a0e
--- lams_common/src/java/org/lamsfoundation/lams/commonContext.xml (.../commonContext.xml) (revision e9d226f1680751f53da50848214c85df0e1f06fb)
+++ lams_common/src/java/org/lamsfoundation/lams/commonContext.xml (.../commonContext.xml) (revision 6e4ed3724bd76354c2ee43c88979385c4a162a0e)
@@ -79,6 +79,10 @@
org/lamsfoundation/lams/notebook/model/NotebookEntry.hbm.xml
+
+ org/lamsfoundation/lams/events/Event.hbm.xml
+ org/lamsfoundation/lams/events/Subscription.hbm.xml
+
org/lamsfoundation/lams/config/ConfigurationItem.hbm.xml
@@ -229,6 +233,7 @@
+
@@ -248,6 +253,45 @@
+
+
+
+
+
+
+ org.springframework.scheduling.quartz.LocalDataSourceJobStore
+ org.quartz.impl.jdbcjobstore.StdJDBCDelegate
+ lams_qtz_
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PROPAGATION_REQUIRED
+ PROPAGATION_REQUIRED
+ PROPAGATION_REQUIRED
+ PROPAGATION_REQUIRED
+ PROPAGATION_REQUIRED
+ PROPAGATION_REQUIRED
+ PROPAGATION_REQUIRED
+
+
+
+
@@ -303,6 +347,11 @@
+
+
+
+
+
Index: lams_common/src/java/org/lamsfoundation/lams/events/AbstractDeliveryMethod.java
===================================================================
diff -u
--- lams_common/src/java/org/lamsfoundation/lams/events/AbstractDeliveryMethod.java (revision 0)
+++ lams_common/src/java/org/lamsfoundation/lams/events/AbstractDeliveryMethod.java (revision 6e4ed3724bd76354c2ee43c88979385c4a162a0e)
@@ -0,0 +1,83 @@
+package org.lamsfoundation.lams.events;
+
+import java.security.InvalidParameterException;
+
+import org.apache.commons.lang.StringUtils;
+
+/**
+ * Provides methods to notify users of an event.
+ * @author Marcin Cieslak
+ *
+ */
+public abstract class AbstractDeliveryMethod {
+ /**
+ * Short name for the delivery method
+ */
+ protected final String signature;
+
+ /**
+ * Short description of the delivery method.
+ */
+ protected final String description;
+
+ /**
+ * Unique identifier of the delivery method.
+ */
+ protected final short id;
+
+ /**
+ * Maximum time for the message to be send.
+ */
+ protected long sendTimeout = Long.MAX_VALUE;
+
+ /**
+ * Standard constructor.
+ * @param id ID of the delivery method
+ * @param signature signature of the delivery method
+ * @param description short description of the delivery method
+ * @throws InvalidParameterException if signature is blank
+ */
+ protected AbstractDeliveryMethod(short id, String signature, String description) throws InvalidParameterException {
+ if (StringUtils.isEmpty(signature)) {
+ throw new InvalidParameterException("Signature can not be blank.");
+ }
+ this.signature = signature;
+ this.description = description;
+ this.id = id;
+ }
+
+ /**
+ * Sends the message to the user.
+ * @param userId ID of the user
+ * @param subject subject of the message
+ * @param message text of the message
+ * @return null
if the operation was successful; error message if it failed
+ * @throws InvalidParameterException
+ */
+ protected abstract String send(Long userId, String subject, String message) throws InvalidParameterException;
+
+ public String getSignature() {
+ return signature;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return o instanceof AbstractDeliveryMethod && ((AbstractDeliveryMethod) o).signature.equalsIgnoreCase(signature);
+ }
+
+ public short getId() {
+ return id;
+ }
+
+ public long getSendTimeout() {
+ return sendTimeout;
+ }
+
+ public void setSendTimeout(long sendTimeout) {
+ this.sendTimeout = sendTimeout;
+ }
+}
\ No newline at end of file
Index: lams_common/src/java/org/lamsfoundation/lams/events/DeliveryMethodMail.java
===================================================================
diff -u
--- lams_common/src/java/org/lamsfoundation/lams/events/DeliveryMethodMail.java (revision 0)
+++ lams_common/src/java/org/lamsfoundation/lams/events/DeliveryMethodMail.java (revision 6e4ed3724bd76354c2ee43c88979385c4a162a0e)
@@ -0,0 +1,102 @@
+package org.lamsfoundation.lams.events;
+
+import java.security.InvalidParameterException;
+import java.util.Properties;
+
+import javax.mail.MessagingException;
+import javax.mail.Session;
+import javax.mail.Transport;
+import javax.mail.Message.RecipientType;
+import javax.mail.internet.AddressException;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeMessage;
+
+import org.apache.commons.lang.StringUtils;
+import org.lamsfoundation.lams.usermanagement.User;
+import org.lamsfoundation.lams.util.Configuration;
+
+/**
+ * Allows sending mail from the configured mail server.
+ * @author Marcin Cieslak
+ *
+ */
+public class DeliveryMethodMail extends AbstractDeliveryMethod {
+
+ private static DeliveryMethodMail instance;
+
+ protected DeliveryMethodMail() {
+ super((short) 1, "MAIL", "Messages will be send by Mail");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String send(Long userId, String subject, String message) throws InvalidParameterException {
+ if (userId == null) {
+ return "User ID should not be null.";
+ }
+ try {
+ User user = (User) EventNotificationService.getInstance().getUserManagementService().findById(User.class,
+ userId.intValue());
+ if (user == null) {
+ return "User with the provided ID was not found.";
+ }
+ String email = user.getEmail();
+ if (StringUtils.isBlank(email)) {
+ return "User's e-mail address is blank.";
+ }
+ sendFromSupportEmail(subject, email, message);
+ return null;
+ }
+ catch (Exception e) {
+ return e.getMessage();
+ }
+ }
+
+ public static DeliveryMethodMail getInstance() {
+ if (DeliveryMethodMail.instance == null) {
+ DeliveryMethodMail.instance = new DeliveryMethodMail();
+ }
+ return DeliveryMethodMail.instance;
+ }
+
+ /**
+ * Sends an email sourced from admin. Copied from Emailer class.
+ * @param subject subject of the message
+ * @param to address to send
+ * @param body text of the message
+ * @throws AddressException if address was incorrect
+ * @throws MessagingException if the operation failed
+ */
+ public void sendFromSupportEmail(String subject, String to, String body) throws AddressException, MessagingException {
+ String supportEmail = Configuration.get("LamsSupportEmail");
+ String smtpServer = Configuration.get("SMTPServer");
+ Properties properties = new Properties();
+ properties.put("mail.smtp.host", smtpServer);
+ Session session = Session.getInstance(properties);
+ MimeMessage message = new MimeMessage(session);
+ if (!StringUtils.isBlank(supportEmail)) {
+ message.setFrom(new InternetAddress(supportEmail));
+ }
+ message.addRecipient(RecipientType.TO, new InternetAddress(to));
+
+ message.setSubject(subject == null ? "" : subject);
+ message.setText(body == null ? "" : body);
+ Transport.send(message);
+ }
+
+ /**
+ * Sends an email to the address provided by the admin.
+ * @param subject subject of the message
+ * @param body text of the message
+ * @throws AddressException if address was incorrect
+ * @throws MessagingException if the operation failed
+ */
+ void notifyAdmin(String subject, String body) throws AddressException, MessagingException {
+ String adminEmail = Configuration.get("LamsSupportEmail");
+ if (!StringUtils.isEmpty(adminEmail)) {
+ sendFromSupportEmail(subject, adminEmail, body);
+ }
+ }
+}
\ No newline at end of file
Index: lams_common/src/java/org/lamsfoundation/lams/events/Event.java
===================================================================
diff -u
--- lams_common/src/java/org/lamsfoundation/lams/events/Event.java (revision 0)
+++ lams_common/src/java/org/lamsfoundation/lams/events/Event.java (revision 6e4ed3724bd76354c2ee43c88979385c4a162a0e)
@@ -0,0 +1,474 @@
+package org.lamsfoundation.lams.events;
+
+import java.security.InvalidParameterException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.commons.lang.StringUtils;
+
+/**
+ * A event that users can subscribe to and at some point can be triggered, notifing the users.
+ *
+ * @hibernate.class table="lams_events"
+ * @author Marcin Cieslak
+ *
+ */
+public class Event {
+
+ //--------- persistent fields -------------
+ /**
+ * Unique ID for Hibernate needs.
+ */
+ private Long uid;
+
+ /**
+ * Name of the event.
+ */
+ protected String name;
+
+ /**
+ * Scope of the event.
+ * For events that are common for the whole LAMS environment,
+ * {@link EventNotificationService#CORE_EVENTS_SCOPE} should be used.
+ * For tools their signature should be used.
+ */
+ protected String scope;
+
+ /**
+ * Identifier for a session in which the event is valid.
+ * or events that are common for the whole LAMS environment null
can be used.
+ * For tools their content ID should be used.
+ */
+ protected Long eventSessionId;
+
+ /**
+ * Was the event triggered (did it happen).
+ */
+ protected Boolean triggered = false;
+
+ /**
+ * Set of users that are subscribed to this event, along with the required data.
+ */
+ protected Set subscriptions = new HashSet();
+
+ /**
+ * Default message that will be send when the event is triggered and no other message is or was provided.
+ */
+ protected String defaultMessage;
+
+ /**
+ * Default subject of the message that will be send when the event is triggered and no other message is or was provided.
+ */
+ protected String defaultSubject;
+
+ /**
+ * Message that will be send when the event is triggered.
+ */
+ protected String message;
+
+ /**
+ * Subject of the message that will be send when the event is triggered.
+ */
+ protected String subject;
+
+ /**
+ * If sending notifications fails, this property holds the time of this failure.
+ */
+ protected Date failTime;
+
+ // -------------- non-persistent fields --------------
+ /**
+ * Label that identifies the event.
+ */
+ protected String fullSignature;
+
+ /**
+ * Used for event instance management in EventNotificationService.
+ */
+ protected int referenceCounter;
+
+ /**
+ * Thread that notifies users of the event.
+ * Since sending messages to multiple users can take some time,
+ * another thread is responsible for that, so the main one does not get blocked.
+ */
+ protected Thread notificationThread;
+
+ /**
+ * Should the event be deleted from the database.
+ */
+ protected boolean deleted;
+
+ private Event() {
+
+ }
+
+ /**
+ * Standard constructor used by EventNotificationService.
+ * @param scope scope of the event
+ * @param name name of the event
+ * @param eventSessionId session ID of the event
+ * @param defaultSubject subject of the message to send
+ * @param defaultMessage body of the message to send
+ * @throws InvalidParameterException if scope is null
or name is blank
+ */
+ public Event(String scope, String name, Long sessionId, String defaultSubject, String defaultMessage)
+ throws InvalidParameterException {
+ if (scope == null) {
+ throw new InvalidParameterException("Event scope can not be null.");
+ }
+ if (StringUtils.isEmpty(name)) {
+ throw new InvalidParameterException("Event name can not be blank.");
+ }
+ this.scope = scope;
+ this.name = name;
+ eventSessionId = sessionId;
+ this.defaultSubject = defaultSubject;
+ this.defaultMessage = defaultMessage;
+ fullSignature = createFullSignature(scope, name, sessionId);
+ }
+
+ /**
+ * Build a string that identifies the event.
+ * @param scope scope of the event
+ * @param name name of the event
+ * @param eventSessionId session ID of the event
+ * @return event signature of the event
+ * @throws InvalidParameterException if scope is null
or name is blank
+ */
+ protected static String createFullSignature(String scope, String name, Long sessionId) throws InvalidParameterException {
+ if (scope == null) {
+ throw new InvalidParameterException("Scope should not be null.");
+ }
+ if (StringUtils.isEmpty(name)) {
+ throw new InvalidParameterException("Name should not be blank.");
+ }
+ return scope + '_' + name + (sessionId == null ? "" : "_" + sessionId);
+ }
+
+ @Override
+ public Object clone() {
+ Event clone = new Event(scope, name, eventSessionId, defaultSubject, defaultMessage);
+ for (Subscription subscription : getSubscriptions()) {
+ clone.getSubscriptions().add((Subscription) subscription.clone());
+ }
+ return clone;
+ }
+
+ /**
+ * Full signature is compared.
+ */
+ @Override
+ public boolean equals(Object o) {
+ return o instanceof Event && ((Event) o).getFullSignature().equalsIgnoreCase(getFullSignature())
+ || o instanceof CharSequence && ((CharSequence) o).toString().equalsIgnoreCase(getFullSignature());
+ }
+
+ @Override
+ public int hashCode() {
+ return getFullSignature().hashCode();
+ }
+
+ /**
+ * @hibernate.property column="default_message"
+ * @return
+ */
+ protected String getDefaultMessage() {
+ return defaultMessage;
+ }
+
+ /**
+ * @hibernate.property column="default_subject"
+ * @return
+ */
+ protected String getDefaultSubject() {
+ return defaultSubject;
+ }
+
+ protected String getFullSignature() {
+ if (fullSignature == null) {
+ fullSignature = createFullSignature(getScope(), getName(), getEventSessionId());
+ }
+ return fullSignature;
+ }
+
+ /**
+ * @hibernate.property column="name"
+ * @return
+ */
+ protected String getName() {
+ return name;
+ }
+
+ /**
+ * @hibernate.property column="scope"
+ * @return
+ */
+ protected String getScope() {
+ return scope;
+ }
+
+ /**
+ * @hibernate.property column="event_session_id"
+ * @return
+ */
+ protected Long getEventSessionId() {
+ return eventSessionId;
+ }
+
+ /**
+ *
+ * @hibernate.set cascade="all-delete-orphan" order-by="last_operation_time desc" outer-join="true"
+ * @hibernate.collection-key column="event_uid"
+ * @hibernate.collection-one-to-many class="org.lamsfoundation.lams.events.Subscription"
+ *
+ * @return
+ */
+ protected Set getSubscriptions() {
+ return subscriptions;
+ }
+
+ /**
+ * @hibernate.id column="uid" generator-class="native"
+ */
+ protected Long getUid() {
+ return uid;
+ }
+
+ /**
+ * @hibernate.property column="triggered"
+ * @return
+ */
+ protected boolean isTriggered() {
+ return triggered;
+ }
+
+ /**
+ * See {@link IEventNotificationService#triggerForSingleUser(String, String, Long, Long)}
+ */
+ protected boolean triggerForSingleUser(Long userId, String subject, String message) throws InvalidParameterException {
+ if (userId == null) {
+ throw new InvalidParameterException("User ID can not be null.");
+ }
+
+ List subscriptionList = new ArrayList(getSubscriptions());
+ for (int index = 0; index < subscriptionList.size(); index++) {
+ Subscription subscription = subscriptionList.get(index);
+ if (subscription.getUserId().equals(userId) && subscription.isEligibleForNotification()) {
+ subscription.notifyUser(subject, message);
+ return subscription.getLastOperationSuccessful();
+ }
+ }
+ return false;
+ }
+
+ protected void setDefaultMessage(String defaultMessage) {
+ this.defaultMessage = defaultMessage;
+ }
+
+ protected void setDefaultSubject(String defaultSubject) {
+ this.defaultSubject = defaultSubject;
+ }
+
+ protected void setName(String name) {
+ this.name = name;
+ }
+
+ protected void setScope(String scope) {
+ this.scope = scope;
+ }
+
+ protected void setEventSessionId(Long sessionId) {
+ eventSessionId = sessionId;
+ }
+
+ protected void setSubscriptions(Set subscriptions) {
+ this.subscriptions = subscriptions;
+ }
+
+ protected void setTriggered(boolean triggered) {
+ this.triggered = triggered;
+ }
+
+ protected void setUid(Long uid) {
+ this.uid = uid;
+ }
+
+ /**
+ * See {@link IEventNotificationService#subscribe(String, String, Long, Long, AbstractDeliveryMethod, Long)
+ *
+ */
+ protected boolean subscribe(Long userId, AbstractDeliveryMethod deliveryMethod, Long periodicity)
+ throws InvalidParameterException {
+ if (userId == null) {
+ throw new InvalidParameterException("User ID can not be null.");
+ }
+ if (deliveryMethod == null) {
+ throw new InvalidParameterException("Delivery method can not be null.");
+ }
+ boolean substriptionFound = false;
+ Subscription toSubscribe = new Subscription(userId, deliveryMethod, periodicity);
+ List subscriptionList = new ArrayList(getSubscriptions());
+ for (int index = 0; index < subscriptionList.size(); index++) {
+ Subscription subscription = subscriptionList.get(index);
+ if (subscription.equals(toSubscribe)) {
+ substriptionFound = true;
+ if (!subscription.getPeriodicity().equals(periodicity)) {
+ subscription.setPeriodicity(periodicity);
+ }
+ }
+ }
+ if (!substriptionFound) {
+ getSubscriptions().add(new Subscription(userId, deliveryMethod, periodicity));
+ return true;
+ }
+ return false;
+
+ }
+
+ /**
+ * See {@link IEventNotificationService#trigger(String, String, Long, String, String)
+ */
+ protected void trigger(String subject, String message) {
+ if (notificationThread == null || !notificationThread.isAlive()) {
+ triggered = true;
+
+ if (subject != null && subject.equals(getDefaultSubject())) {
+ setSubject(null);
+ }
+ else {
+ setSubject(subject);
+ }
+
+ if (message != null && message.equals(getDefaultMessage())) {
+ setMessage(null);
+ }
+ else {
+ setMessage(message);
+ }
+
+ final String subjectToSend = getSubject() == null ? getDefaultSubject() : getSubject();
+ final String messageToSend = getMessage() == null ? getDefaultMessage() : getMessage();
+ final Event finalRef = this;
+ notificationThread = new Thread(new Runnable() {
+ public void run() {
+ List subscriptionList = new ArrayList(getSubscriptions());
+ Event eventFailCopy = null;
+ for (int index = 0; index < subscriptionList.size(); index++) {
+ Subscription subscription = subscriptionList.get(index);
+ if (getFailTime() != null || subscription.isEligibleForNotification()) {
+ subscription.notifyUser(subjectToSend, messageToSend);
+ if (subscription.getLastOperationSuccessful()) {
+ if (getFailTime() != null) {
+ getSubscriptions().remove(subscription);
+ }
+ }
+ else if (getFailTime() == null) {
+ if (eventFailCopy == null) {
+ eventFailCopy = (Event) finalRef.clone();
+ eventFailCopy.referenceCounter++;
+ eventFailCopy.getSubscriptions().clear();
+ }
+ eventFailCopy.subscribe(subscription.getUserId(), subscription.getDeliveryMethod(), subscription
+ .getPeriodicity());
+ }
+ }
+ }
+ EventNotificationService.getInstance().saveEvent(finalRef);
+
+ /*
+ * if any of the notifications failed,
+ * a copy of the event is created in order to repeat the attempt later
+ */
+ if (eventFailCopy != null && !eventFailCopy.getSubscriptions().isEmpty()) {
+ eventFailCopy.setFailTime(new Date());
+ eventFailCopy.setDefaultSubject(subjectToSend);
+ eventFailCopy.setDefaultMessage(messageToSend);
+ eventFailCopy.setTriggered(true);
+ EventNotificationService.getInstance().saveEvent(eventFailCopy);
+ }
+ }
+ }, "LAMS_" + getFullSignature() + "_notification_thread");
+ notificationThread.start();
+ }
+ }
+
+ /**
+ * See {@link IEventNotificationService#unsubscribe(String, String, Long, Long)
+ */
+ protected boolean unsubscribe(Long userId) throws InvalidParameterException {
+ if (userId == null) {
+ throw new InvalidParameterException("User ID can not be null.");
+ }
+ List subscriptionList = new ArrayList(getSubscriptions());
+ boolean subscriptionFound = false;
+ for (int index = 0; index < subscriptionList.size(); index++) {
+ Subscription subscription = subscriptionList.get(index);
+ if (subscription.getUserId().equals(userId)) {
+ getSubscriptions().remove(subscription);
+ subscriptionFound = true;
+ }
+ }
+ return subscriptionFound;
+ }
+
+ /**
+ * See {@link IEventNotificationService#unsubscribe(String, String, Long, Long, AbstractDeliveryMethod)
+ */
+ protected boolean unsubscribe(Long userId, AbstractDeliveryMethod deliveryMethod) throws InvalidParameterException {
+ if (userId == null) {
+ throw new InvalidParameterException("User ID can not be null.");
+ }
+ if (deliveryMethod == null) {
+ throw new InvalidParameterException("Delivery method can not be null.");
+ }
+ List subscriptionList = new ArrayList(getSubscriptions());
+ for (int index = 0; index < subscriptionList.size(); index++) {
+ Subscription subscription = subscriptionList.get(index);
+ if (subscription.getUserId().equals(userId) && subscription.getDeliveryMethod().equals(deliveryMethod)) {
+ getSubscriptions().remove(subscription);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @hibernate.property column="fail_time"
+ * @return
+ */
+ protected Date getFailTime() {
+ return failTime;
+ }
+
+ protected void setFailTime(Date failTime) {
+ this.failTime = failTime;
+ }
+
+ /**
+ * @hibernate.property column="message"
+ * @return
+ */
+ protected String getMessage() {
+ return message;
+ }
+
+ protected void setMessage(String message) {
+ this.message = message;
+ }
+
+ /**
+ * @hibernate.property column="subject"
+ * @return
+ */
+ protected String getSubject() {
+ return subject;
+ }
+
+ protected void setSubject(String subject) {
+ this.subject = subject;
+ }
+}
\ No newline at end of file
Index: lams_common/src/java/org/lamsfoundation/lams/events/EventNotificationService.java
===================================================================
diff -u
--- lams_common/src/java/org/lamsfoundation/lams/events/EventNotificationService.java (revision 0)
+++ lams_common/src/java/org/lamsfoundation/lams/events/EventNotificationService.java (revision 6e4ed3724bd76354c2ee43c88979385c4a162a0e)
@@ -0,0 +1,616 @@
+package org.lamsfoundation.lams.events;
+
+import java.security.InvalidParameterException;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.log4j.Logger;
+import org.lamsfoundation.lams.events.dao.EventDAO;
+import org.lamsfoundation.lams.usermanagement.service.IUserManagementService;
+import org.lamsfoundation.lams.util.MessageService;
+import org.quartz.JobDetail;
+import org.quartz.Scheduler;
+import org.quartz.SchedulerException;
+import org.quartz.SimpleTrigger;
+import org.quartz.Trigger;
+
+/**
+ * Provides tools for managing events and notifing users.
+ * @author Marcin Cieslak
+ *
+ */
+class EventNotificationService implements IEventNotificationService {
+ /**
+ * The only instance of this class, since it is a singleton.
+ */
+ private static EventNotificationService instance;
+
+ /**
+ * Scope for events that were created after {@link #sendMessage(Long, AbstractDeliveryMethod, String, String)} failed.
+ */
+ protected static final String SINGLE_MESSAGE_SCOPE = "SINGLE_MESSAGE";
+
+ /**
+ * Contains message delivery methods that are available for programmers.
+ */
+ protected final static Set availableDeliveryMethods = new HashSet(2);
+
+ /**
+ * Contains events that are currently in use.
+ * Prevents multiple loading of an event from the database.
+ */
+ protected final static Set eventPool = new HashSet();
+
+ protected static final Logger log = Logger.getLogger(EventNotificationService.class);
+
+ /**
+ * How often the attempts to resend messages should be taken (in miliseconds). Currently - every hour.
+ */
+ private static final long RESEND_FREQUENCY = 60 * 60 * 1000;
+
+ /**
+ * Interface to contact the database.
+ */
+ protected EventDAO eventDAO;
+
+ /**
+ * Interface to receive contact details of the users.
+ */
+ protected IUserManagementService userManagementService;
+
+ protected MessageService messageService;
+
+ /**
+ * Quartz scheduler used for resending messages.
+ */
+ private Scheduler scheduler;
+
+ /**
+ * Default constructor. Should be called only once, since this class in a singleton.
+ * @param scheduler scheduler injected by Spring
+ */
+ public EventNotificationService(Scheduler scheduler) {
+ if (EventNotificationService.instance == null) {
+ EventNotificationService.instance = this;
+ this.scheduler = scheduler;
+ EventNotificationService.availableDeliveryMethods.add(IEventNotificationService.DELIVERY_METHOD_MAIL);
+
+ JobDetail resendMessagesJobDetail = new JobDetail("Resend Messages Job", Scheduler.DEFAULT_GROUP,
+ ResendMessagesJob.class);
+ resendMessagesJobDetail.setDescription("");
+ Trigger resendMessageTrigger = new SimpleTrigger("Resend Messages Job trigger", Scheduler.DEFAULT_GROUP,
+ SimpleTrigger.REPEAT_INDEFINITELY, EventNotificationService.RESEND_FREQUENCY);
+
+ try {
+ getScheduler().start();
+ getScheduler().scheduleJob(resendMessagesJobDetail, resendMessageTrigger);
+ }
+ catch (SchedulerException e) {
+
+ EventNotificationService.log.error(e.getMessage());
+ }
+ }
+ }
+
+ /**
+ * Gets the only existing instance of the class.
+ * @return instance of this class
+ */
+ public static EventNotificationService getInstance() {
+ return EventNotificationService.instance;
+ }
+
+ /**
+ * Allows to plug-in a delivery method.
+ * @param deliveryMethod delivery method to add
+ * @return true
if the delivery method with the same ID did not exist and the given delivery method was added successfuly
+ */
+ public boolean addDeliveryMethod(AbstractDeliveryMethod deliveryMethod) {
+ return EventNotificationService.availableDeliveryMethods.add(deliveryMethod);
+ }
+
+ /* (non-Javadoc)
+ * @see org.lamsfoundation.lams.events.IEventNotificationService#createEvent(java.lang.String, java.lang.String, java.lang.Long, java.lang.String, java.lang.String)
+ */
+ public boolean createEvent(String scope, String name, Long eventSessionId, String defaultSubject, String defaultMessage)
+ throws InvalidParameterException {
+ Event event = getEvent(scope, name, eventSessionId);
+ if (event != null) {
+ saveEvent(event);
+ return false;
+ }
+ event = new Event(scope, name, eventSessionId, defaultSubject, defaultMessage);
+ event.referenceCounter++;
+ saveEvent(event);
+ return true;
+ }
+
+ /* (non-Javadoc)
+ * @see org.lamsfoundation.lams.events.IEventNotificationService#deleteEvent(java.lang.String, java.lang.String, java.lang.Long, java.lang.String)
+ */
+ public boolean deleteEvent(String scope, String name, Long eventSessionId) throws InvalidParameterException {
+ if (scope == null) {
+ throw new InvalidParameterException("Scope should not be null.");
+ }
+ if (StringUtils.isEmpty(name)) {
+ throw new InvalidParameterException("Name should not be blank.");
+ }
+ Event event = getEvent(scope, name, eventSessionId);
+ if (event == null) {
+ return false;
+ }
+ else {
+ event.deleted = true;
+ saveEvent(event);
+ return true;
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.lamsfoundation.lams.events.IEventNotificationService#eventExists(java.lang.String, java.lang.String, java.lang.Long)
+ */
+ public boolean eventExists(String scope, String name, Long eventSessionId) throws InvalidParameterException {
+ if (scope == null) {
+ throw new InvalidParameterException("Scope should not be null.");
+ }
+ if (StringUtils.isEmpty(name)) {
+ throw new InvalidParameterException("Name should not be blank.");
+ }
+ Event event = getEvent(scope, name, eventSessionId);
+ if (event == null) {
+ return false;
+ }
+ else {
+ saveEvent(event);
+ return true;
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.lamsfoundation.lams.events.IEventNotificationService#getAvailableDeliveryMethods()
+ */
+ public Set getAvailableDeliveryMethods() {
+ return EventNotificationService.availableDeliveryMethods;
+ }
+
+ public EventDAO getEventDAO() {
+ return eventDAO;
+ }
+
+ /* (non-Javadoc)
+ * @see org.lamsfoundation.lams.events.IEventNotificationService#isSubscribed(java.lang.String, java.lang.String, java.lang.Long, java.lang.Long)
+ */
+ public boolean isSubscribed(String scope, String name, Long eventSessionId, Long userId) throws InvalidParameterException {
+ if (scope == null) {
+ throw new InvalidParameterException("Scope should not be null.");
+ }
+ if (StringUtils.isEmpty(name)) {
+ throw new InvalidParameterException("Name should not be blank.");
+ }
+ if (userId == null) {
+ throw new InvalidParameterException("User ID should not be null.");
+ }
+ Event event = getEvent(scope, name, eventSessionId);
+ boolean isSubscribed = false;
+ if (event != null) {
+ for (Subscription subscription : event.getSubscriptions()) {
+ if (subscription.getUserId().equals(userId)) {
+ isSubscribed = true;
+ break;
+ }
+ }
+ saveEvent(event);
+ }
+ return isSubscribed;
+ }
+
+ /* (non-Javadoc)
+ * @see org.lamsfoundation.lams.events.IEventNotificationService#sendMessage(java.lang.Long, org.lamsfoundation.lams.events.AbstractDeliveryMethod, java.lang.String, java.lang.String)
+ */
+ public boolean sendMessage(Long userId, AbstractDeliveryMethod deliveryMethod, String subject, String message)
+ throws InvalidParameterException {
+ Event eventFailCopy = new Event(EventNotificationService.SINGLE_MESSAGE_SCOPE,
+ String.valueOf(System.currentTimeMillis()), null, subject, message);
+ String result = deliveryMethod.send(userId, subject, message);
+ if (result != null) {
+ eventFailCopy.subscribe(userId, deliveryMethod, null);
+ }
+
+ if (!eventFailCopy.subscriptions.isEmpty()) {
+ eventFailCopy.setFailTime(new Date());
+ eventFailCopy.referenceCounter++;
+ saveEvent(eventFailCopy);
+ return false;
+ }
+ return true;
+ }
+
+ /* (non-Javadoc)
+ * @see org.lamsfoundation.lams.events.IEventNotificationService#sendMessage(java.lang.Long[], org.lamsfoundation.lams.events.AbstractDeliveryMethod, java.lang.String, java.lang.String)
+ */
+ public boolean sendMessage(final Long[] userId, final AbstractDeliveryMethod deliveryMethod, final String subject,
+ final String message) throws InvalidParameterException {
+ if (userId == null) {
+ throw new InvalidParameterException("User IDs array should not be null.");
+ }
+ if (deliveryMethod == null) {
+ throw new InvalidParameterException("Delivery method should not be null.");
+ }
+ if (userId.length > 0) {
+ if (userId.length == 1) {
+ return sendMessage(userId[0], deliveryMethod, subject, message);
+ }
+ else {
+ final Event event = new Event(EventNotificationService.SINGLE_MESSAGE_SCOPE, String.valueOf(System
+ .currentTimeMillis()), null, subject, message);
+ event.referenceCounter++;
+ event.notificationThread = new Thread(new Runnable() {
+ public void run() {
+ for (Long id : userId) {
+ String result = deliveryMethod.send(id, subject, message);
+ if (result != null) {
+ event.subscribe(id, deliveryMethod, null);
+ }
+ }
+ if (!event.subscriptions.isEmpty()) {
+ event.setFailTime(new Date());
+ event.setTriggered(true);
+ saveEvent(event);
+ }
+ }
+ }, "LAMS_single_message_send_thread");
+ event.notificationThread.start();
+
+ }
+ }
+ return true;
+ }
+
+ public void setEventDAO(EventDAO eventDAO) {
+ this.eventDAO = eventDAO;
+ }
+
+ public void setUserManagementService(IUserManagementService userManagementService) {
+ this.userManagementService = userManagementService;
+ }
+
+ /* (non-Javadoc)
+ * @see org.lamsfoundation.lams.events.IEventNotificationService#subscribe(java.lang.String, java.lang.String, java.lang.Long, java.lang.Long, org.lamsfoundation.lams.events.AbstractDeliveryMethod, java.lang.Long)
+ */
+ public boolean subscribe(String scope, String name, Long eventSessionId, Long userId, AbstractDeliveryMethod deliveryMethod,
+ Long periodicity) throws InvalidParameterException {
+ if (scope == null) {
+ throw new InvalidParameterException("Scope should not be null.");
+ }
+ if (StringUtils.isEmpty(name)) {
+ throw new InvalidParameterException("Name should not be blank.");
+ }
+ if (userId == null) {
+ throw new InvalidParameterException("User ID should not be null.");
+ }
+ if (deliveryMethod == null) {
+ throw new InvalidParameterException("Delivery method should not be null.");
+ }
+ Event event = getEvent(scope, name, eventSessionId);
+ if (event == null) {
+ throw new InvalidParameterException("An event with the given parameters does not exist.");
+ }
+ boolean newSubscription = true;
+ try {
+ newSubscription = event.subscribe(userId, deliveryMethod, periodicity);
+ }
+ catch (Exception e) {
+ EventNotificationService.log.error(e.getMessage());
+ }
+ saveEvent(event);
+ return newSubscription;
+
+ }
+
+ /* (non-Javadoc)
+ * @see org.lamsfoundation.lams.events.IEventNotificationService#trigger(java.lang.String, java.lang.String, java.lang.Long)
+ */
+ public boolean trigger(String scope, String name, Long eventSessionId) throws InvalidParameterException {
+ if (scope == null) {
+ throw new InvalidParameterException("Scope should not be null.");
+ }
+ if (StringUtils.isEmpty(name)) {
+ throw new InvalidParameterException("Name should not be blank.");
+ }
+ Event event = getEvent(scope, name, eventSessionId);
+ if (event == null) {
+ throw new InvalidParameterException("An event with the given parameters does not exist.");
+ }
+ try {
+ event.trigger(event.getDefaultSubject(), event.getDefaultMessage());
+ }
+ catch (Exception e) {
+ EventNotificationService.log.error(e.getMessage());
+ }
+ return true;
+ }
+
+ /* (non-Javadoc)
+ * @see org.lamsfoundation.lams.events.IEventNotificationService#trigger(java.lang.String, java.lang.String, java.lang.Long, java.lang.Object[] parameterValues)
+ */
+ public boolean trigger(String scope, String name, Long eventSessionId, Object[] parameterValues)
+ throws InvalidParameterException {
+ if (scope == null) {
+ throw new InvalidParameterException("Scope should not be null.");
+ }
+ if (StringUtils.isEmpty(name)) {
+ throw new InvalidParameterException("Name should not be blank.");
+ }
+ Event event = getEvent(scope, name, eventSessionId);
+ if (event == null) {
+ throw new InvalidParameterException("An event with the given parameters does not exist.");
+ }
+ String body = event.getDefaultMessage();
+ if (parameterValues != null && parameterValues.length > 0) {
+ for (int index = 0; index < parameterValues.length; index++) {
+ Object value = parameterValues[index];
+ String replacement = value == null ? "" : value.toString();
+ body = body.replace("{" + index + "}", replacement);
+ }
+ }
+ try {
+ event.trigger(event.getDefaultSubject(), body);
+ }
+ catch (Exception e) {
+ EventNotificationService.log.error(e.getMessage());
+ }
+ return true;
+ }
+
+ /* (non-Javadoc)
+ * @see org.lamsfoundation.lams.events.IEventNotificationService#trigger(java.lang.String, java.lang.String, java.lang.Long, java.lang.String, java.lang.String)
+ */
+ public boolean trigger(String scope, String name, Long eventSessionId, String title, String message)
+ throws InvalidParameterException {
+ if (scope == null) {
+ throw new InvalidParameterException("Scope should not be null.");
+ }
+ if (StringUtils.isEmpty(name)) {
+ throw new InvalidParameterException("Name should not be blank.");
+ }
+ Event event = getEvent(scope, name, eventSessionId);
+ if (event == null) {
+ throw new InvalidParameterException("An event with the given parameters does not exist.");
+ }
+ try {
+ event.trigger(title, message);
+ }
+ catch (Exception e) {
+ EventNotificationService.log.error(e.getMessage());
+ }
+ return true;
+ }
+
+ /* (non-Javadoc)
+ * @see org.lamsfoundation.lams.events.IEventNotificationService#triggerForSingleUser(java.lang.String, java.lang.String, java.lang.Long, java.lang.Long)
+ */
+ public boolean triggerForSingleUser(String scope, String name, Long eventSessionId, Long userId)
+ throws InvalidParameterException {
+ if (scope == null) {
+ throw new InvalidParameterException("Scope should not be null.");
+ }
+ if (StringUtils.isEmpty(name)) {
+ throw new InvalidParameterException("Name should not be blank.");
+ }
+ if (userId == null) {
+ throw new InvalidParameterException("User ID should not be null.");
+ }
+ Event event = getEvent(scope, name, eventSessionId);
+ if (event == null) {
+ throw new InvalidParameterException("An event with the given parameters does not exist.");
+ }
+ boolean notificationSuccessful = false;
+ try {
+ notificationSuccessful = event.triggerForSingleUser(userId, event.getDefaultSubject(), event.getDefaultMessage());
+ }
+ catch (Exception e) {
+ EventNotificationService.log.error(e.getMessage());
+ }
+ saveEvent(event);
+ return notificationSuccessful;
+ }
+
+ /* (non-Javadoc)
+ * @see org.lamsfoundation.lams.events.IEventNotificationService#triggerForSingleUser(java.lang.String, java.lang.String, java.lang.Long, java.lang.Long)
+ */
+ public boolean triggerForSingleUser(String scope, String name, Long eventSessionId, Long userId, Object[] parameterValues)
+ throws InvalidParameterException {
+ if (scope == null) {
+ throw new InvalidParameterException("Scope should not be null.");
+ }
+ if (StringUtils.isEmpty(name)) {
+ throw new InvalidParameterException("Name should not be blank.");
+ }
+ if (userId == null) {
+ throw new InvalidParameterException("User ID should not be null.");
+ }
+ Event event = getEvent(scope, name, eventSessionId);
+ if (event == null) {
+ throw new InvalidParameterException("An event with the given parameters does not exist.");
+ }
+ String body = event.getDefaultMessage();
+ if (parameterValues != null && parameterValues.length > 0) {
+ for (int index = 0; index < parameterValues.length; index++) {
+ Object value = parameterValues[index];
+ String replacement = value == null ? "" : value.toString();
+ body = body.replace("{" + index + "}", replacement);
+ }
+ }
+ boolean notificationSuccessful = false;
+ try {
+ notificationSuccessful = event.triggerForSingleUser(userId, event.getDefaultSubject(), body);
+ }
+ catch (Exception e) {
+ EventNotificationService.log.error(e.getMessage());
+ }
+ saveEvent(event);
+ return notificationSuccessful;
+ }
+
+ /* (non-Javadoc)
+ * @see org.lamsfoundation.lams.events.IEventNotificationService#triggerForSingleUser(java.lang.String, java.lang.String, java.lang.Long, java.lang.Long, java.lang.String, java.lang.String)
+ */
+ public boolean triggerForSingleUser(String scope, String name, Long eventSessionId, Long userId, String subject,
+ String message) throws InvalidParameterException {
+ if (scope == null) {
+ throw new InvalidParameterException("Scope should not be null.");
+ }
+ if (StringUtils.isEmpty(name)) {
+ throw new InvalidParameterException("Name should not be blank.");
+ }
+ if (userId == null) {
+ throw new InvalidParameterException("User ID should not be null.");
+ }
+ Event event = getEvent(scope, name, eventSessionId);
+ if (event == null) {
+ throw new InvalidParameterException("An event with the given parameters does not exist.");
+ }
+ boolean notificationSuccessful = false;
+ try {
+ notificationSuccessful = event.triggerForSingleUser(userId, subject, message);
+ }
+ catch (Exception e) {
+
+ EventNotificationService.log.error(e.getMessage());
+ }
+ finally {
+ saveEvent(event);
+ }
+ return notificationSuccessful;
+ }
+
+ /* (non-Javadoc)
+ * @see org.lamsfoundation.lams.events.IEventNotificationService#unsubscribe(java.lang.String, java.lang.String, java.lang.Long, java.lang.Long)
+ */
+ public boolean unsubscribe(String scope, String name, Long eventSessionId, Long userId) throws InvalidParameterException {
+ if (scope == null) {
+ throw new InvalidParameterException("Scope should not be null.");
+ }
+ if (StringUtils.isEmpty(name)) {
+ throw new InvalidParameterException("Name should not be blank.");
+ }
+ if (userId == null) {
+ throw new InvalidParameterException("User ID should not be null.");
+ }
+ Event event = getEvent(scope, name, eventSessionId);
+ if (event == null) {
+ throw new InvalidParameterException("An event with the given parameters does not exist.");
+ }
+ boolean subscriptionFound = false;
+ try {
+ subscriptionFound = event.unsubscribe(userId);
+ }
+ catch (Exception e) {
+
+ EventNotificationService.log.error(e.getMessage());
+ }
+ finally {
+ saveEvent(event);
+ }
+ return subscriptionFound;
+ }
+
+ /* (non-Javadoc)
+ * @see org.lamsfoundation.lams.events.IEventNotificationService#unsubscribe(java.lang.String, java.lang.String, java.lang.Long, java.lang.Long, org.lamsfoundation.lams.events.AbstractDeliveryMethod)
+ */
+ public boolean unsubscribe(String scope, String name, Long eventSessionId, Long userId, AbstractDeliveryMethod deliveryMethod)
+ throws InvalidParameterException {
+ if (scope == null) {
+ throw new InvalidParameterException("Scope should not be null.");
+ }
+ if (StringUtils.isEmpty(name)) {
+ throw new InvalidParameterException("Name should not be blank.");
+ }
+ if (userId == null) {
+ throw new InvalidParameterException("User ID should not be null.");
+ }
+ if (deliveryMethod == null) {
+ throw new InvalidParameterException("Delivery nethod should not be null.");
+ }
+ Event event = getEvent(scope, name, eventSessionId);
+ if (event == null) {
+ throw new InvalidParameterException("An event with the given parameters does not exist.");
+ }
+ boolean subscriptionFound = false;
+ try {
+ subscriptionFound = event.unsubscribe(userId, deliveryMethod);
+ }
+ catch (Exception e) {
+
+ EventNotificationService.log.error(e.getMessage());
+ }
+ finally {
+ saveEvent(event);
+ }
+ return subscriptionFound;
+ }
+
+ /**
+ * Gets the event, either from the database or the event pool.
+ * @param scope scope of the event
+ * @param name name of the event
+ * @param eventSessionId session ID of the event
+ * @return event if it exists and it is not deleted; null
if it does not exist
+ */
+ protected Event getEvent(String scope, String name, Long eventSessionId) {
+ String fullSignature = Event.createFullSignature(scope, name, eventSessionId);
+
+ for (Event event : EventNotificationService.eventPool) {
+ if (event.equals(fullSignature)) {
+ if (event.deleted) {
+ return null;
+ }
+ event.referenceCounter++;
+ return event;
+ }
+ }
+
+ Event result = getEventDAO().getEvent(scope, name, eventSessionId);
+ if (result == null) {
+ return null;
+ }
+ result.referenceCounter++;
+ EventNotificationService.eventPool.add(result);
+ return result;
+ }
+
+ protected IUserManagementService getUserManagementService() {
+ return userManagementService;
+ }
+
+ /**
+ * Saves the event into the database
+ * @param event event to be saved
+ */
+ protected void saveEvent(Event event) {
+ event.referenceCounter--;
+ if (event.referenceCounter <= 0) {
+ EventNotificationService.eventPool.remove(event);
+ if (event.deleted) {
+ getEventDAO().deleteEvent(event);
+ }
+ else {
+ getEventDAO().saveEvent(event);
+ }
+ }
+ }
+
+ private Scheduler getScheduler() {
+ return scheduler;
+ }
+
+ public void setMessageService(MessageService messageService) {
+ this.messageService = messageService;
+ }
+
+ protected MessageService getMessageService() {
+ return messageService;
+ }
+}
\ No newline at end of file
Index: lams_common/src/java/org/lamsfoundation/lams/events/IEventNotificationService.java
===================================================================
diff -u
--- lams_common/src/java/org/lamsfoundation/lams/events/IEventNotificationService.java (revision 0)
+++ lams_common/src/java/org/lamsfoundation/lams/events/IEventNotificationService.java (revision 6e4ed3724bd76354c2ee43c88979385c4a162a0e)
@@ -0,0 +1,225 @@
+package org.lamsfoundation.lams.events;
+
+import java.security.InvalidParameterException;
+import java.util.Set;
+
+/**
+ * Provides tools for managing events and notifing users.
+ * @author Marcin Cieslak
+ *
+ */
+public interface IEventNotificationService {
+
+ /**
+ * Scope for the events that are common for the whole LAMS environment.
+ */
+ public static final String CORE_EVENTS_SCOPE = "CORE";
+
+ /**
+ * User should be notified only once. Used when subscribing a user to an event.
+ */
+ public static final long PERIODICITY_SINGLE = 0;
+
+ /**
+ * User should be notified daily. Used when subscribing a user to an event.
+ */
+ public static final long PERIODICITY_DAILY = 24 * 60 * 60;
+
+ /**
+ * User should be notified weekly. Used when subscribing a user to an event.
+ */
+ public static final long PERIODICITY_WEEKLY = IEventNotificationService.PERIODICITY_DAILY * 7;
+
+ /**
+ * User should be notified monthly. Used when subscribing a user to an event.
+ */
+ public static final long PERIODICITY_MONTHLY = IEventNotificationService.PERIODICITY_WEEKLY * 4;
+
+ /**
+ * Allows sending mail to users using the configured SMTP server.
+ * Currently it is the only delivery method available.
+ */
+ public static final AbstractDeliveryMethod DELIVERY_METHOD_MAIL = DeliveryMethodMail.getInstance();
+
+ /**
+ * Creates an event and saves it into the database.
+ * @param scope scope of the event
+ * @param name name of the event
+ * @param eventSessionId session ID of the event
+ * @param defaultSubject subject of the message send to users; it can be altered when triggering the event
+ * @param defaultMessage body of the message send to users; it can be altered when triggering the event
+ * @return true
if the event did not exist and was correctly created
+ * @throws InvalidParameterException if scope was null
or name was blank
+ */
+ public abstract boolean createEvent(String scope, String name, Long eventSessionId, String defaultSubject,
+ String defaultMessage) throws InvalidParameterException;
+
+ /**
+ * Deletes an event.
+ * @param scope scope of the event
+ * @param name name of the event
+ * @param eventSessionId session ID of the event
+ * @return true
if the event existed and was deleted
+ * @throws InvalidParameterException if scope was null
or name was blank
+ */
+ public abstract boolean deleteEvent(String scope, String name, Long eventSessionId) throws InvalidParameterException;;
+
+ /**
+ * Checks if event with the given parameters exists in the database.
+ * @param scope scope of the event
+ * @param name name of the event
+ * @param eventSessionId session ID of the event
+ * @return true
if the event exists
+ * @throws InvalidParameterException if scope was null
or name was blank
+ */
+ public abstract boolean eventExists(String scope, String name, Long eventSessionId) throws InvalidParameterException;
+
+ /**
+ * Gets the available delivery methods that can be used when subscribing an user to an event.
+ * @return set of available delivery methods in the system
+ */
+ public abstract Set getAvailableDeliveryMethods();
+
+ /**
+ * Checks if an user is subscribed to the given event.
+ * @param scope scope of the event
+ * @param name name of the event
+ * @param eventSessionId session ID of the event
+ * @param userId ID of the user
+ * @return true
if the event exists and the user is subscribed (with at least one delivery method) to the event
+ * @throws InvalidParameterException if scope or user ID were null
, name was blank or event does not exist
+ */
+ public abstract boolean isSubscribed(String scope, String name, Long eventSessionId, Long userId)
+ throws InvalidParameterException;
+
+ /**
+ * Sends a single message to the given users.If it fails, an event is created for the needs of the resending mechanism.
+ * @param userId ID of users to send the message to
+ * @param deliveryMethod method of messaged delivery to use
+ * @param subject subject of the message to send
+ * @param message body of the message to send
+ * @return true
if the message was succefully send to the user
+ * @throws InvalidParameterException if userId or delivery method are null
+ */
+ public abstract boolean sendMessage(Long userId, AbstractDeliveryMethod deliveryMethod, String subject, String message)
+ throws InvalidParameterException;
+
+ /**
+ *
+ * Sends a single message to the given user. If it fails, an event is created for the needs of the resending mechanism.
+ * @param userId IDs of users to send the message to
+ * @param deliveryMethod method of messaged delivery to use
+ * @param subject subject of the message to send
+ * @param message body of the message to send
+ * @return true
if the message was succefully send to all the users; as in the current implementation a separate thread is used for sending messages, this method always returns true
+ * @throws InvalidParameterException if userId array or delivery method are null
+ */
+ public abstract boolean sendMessage(Long[] userId, AbstractDeliveryMethod deliveryMethod, String subject, String message)
+ throws InvalidParameterException;
+
+ /**
+ * Registeres an user for notification of the event.
+ * If a subscription with given user ID and delivery method already exists,
+ * only periodicity is updated.
+ * @param scope scope of the event
+ * @param name name of the event
+ * @param eventSessionId session ID of the event
+ * @param userId ID of the user
+ * @param deliveryMethod method of messaged delivery to use
+ * @param periodicity how often the user should be notified (in seconds)
+ * @throws InvalidParameterException if scope, userId or delivery method are null
, or name is blank
+ */
+ public abstract boolean subscribe(String scope, String name, Long eventSessionId, Long userId,
+ AbstractDeliveryMethod deliveryMethod, Long periodicity) throws InvalidParameterException;
+
+ /**
+ * Triggers the event with the default (or previously set) subject and message. Each subscribed user is notified.
+ * @param scope scope of the event
+ * @param name name of the event
+ * @param eventSessionId session ID of the event
+ * @throws InvalidParameterException if scope is null
or name is blank
+ */
+ public abstract boolean trigger(String scope, String name, Long eventSessionId) throws InvalidParameterException;
+
+ /**
+ * Triggers the event with the default subject and message, modifying placeholders ({0}, {1}, {2}
...) in the message body with the parameterValues
. Each subscribed user is notified.
+ * @param scope scope of the event
+ * @param name name of the event
+ * @param eventSessionId session ID of the event
+ * @param parameterValues values that should replace placeholders in the message body; for each object its text representation is acquired by toString()
method; then, the first string replaces {0}
tag, second one the {1}
tag and so on.
+ * @throws InvalidParameterException if scope is null
or name is blank
+ */
+ public abstract boolean trigger(String scope, String name, Long eventSessionId, Object[] parameterValues)
+ throws InvalidParameterException;
+
+ /**
+ * Triggers the event with given subject and message. Each subscribed user is notified. Default message and subject are overridden.
+ * @param scope scope of the event
+ * @param name name of the event
+ * @param eventSessionId session ID of the event
+ * @param subject subject of the message to send
+ * @param message body of the message to send
+ * @throws InvalidParameterException if scope is null
or name is blank
+ */
+ public abstract boolean trigger(String scope, String name, Long eventSessionId, String subject, String message)
+ throws InvalidParameterException;
+
+ /**
+ * Notifies only a single user of the event using the default subject and message. Does not set the event as "triggered".
+ * @param scope scope of the event
+ * @param name name of the event
+ * @param eventSessionId session ID of the event
+ * @param userId ID of the user
+ * @throws InvalidParameterException if scope or userId are null
or name is blank
+ */
+ public abstract boolean triggerForSingleUser(String scope, String name, Long eventSessionId, Long userId)
+ throws InvalidParameterException;
+
+ /**
+ * Notifies only a single user of the event using the default subject and message, modifying placeholders ({0}, {1}, {2}
...) in the message body with the parameterValues
. Does not set the event as "triggered".
+ * @param scope scope of the event
+ * @param name name of the event
+ * @param eventSessionId session ID of the event
+ * @param userId ID of the user
+ * @param parameterValues values that should replace placeholders in the message body; for each object its text representation is acquired by toString()
method; then, the first string replaces {0}
tag, second one the {1}
tag and so on.
+ * @throws InvalidParameterException if scope or userId are null
or name is blank
+ */
+ public boolean triggerForSingleUser(String scope, String name, Long eventSessionId, Long userId, Object[] parameterValues)
+ throws InvalidParameterException;
+
+ /**
+ * Notifies only a single user of the event using the given subject and message. Does not set the event as "triggered". Default subject and message are NOT overridden.
+ * @param scope scope of the event
+ * @param name name of the event
+ * @param eventSessionId session ID of the event
+ * @param userId ID of the user
+ * @param subject subject of the message to send
+ * @param message body of the message to send
+ * @throws InvalidParameterException if scope or userId are null
or name is blank
+ */
+ public abstract boolean triggerForSingleUser(String scope, String name, Long eventSessionId, Long userId, String subject,
+ String message) throws InvalidParameterException;
+
+ /**
+ * Unregister an user from notification of the event.
+ * @param scope scope of the event
+ * @param name name of the event
+ * @param eventSessionId session ID of the event
+ * @param userId ID of the user
+ * @throws InvalidParameterException if scope or userId are null
or name is blank
+ */
+ public abstract boolean unsubscribe(String scope, String name, Long eventSessionId, Long userId)
+ throws InvalidParameterException;
+
+ /**
+ * Unregister delivery method of the user from notification of the event.
+ * @param scope scope of the event
+ * @param name name of the event
+ * @param eventSessionId session ID of the event
+ * @param userId ID of the user
+ * @param deliveryMethod delivery method which should be unregistered
+ * @throws InvalidParameterException if scope, userId or delivery method are null
or name is blank
+ */
+ public abstract boolean unsubscribe(String scope, String name, Long eventSessionId, Long userId,
+ AbstractDeliveryMethod deliveryMethod) throws InvalidParameterException;
+}
\ No newline at end of file
Index: lams_common/src/java/org/lamsfoundation/lams/events/ResendMessagesJob.java
===================================================================
diff -u
--- lams_common/src/java/org/lamsfoundation/lams/events/ResendMessagesJob.java (revision 0)
+++ lams_common/src/java/org/lamsfoundation/lams/events/ResendMessagesJob.java (revision 6e4ed3724bd76354c2ee43c88979385c4a162a0e)
@@ -0,0 +1,72 @@
+package org.lamsfoundation.lams.events;
+
+import java.util.List;
+
+import org.lamsfoundation.lams.usermanagement.User;
+import org.lamsfoundation.lams.util.MessageService;
+import org.quartz.JobExecutionContext;
+import org.quartz.JobExecutionException;
+import org.springframework.scheduling.quartz.QuartzJobBean;
+
+/**
+ * Quartz job for resending messages.
+ * This job is scheduled from the start-up and periodically attempts to resend failed and marked for resending messages.
+ * @author Administrator
+ *
+ */
+public class ResendMessagesJob extends QuartzJobBean {
+ /**
+ * Period after which the thread gives up on attempting to resend messages. Currently - 2 days.
+ */
+ private static final long RESEND_TIME_LIMIT = 2 * 24 * 60 * 60 * 1000;
+
+ private static final EventNotificationService notificationService = EventNotificationService.getInstance();
+
+ private static final MessageService messageService = ResendMessagesJob.notificationService.getMessageService();
+
+ @Override
+ protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
+ try {
+ EventNotificationService.log.debug("Event notification service is running a resend messages thread...");
+ List events = ResendMessagesJob.notificationService.getEventDAO().getEventsToResend();
+ for (Event event : events) {
+ event.referenceCounter++;
+ if (event.getFailTime() != null) {
+ event.trigger(event.getSubject(), event.getMessage());
+ event.notificationThread.join();
+ if (event.getSubscriptions().isEmpty()) {
+ event.deleted = true;
+ }
+ else if (System.currentTimeMillis() - event.getFailTime().getTime() >= ResendMessagesJob.RESEND_TIME_LIMIT) {
+ event.deleted = true;
+ StringBuilder body = new StringBuilder(ResendMessagesJob.messageService
+ .getMessage("mail.resend.abandon.body1")).append(event.getDefaultMessage()).append(
+ ResendMessagesJob.messageService.getMessage("mail.resend.abandon.body2"));
+ for (Subscription subscription : event.getSubscriptions()) {
+ User user = (User) EventNotificationService.getInstance().getUserManagementService().findById(
+ User.class, subscription.getUserId().intValue());
+ body.append(user.getLogin()).append('\n');
+ }
+ ((DeliveryMethodMail) IEventNotificationService.DELIVERY_METHOD_MAIL).notifyAdmin(
+ ResendMessagesJob.messageService.getMessage("mail.resend.abandon.subject"), body.toString());
+ }
+ }
+ else {
+ for (Subscription subscription : event.getSubscriptions()) {
+ if (subscription.getLastOperationTime() != null
+ && System.currentTimeMillis() - subscription.getLastOperationTime().getTime() > subscription
+ .getPeriodicity()) {
+ String subject = event.getSubject() == null ? event.getDefaultSubject() : event.getSubject();
+ String message = event.getMessage() == null ? event.getDefaultMessage() : event.getMessage();
+ subscription.notifyUser(subject, message);
+ }
+ }
+ }
+ ResendMessagesJob.notificationService.saveEvent(event);
+ }
+ }
+ catch (Exception e) {
+ throw new JobExecutionException(e.getMessage());
+ }
+ }
+}
\ No newline at end of file
Index: lams_common/src/java/org/lamsfoundation/lams/events/Subscription.java
===================================================================
diff -u
--- lams_common/src/java/org/lamsfoundation/lams/events/Subscription.java (revision 0)
+++ lams_common/src/java/org/lamsfoundation/lams/events/Subscription.java (revision 6e4ed3724bd76354c2ee43c88979385c4a162a0e)
@@ -0,0 +1,204 @@
+package org.lamsfoundation.lams.events;
+
+import java.security.InvalidParameterException;
+import java.util.Date;
+
+import org.apache.commons.lang.builder.HashCodeBuilder;
+
+/**
+ * Subscription for an event notification.
+ * This class binds an user to an event and stores some information on the notification attempts.
+ *
+ * @hibernate.class table="lams_event_subscriptions"
+ * @author Marcin Cieslak
+ *
+ */
+class Subscription {
+ //------ persistent fields -------
+ /**
+ * Unique ID for Hibernate needs.
+ */
+ private Long uid;
+
+ /**
+ * ID of the subscribed user
+ */
+ protected Long userId;
+
+ /**
+ * ID of the delivery method used to send a message for this subscription.
+ */
+ protected Short deliveryMethodId;
+
+ /**
+ * How often should the message be resend
+ */
+ protected Long periodicity;
+
+ /**
+ * Time of the notification attempt
+ */
+ protected Date lastOperationTime;
+
+ /**
+ * Message returned by a delivery methond during the last notification attempt
+ */
+ protected String lastOperationMessage;
+
+ // --------- non-persitent fields ----------
+ /**
+ * Delivery method used to send a message.
+ */
+ protected AbstractDeliveryMethod deliveryMethod;
+
+ /**
+ * For Hibernate usage.
+ */
+ private Subscription() {
+
+ }
+
+ /**
+ * Standard consctructor used by Events.
+ * @param userId
+ * @param deliveryMethod
+ * @param periodicity
+ */
+ protected Subscription(Long userId, AbstractDeliveryMethod deliveryMethod, Long periodicity) {
+ if (deliveryMethod == null) {
+ throw new InvalidParameterException("Delivery method can not be null.");
+ }
+ this.userId = userId;
+ this.deliveryMethod = deliveryMethod;
+ setPeriodicity(periodicity);
+ deliveryMethodId = deliveryMethod.getId();
+ }
+
+ /**
+ * Only user ID and delivery method count.
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof Subscription)) {
+ return false;
+ }
+ Subscription other = (Subscription) o;
+ return other.getUserId().equals(getUserId()) && other.getDeliveryMethod().equals(getDeliveryMethod());
+ }
+
+ protected AbstractDeliveryMethod getDeliveryMethod() {
+ if (deliveryMethod == null) {
+ for (AbstractDeliveryMethod delivery : EventNotificationService.getInstance().getAvailableDeliveryMethods()) {
+ if (delivery.getId() == deliveryMethodId) {
+ deliveryMethod = delivery;
+ }
+ }
+ }
+ return deliveryMethod;
+ }
+
+ /**
+ * @hibernate.property column="last_operation_message"
+ * @return
+ */
+ protected String getLastOperationMessage() {
+ return lastOperationMessage;
+ }
+
+ protected boolean getLastOperationSuccessful() {
+ return lastOperationMessage == null;
+ }
+
+ /**
+ * @hibernate.property column="periodicity"
+ * @return
+ */
+ protected Long getPeriodicity() {
+ return periodicity;
+ }
+
+ /**
+ * @hibernate.property column="user_id"
+ * @return
+ */
+ protected Long getUserId() {
+ return userId;
+ }
+
+ @Override
+ public int hashCode() {
+ return new HashCodeBuilder().append(getUserId()).append(getDeliveryMethod()).toHashCode();
+ }
+
+ /**
+ * States if a message should be send to the user or rather this subscription should be skipped.
+ * @return if the message should be send
+ */
+ protected boolean isEligibleForNotification() {
+ return !getLastOperationSuccessful() || lastOperationTime == null
+ || System.currentTimeMillis() - lastOperationTime.getTime() > periodicity;
+ }
+
+ /**
+ * Sends the message to the user.
+ * Properties storing information of the last notification attempt are updated.
+ * @param subject subject of the message; null
if not applicable
+ * @param message message to send
+ */
+ protected void notifyUser(String subject, String message) {
+ lastOperationTime = new Date();
+ lastOperationMessage = deliveryMethod.send(userId, subject, message);
+ }
+
+ protected void setPeriodicity(Long periodicity) {
+ this.periodicity = periodicity == null ? IEventNotificationService.PERIODICITY_SINGLE : periodicity;
+ }
+
+ /**
+ * @hibernate.property column="delivery_method_id"
+ * @return
+ */
+ protected Short getDeliveryMethodId() {
+ return deliveryMethodId;
+ }
+
+ /**
+ * @hibernate.property column="last_operation_time"
+ * @return
+ */
+ protected Date getLastOperationTime() {
+ return lastOperationTime;
+ }
+
+ protected void setDeliveryMethodId(Short deliveryMethodId) {
+ this.deliveryMethodId = deliveryMethodId;
+ }
+
+ protected void setLastOperationTime(Date lastOperationTime) {
+ this.lastOperationTime = lastOperationTime;
+ }
+
+ protected void setUserId(Long userId) {
+ this.userId = userId;
+ }
+
+ /**
+ * @hibernate.id column="uid" generator-class="native"
+ */
+ private Long getUid() {
+ return uid;
+ }
+
+ private void setUid(Long uid) {
+ this.uid = uid;
+ }
+
+ @Override
+ public Object clone() {
+ return new Subscription(userId, deliveryMethod, periodicity);
+ }
+
+ protected void setLastOperationMessage(String lastOperationMessage) {
+ this.lastOperationMessage = lastOperationMessage;
+ }
+}
\ No newline at end of file
Index: lams_common/src/java/org/lamsfoundation/lams/events/dao/EventDAO.java
===================================================================
diff -u
--- lams_common/src/java/org/lamsfoundation/lams/events/dao/EventDAO.java (revision 0)
+++ lams_common/src/java/org/lamsfoundation/lams/events/dao/EventDAO.java (revision 6e4ed3724bd76354c2ee43c88979385c4a162a0e)
@@ -0,0 +1,27 @@
+package org.lamsfoundation.lams.events.dao;
+
+import java.util.List;
+
+import org.lamsfoundation.lams.events.Event;
+
+public interface EventDAO {
+ /**
+ * Gets an instance of the event.
+ * @param scope
+ * @param name
+ * @param eventSessionId
+ * @return
+ */
+ Event getEvent(String scope, String name, Long sessionId);
+
+ /**
+ * Gets events with messages that need to be resend.
+ * Either they failed to be send or they should be repeated.
+ * @return list of events
+ */
+ List getEventsToResend();
+
+ void deleteEvent(Event event);
+
+ void saveEvent(Event event);
+}
\ No newline at end of file
Index: lams_common/src/java/org/lamsfoundation/lams/events/dao/hibernate/EventDAOHibernate.java
===================================================================
diff -u
--- lams_common/src/java/org/lamsfoundation/lams/events/dao/hibernate/EventDAOHibernate.java (revision 0)
+++ lams_common/src/java/org/lamsfoundation/lams/events/dao/hibernate/EventDAOHibernate.java (revision 6e4ed3724bd76354c2ee43c88979385c4a162a0e)
@@ -0,0 +1,42 @@
+package org.lamsfoundation.lams.events.dao.hibernate;
+
+import java.security.InvalidParameterException;
+import java.util.List;
+
+import org.lamsfoundation.lams.events.Event;
+import org.lamsfoundation.lams.events.dao.EventDAO;
+import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
+
+class EventDAOHibernate extends HibernateDaoSupport implements EventDAO {
+
+ protected static final String GET_EVENT_QUERY = "FROM " + Event.class.getName()
+ + " AS e WHERE e.scope=? AND e.name=? AND e.eventSessionId=? AND e.failTime IS NULL";
+
+ protected static final String GET_EVENTS_TO_RESEND_QUERY = "SELECT DISTINCT e FROM " + Event.class.getName()
+ + " AS e LEFT JOIN FETCH e.subscriptions WHERE e.failTime IS NOT NULL OR "
+ + "(e.subscriptions.periodicity > 0 AND (NOW()- e.subscriptions.lastOperationTime >= e.subscriptions.periodicity))";
+
+ public Event getEvent(String scope, String name, Long sessionId) throws InvalidParameterException {
+ List events = getHibernateTemplate().find(EventDAOHibernate.GET_EVENT_QUERY,
+ new Object[] { scope, name, sessionId });
+ if (events.size() > 1) {
+ throw new InvalidParameterException("Two events with the same parameters exist in the database.");
+ }
+ if (events.size() == 0) {
+ return null;
+ }
+ return events.get(0);
+ }
+
+ public List getEventsToResend() {
+ return getHibernateTemplate().find(EventDAOHibernate.GET_EVENTS_TO_RESEND_QUERY);
+ }
+
+ public void deleteEvent(Event event) {
+ getHibernateTemplate().delete(event);
+ }
+
+ public void saveEvent(Event event) {
+ getHibernateTemplate().saveOrUpdate(event);
+ }
+}
\ No newline at end of file