HONEYCOMB-170 Add more information to RevertFailedEx
[honeycomb.git] / infra / translate-api / src / main / java / io / fd / honeycomb / translate / write / registry / WriterRegistry.java
1 /*
2  * Copyright (c) 2016 Cisco and/or its affiliates.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at:
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package io.fd.honeycomb.translate.write.registry;
18
19 import static com.google.common.base.Preconditions.checkNotNull;
20
21 import com.google.common.annotations.Beta;
22 import com.google.common.collect.Multimap;
23 import com.google.common.collect.Sets;
24 import io.fd.honeycomb.translate.TranslationException;
25 import io.fd.honeycomb.translate.write.DataObjectUpdate;
26 import io.fd.honeycomb.translate.write.WriteContext;
27 import io.fd.honeycomb.translate.write.Writer;
28 import java.util.Set;
29 import javax.annotation.Nonnull;
30 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
31
32 /**
33  * Special {@link Writer} capable of performing bulk updates.
34  */
35 @Beta
36 public interface WriterRegistry {
37
38     /**
39      * Performs bulk update.
40      *
41      * @throws BulkUpdateException in case bulk update fails
42      * @throws TranslationException in case some other error occurs while processing update request
43      */
44     void update(@Nonnull DataObjectUpdates updates,
45                 @Nonnull WriteContext ctx) throws TranslationException;
46
47     /**
48      * Simple DTO containing updates for {@link WriterRegistry}. Currently only deletes and updates (create + update)
49      * are distinguished.
50      */
51     @Beta
52     final class DataObjectUpdates {
53
54         private final Multimap<InstanceIdentifier<?>, DataObjectUpdate> updates;
55         private final Multimap<InstanceIdentifier<?>, DataObjectUpdate.DataObjectDelete> deletes;
56
57         /**
58          * Create new instance.
59          *
60          * @param updates All updates indexed by their unkeyed {@link InstanceIdentifier}
61          * @param deletes All deletes indexed by their unkeyed {@link InstanceIdentifier}
62          */
63         public DataObjectUpdates(@Nonnull final Multimap<InstanceIdentifier<?>, DataObjectUpdate> updates,
64                                  @Nonnull final Multimap<InstanceIdentifier<?>, DataObjectUpdate.DataObjectDelete> deletes) {
65             this.deletes = deletes;
66             this.updates = updates;
67         }
68
69         public Multimap<InstanceIdentifier<?>, DataObjectUpdate> getUpdates() {
70             return updates;
71         }
72
73         public Multimap<InstanceIdentifier<?>, DataObjectUpdate.DataObjectDelete> getDeletes() {
74             return deletes;
75         }
76
77         public boolean isEmpty() {
78             return updates.isEmpty() && deletes.isEmpty();
79         }
80
81         @Override
82         public String toString() {
83             return "DataObjectUpdates{" + "updates=" + updates + ", deletes=" + deletes + '}';
84         }
85
86         /**
87          * Get a {@link Set} containing all update types from both updates as well as deletes.
88          */
89         public Set<InstanceIdentifier<?>> getTypeIntersection() {
90             return Sets.union(deletes.keySet(), updates.keySet());
91         }
92
93         /**
94          * Check whether there is only a single type of data object to be updated.
95          *
96          * @return true if there is only a single type of updates (update + delete)
97          */
98         public boolean containsOnlySingleType() {
99             return getTypeIntersection().size() == 1;
100         }
101
102         @Override
103         public boolean equals(final Object other) {
104             if (this == other) {
105                 return true;
106             }
107             if (other == null || getClass() != other.getClass()) {
108                 return false;
109             }
110
111             final DataObjectUpdates that = (DataObjectUpdates) other;
112
113             if (!updates.equals(that.updates)) {
114                 return false;
115             }
116             return deletes.equals(that.deletes);
117
118         }
119
120         @Override
121         public int hashCode() {
122             int result = updates.hashCode();
123             result = 31 * result + deletes.hashCode();
124             return result;
125         }
126
127     }
128
129     /**
130      * Thrown when bulk update failed.
131      */
132     @Beta
133     class BulkUpdateException extends TranslationException {
134
135         private final transient Reverter reverter;
136         private final InstanceIdentifier<?> failedSubtree;
137         private final DataObjectUpdate failedData;
138         private final Set<InstanceIdentifier<?>> unrevertedSubtrees;
139
140         /**
141          * Constructs an BulkUpdateException.
142          * @param unhandledSubtrees instance identifiers of the data objects that were not processed during bulk update.
143          * @param cause the cause of bulk update failure
144          */
145         public BulkUpdateException(@Nonnull final InstanceIdentifier<?> failedSubtree,
146                                    @Nonnull final DataObjectUpdate failedData,
147                                    @Nonnull final Set<InstanceIdentifier<?>> unhandledSubtrees,
148                                    @Nonnull final Reverter reverter,
149                                    @Nonnull final Throwable cause) {
150             super("Bulk update failed at: " + failedSubtree + " ignored updates: " + unhandledSubtrees, cause);
151             this.failedSubtree = failedSubtree;
152             this.failedData = failedData;
153             this.unrevertedSubtrees = unhandledSubtrees;
154             this.reverter = checkNotNull(reverter, "reverter should not be null");
155         }
156
157         /**
158          * Reverts changes that were successfully applied during bulk update before failure occurred.
159          *
160          * @param writeContext Non-closed {@code WriteContext} to be used by reverting logic.<br> <b>Do not use same
161          *                     write context as was used in previous write</b>
162          * @throws Reverter.RevertFailedException if revert fails
163          */
164         public void revertChanges(@Nonnull final WriteContext writeContext) throws Reverter.RevertFailedException {
165             reverter.revert(writeContext);
166         }
167
168         public Set<InstanceIdentifier<?>> getUnrevertedSubtrees() {
169             return unrevertedSubtrees;
170         }
171
172         public InstanceIdentifier<?> getFailedSubtree() {
173             return failedSubtree;
174         }
175
176         public DataObjectUpdate getFailedData() {
177             return failedData;
178         }
179     }
180
181     /**
182      * Abstraction over revert mechanism in case of a bulk update failure.
183      */
184     @Beta
185     interface Reverter {
186
187         /**
188          * Reverts changes that were successfully applied during bulk update before failure occurred. Changes are
189          * reverted in reverse order they were applied.
190          * Used {@code WriteContext} needs to be in non-closed state, creating fresh one for revert
191          * is recommended, same way as for write, to allow {@code Reverter} use same logic as write.
192          *
193          * @param writeContext Non-closed {@code WriteContext} to be used by reverting logic
194          * @throws RevertFailedException if not all of applied changes were successfully reverted
195          */
196         void revert(@Nonnull final WriteContext writeContext) throws RevertFailedException;
197
198         /**
199          * Thrown when some of the changes applied during bulk update were not reverted.
200          */
201         @Beta
202         class RevertFailedException extends TranslationException {
203
204             /**
205              * Constructs a RevertFailedException with the list of changes that were not reverted.
206              *
207              * @param cause              the cause of revert failure
208              */
209             public RevertFailedException(@Nonnull final BulkUpdateException cause) {
210                 super("Unable to revert changes after failure. Revert failed for "
211                         + cause.getFailedSubtree() + " unreverted subtrees: " + cause.getUnrevertedSubtrees(), cause);
212             }
213
214             /**
215              * Returns the list of changes that were not reverted.
216              *
217              * @return list of changes that were not reverted
218              */
219             @Nonnull
220             public Set<InstanceIdentifier<?>> getNotRevertedChanges() {
221                 return ((BulkUpdateException) getCause()).getUnrevertedSubtrees();
222             }
223
224             /**
225              * Returns the update that caused the failure.
226              *
227              * @return update that caused the failure
228              */
229             @Nonnull
230             public DataObjectUpdate getFailedUpdate() {
231                 return ((BulkUpdateException) getCause()).getFailedData();
232             }
233         }
234
235         /**
236          * Thrown after bulk operation was successfully reverted,
237          * to maintain marking of transaction as failed,without double logging of
238          * cause of update fail(its logged before reverting in ModifiableDataTreeDelegator
239          */
240         @Beta
241         class RevertSuccessException extends TranslationException {
242             private final Set<InstanceIdentifier<?>> failedIds;
243
244             /**
245              * Constructs an RevertSuccessException.
246              *
247              * @param failedIds instance identifiers of the data objects that were not processed during bulk update.
248              */
249             public RevertSuccessException(@Nonnull final Set<InstanceIdentifier<?>> failedIds) {
250                 super("Bulk update failed for: " + failedIds);
251                 this.failedIds = failedIds;
252             }
253
254             public Set<InstanceIdentifier<?>> getFailedIds() {
255                 return failedIds;
256             }
257         }
258     }
259 }