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