-
Notifications
You must be signed in to change notification settings - Fork 51
Expand file tree
/
Copy pathTriggerManager.java
More file actions
210 lines (193 loc) · 6.09 KB
/
TriggerManager.java
File metadata and controls
210 lines (193 loc) · 6.09 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
/* Copyright (c) restSQL Project Contributors. Licensed under MIT. */
package org.restsql.core.impl;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.StringTokenizer;
import org.restsql.core.Config;
import org.restsql.core.Request;
import org.restsql.core.SqlResourceException;
import org.restsql.core.Trigger;
/**
* Manages trigger instantiation and execution. Triggers are defined in a properties file, whose location is referenced
* in the restSQL properties file.
*
* @author Mark Sawers
*/
public class TriggerManager {
static final String TOKEN_WILDCARD = "*";
private static final int DEFAULT_SUBLIST_SIZE = 5;
// Map<sqlResource, List<Trigger>> -- trigger may repeat
private static Map<String, List<Trigger>> triggers;
/**
* Executes all trigger defined for the resource.
*
* @param sqlResource SqlResource name
* @param request restSQL request
* @param before true if before execution; false if after execution
* @throws SqlResourceException if a trigger exception is thrown or database error occurs
*/
public static void executeTriggers(final String sqlResource, final Request request, final boolean before)
throws SqlResourceException {
// Load triggers if necessary
if (triggers == null) {
Properties definitions = getTriggerDefinitions();
loadTriggers(definitions);
}
// Execute triggers scoped to this particulal SqlResource
if (triggers.containsKey(sqlResource)) {
for (final Trigger trigger : triggers.get(sqlResource)) {
executeTrigger(request, trigger, before);
}
}
// Execute triggers scoped to all SqlResources
if (triggers.containsKey(TOKEN_WILDCARD)) {
for (final Trigger trigger : triggers.get(TOKEN_WILDCARD)) {
executeTrigger(request, trigger, before);
}
}
}
// Package-level utils, also for testing
/**
* Adds trigger with the specified scope to the triggers data structure. Access is package-level to allow for
* testing.
*/
static void addTrigger(final Trigger trigger, final String sqlResources) {
if (triggers == null) {
triggers = new HashMap<String, List<Trigger>>();
}
if (sqlResources != null && sqlResources.length() > 0) {
final StringTokenizer tokenizer = new StringTokenizer(sqlResources, ",");
while (tokenizer.hasMoreTokens()) {
final String sqlResource = tokenizer.nextToken();
List<Trigger> subList = triggers.get(sqlResource);
if (subList == null) {
subList = new ArrayList<Trigger>(DEFAULT_SUBLIST_SIZE);
triggers.put(sqlResource, subList);
}
subList.add(trigger);
}
}
}
/**
* Loads classes and creates objects and then populates the triggers data structure. Package level access for
* testability.
*/
@SuppressWarnings("unchecked")
static void loadTriggers(final Properties definitions) {
String triggersClasspath = Config.properties.getProperty(Config.KEY_TRIGGERS_CLASSPATH, null);
if (Config.logger.isInfoEnabled()) {
if (triggersClasspath != null) {
Config.logger.info("Loading triggers from classpath " + triggersClasspath);
} else {
Config.logger.info("Loading triggers from system classpath");
}
}
for (final String triggerClassName : definitions.stringPropertyNames()) {
URLClassLoader classLoader = null;
try {
Class<Trigger> triggerClass;
if (triggersClasspath != null) {
File dir = new File(triggersClasspath);
URL url = dir.toURI().toURL();
URL[] urls = new URL[] { url };
classLoader = new URLClassLoader(urls, Trigger.class.getClassLoader());
triggerClass = (Class<Trigger>) classLoader.loadClass(triggerClassName);
} else {
triggerClass = (Class<Trigger>) Class.forName(triggerClassName);
}
final Trigger trigger = triggerClass.newInstance();
addTrigger(trigger, definitions.getProperty(triggerClassName));
} catch (final Exception exception) {
Config.logger.error("Failed to load trigger " + triggerClassName, exception);
// If we were pure 1.7, then we could close the class loader
// } finally {
// if (classLoader != null) {
// try {
// classLoader.close();
// } catch (Throwable t) {
// }
// }
}
}
if (triggers == null) {
triggers = new HashMap<String, List<Trigger>>();
}
}
// Private utils
/**
* Executes trigger method appropriate for the request.
*/
private static void executeTrigger(final Request request, final Trigger trigger, final boolean before)
throws SqlResourceException {
switch (request.getType()) {
case SELECT:
if (before) {
trigger.beforeSelect(request);
} else {
trigger.afterSelect(request);
}
break;
case INSERT:
if (before) {
trigger.beforeInsert(request);
} else {
trigger.afterInsert(request);
}
break;
case UPDATE:
if (before) {
trigger.beforeUpdate(request);
} else {
trigger.afterUpdate(request);
}
break;
case DELETE:
if (before) {
trigger.beforeDelete(request);
} else {
trigger.afterDelete(request);
}
break;
}
}
/**
* Loads trigger definition properties file and then .
*/
private static Properties getTriggerDefinitions() {
final Properties definitions = new Properties();
final String fileName = Config.properties.getProperty(Config.KEY_TRIGGERS_DEFINITION, null);
if (fileName != null) {
InputStream inputStream = null;
try {
final File file = new File(fileName);
if (file.exists()) {
inputStream = new FileInputStream(file);
} else {
inputStream = Config.class.getResourceAsStream(fileName);
}
definitions.load(inputStream);
if (Config.logger.isInfoEnabled()) {
Config.logger.info("Loading trigger definitions from " + fileName);
}
} catch (final Exception exception) {
Config.logger.error("Failed to load trigger definitions " + fileName, exception);
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (Throwable t) {
}
}
}
}
return definitions;
}
}