summaryrefslogtreecommitdiff
path: root/src/main/java/com/amazon/carbonado/Trigger.java
blob: df737e816d95e5da235291950d0e1e4621f3047f (plain)
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
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
/*
 * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
 * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
 * of Amazon Technologies, Inc. or its affiliates.  All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.amazon.carbonado;

/**
 * Callback mechanism to allow custom code to run when a storable is
 * persisted. By default, the methods defined in this class do
 * nothing. Subclass and override trigger conditions of interest, and then
 * {@link Storage#addTrigger register} it. Each overridden trigger method is
 * called in the same transaction scope as the persist operation. Trigger
 * implementations are encouraged to override the equals method, to prevent
 * accidental double registration.
 *
 * <p>To ensure proper nesting, all "before" events are run in the
 * <em>opposite</em> order that the trigger was registered. All "after" and
 * "failed" events are run in the same order that the trigger was registered.
 * In other words, the last added trigger is at the outermost nesting level.
 *
 * <p>Triggers always run within the same transaction as the triggering
 * operation. The exact isolation level and update mode is outside the
 * trigger's control. If an explicit isolation level or update mode is
 * required, create a nested transaction within a trigger method. A trigger's
 * nested transaction can also be defined span the entire triggering operation.
 * To do this, enter the transaction in the "before" method, but return the
 * transaction object without exiting it. The "after" method is responsible for
 * exiting the transaction. It extracts (or simply casts) the transaction from
 * the state object passed into it. When creating spanning transactions like
 * this, it is critical that the "failed" method be defined to properly exit
 * the transaction upon failure.
 *
 * @author Brian S O'Neill
 */
public abstract class Trigger<S> {
    /**
     * Called before a storable is to be inserted. The default implementation
     * does nothing.
     *
     * <p>Any exception thrown by this method will cause the insert operation
     * to rollback and all remaining triggers to not run. The exception is
     * ultimately passed to the caller of the insert method.
     *
     * @param storable storable before being inserted
     * @return arbitrary state object, passed to afterInsert or failedInsert method
     */
    public Object beforeInsert(S storable) throws PersistException {
        return null;
    }

    /**
     * Called before a storable is to be inserted via tryInsert. The default
     * implementation simply calls {@link #beforeInsert}. Only override if
     * trigger needs to distinguish between different insert variants.
     *
     * <p>Any exception thrown by this method will cause the tryInsert operation
     * to rollback and all remaining triggers to not run. The exception is
     * ultimately passed to the caller of the tryInsert method.
     *
     * @param storable storable before being inserted
     * @return arbitrary state object, passed to afterTryInsert or failedInsert method
     * @see #abortTry
     */
    public Object beforeTryInsert(S storable) throws PersistException {
        return beforeInsert(storable);
    }

    /**
     * Called right after a storable has been successfully inserted. The
     * default implementation does nothing.
     *
     * <p>Any exception thrown by this method will cause the insert operation
     * to rollback and all remaining triggers to not run. The exception is
     * ultimately passed to the caller of the insert method.
     *
     * @param storable storable after being inserted
     * @param state object returned by beforeInsert method
     */
    public void afterInsert(S storable, Object state) throws PersistException {
    }

    /**
     * Called right after a storable has been successfully inserted via
     * tryInsert. The default implementation simply calls {@link #afterInsert}.
     * Only override if trigger needs to distinguish between different insert
     * variants.
     *
     * <p>Any exception thrown by this method will cause the tryInsert
     * operation to rollback and all remaining triggers to not run. The
     * exception is ultimately passed to the caller of the tryInsert method.
     *
     * @param storable storable after being inserted
     * @param state object returned by beforeTryInsert method
     * @see #abortTry
     */
    public void afterTryInsert(S storable, Object state) throws PersistException {
        afterInsert(storable, state);
    }

    /**
     * Called when an insert operation failed due to a unique constraint
     * violation or an exception was thrown. The main purpose of this method is
     * to allow any necessary clean-up to occur on the optional state object.
     *
     * <p>Any exception thrown by this method will be passed to the current
     * thread's uncaught exception handler.
     *
     * @param storable storable which failed to be inserted
     * @param state object returned by beforeInsert method, but it may be null
     */
    public void failedInsert(S storable, Object state) {
    }

    /**
     * Called before a storable is to be updated. The default implementation
     * does nothing.
     *
     * <p>Any exception thrown by this method will cause the update operation
     * to rollback and all remaining triggers to not run. The exception is
     * ultimately passed to the caller of the update method.
     *
     * @param storable storable before being updated
     * @return arbitrary state object, passed to afterUpdate or failedUpdate method
     */
    public Object beforeUpdate(S storable) throws PersistException {
        return null;
    }

    /**
     * Called before a storable is to be updated via tryUpdate. The default
     * implementation simply calls {@link #beforeUpdate}. Only override if
     * trigger needs to distinguish between different update variants.
     *
     * <p>Any exception thrown by this method will cause the tryUpdate operation
     * to rollback and all remaining triggers to not run. The exception is
     * ultimately passed to the caller of the tryUpdate method.
     *
     * @param storable storable before being updated
     * @return arbitrary state object, passed to afterTryUpdate or failedUpdate method
     * @see #abortTry
     */
    public Object beforeTryUpdate(S storable) throws PersistException {
        return beforeUpdate(storable);
    }

    /**
     * Called right after a storable has been successfully updated. The default
     * implementation does nothing.
     *
     * <p>Any exception thrown by this method will cause the update operation
     * to rollback and all remaining triggers to not run. The exception is
     * ultimately passed to the caller of the update method.
     *
     * @param storable storable after being updated
     * @param state optional object returned by beforeUpdate method
     */
    public void afterUpdate(S storable, Object state) throws PersistException {
    }

    /**
     * Called right after a storable has been successfully updated via
     * tryUpdate. The default implementation simply calls {@link #afterUpdate}.
     * Only override if trigger needs to distinguish between different update
     * variants.
     *
     * <p>Any exception thrown by this method will cause the tryUpdate
     * operation to rollback and all remaining triggers to not run. The
     * exception is ultimately passed to the caller of the tryUpdate method.
     *
     * @param storable storable after being updated
     * @param state object returned by beforeTryUpdate method
     * @see #abortTry
     */
    public void afterTryUpdate(S storable, Object state) throws PersistException {
        afterUpdate(storable, state);
    }

    /**
     * Called when an update operation failed because the record was missing or
     * an exception was thrown. The main purpose of this method is to allow any
     * necessary clean-up to occur on the optional state object.
     *
     * <p>Any exception thrown by this method will be passed to the current
     * thread's uncaught exception handler.
     *
     * @param storable storable which failed to be updated
     * @param state optional object returned by beforeUpdate
     * method, but it may be null
     */
    public void failedUpdate(S storable, Object state) {
    }

    /**
     * Called before a storable is to be deleted. The default implementation
     * does nothing.
     *
     * <p>Any exception thrown by this method will cause the delete operation
     * to rollback and all remaining triggers to not run. The exception is
     * ultimately passed to the caller of the delete method.
     *
     * @param storable storable before being deleted
     * @return arbitrary state object, passed to afterDelete or failedDelete method
     */
    public Object beforeDelete(S storable) throws PersistException {
        return null;
    }

    /**
     * Called before a storable is to be deleted via tryDelete. The default
     * implementation simply calls {@link #beforeDelete}. Only override if
     * trigger needs to distinguish between different delete variants.
     *
     * <p>Any exception thrown by this method will cause the tryDelete operation
     * to rollback and all remaining triggers to not run. The exception is
     * ultimately passed to the caller of the tryDelete method.
     *
     * @param storable storable before being deleted
     * @return arbitrary state object, passed to afterTryDelete or failedDelete method
     * @see #abortTry
     */
    public Object beforeTryDelete(S storable) throws PersistException {
        return beforeDelete(storable);
    }

    /**
     * Called right after a storable has been successfully deleted. The default
     * implementation does nothing.
     *
     * <p>Any exception thrown by this method will cause the delete operation
     * to rollback and all remaining triggers to not run. The exception is
     * ultimately passed to the caller of the delete method.
     *
     * @param storable storable after being deleted
     * @param state optional object returned by beforeDelete method
     */
    public void afterDelete(S storable, Object state) throws PersistException {
    }

    /**
     * Called right after a storable has been successfully deleted via
     * tryDelete. The default implementation simply calls {@link #afterDelete}.
     * Only override if trigger needs to distinguish between different delete
     * variants.
     *
     * <p>Any exception thrown by this method will cause the tryDelete
     * operation to rollback and all remaining triggers to not run. The
     * exception is ultimately passed to the caller of the tryDelete method.
     *
     * @param storable storable after being deleted
     * @param state object returned by beforeTryDelete method
     * @see #abortTry
     */
    public void afterTryDelete(S storable, Object state) throws PersistException {
        afterDelete(storable, state);
    }

    /**
     * Called when an delete operation failed because the record was missing or
     * an exception was thrown. The main purpose of this method is to allow any
     * necessary clean-up to occur on the optional state object.
     *
     * <p>Any exception thrown by this method will be passed to the current
     * thread's uncaught exception handler.
     *
     * @param storable storable which failed to be deleted
     * @param state optional object returned by beforeDelete
     * method, but it may be null
     */
    public void failedDelete(S storable, Object state) {
    }

    /**
     * Call to quickly abort a "try" operation, returning false to the
     * caller. This method should not be called by a non-try trigger method,
     * since the caller gets thrown an exception with an incomplete stack trace.
     *
     * <p>This method never returns normally, but as a convenience, a return
     * type is defined. The abort exception can be thrown by {@code throw abortTry()},
     * but the {@code throw} keyword is not needed.
     */
    protected Abort abortTry() throws Abort {
        // Throwing and catching an exception is not terribly expensive, but
        // creating a new exception is more than an order of magnitude slower.
        // Therefore, re-use the same instance. It has no stack trace since it
        // would be meaningless.
        throw Abort.INSTANCE;
    }

    public static final class Abort extends PersistException {
        private static final long serialVersionUID = -8498639796139966911L;

        static final Abort INSTANCE = new Abort();

        private Abort() {
            super("Trigger aborted operation", null);
        }

        private Abort(String message) {
            super(message);
            super.fillInStackTrace();
        }

        /**
         * Override to remove the stack trace.
         */
        @Override
        public Throwable fillInStackTrace() {
            return null;
        }

        /**
         * Returns this exception but with a fresh stack trace. The trace does
         * not include the original thrower of this exception.
         */
        public Abort withStackTrace() {
            Abort a = new Abort(getMessage());

            StackTraceElement[] trace = a.getStackTrace();
            if (trace != null && trace.length > 1) {
                // Trim off this method from the trace, which is element 0.
                StackTraceElement[] trimmed = new StackTraceElement[trace.length - 1];
                System.arraycopy(trace, 1, trimmed, 0, trimmed.length);
                a.setStackTrace(trimmed);
            }

            return a;
        }
    }
}