Prevent crash on more invalid links.
[Sone.git] / src / main / java / net / pterodactylus / sone / freenet / wot / IdentityManager.java
1 /*
2  * Sone - IdentityManager.java - Copyright © 2010 David Roden
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16  */
17
18 package net.pterodactylus.sone.freenet.wot;
19
20 import java.util.Collections;
21 import java.util.HashMap;
22 import java.util.Map;
23 import java.util.Map.Entry;
24 import java.util.Set;
25 import java.util.logging.Level;
26 import java.util.logging.Logger;
27
28 import net.pterodactylus.util.logging.Logging;
29 import net.pterodactylus.util.service.AbstractService;
30
31 /**
32  * The identity manager takes care of loading and storing identities, their
33  * contexts, and properties. It does so in a way that does not expose errors via
34  * exceptions but it only logs them and tries to return sensible defaults.
35  * <p>
36  * It is also responsible for polling identities from the Web of Trust plugin
37  * and notifying registered {@link IdentityListener}s when {@link Identity}s and
38  * {@link OwnIdentity}s are discovered or disappearing.
39  *
40  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
41  */
42 public class IdentityManager extends AbstractService {
43
44         /** Object used for synchronization. */
45         private final Object syncObject = new Object() {
46                 /* inner class for better lock names. */
47         };
48
49         /** The logger. */
50         private static final Logger logger = Logging.getLogger(IdentityManager.class);
51
52         /** The event manager. */
53         private final IdentityListenerManager identityListenerManager = new IdentityListenerManager();
54
55         /** The Web of Trust connector. */
56         private final WebOfTrustConnector webOfTrustConnector;
57
58         /** The context to filter for. */
59         private volatile String context;
60
61         /** The currently known own identities. */
62         /* synchronize access on syncObject. */
63         private Map<String, OwnIdentity> currentOwnIdentities = new HashMap<String, OwnIdentity>();
64
65         /**
66          * Creates a new identity manager.
67          *
68          * @param webOfTrustConnector
69          *            The Web of Trust connector
70          */
71         public IdentityManager(WebOfTrustConnector webOfTrustConnector) {
72                 super("Sone Identity Manager", false);
73                 this.webOfTrustConnector = webOfTrustConnector;
74         }
75
76         //
77         // LISTENER MANAGEMENT
78         //
79
80         /**
81          * Adds a listener for identity events.
82          *
83          * @param identityListener
84          *            The listener to add
85          */
86         public void addIdentityListener(IdentityListener identityListener) {
87                 identityListenerManager.addListener(identityListener);
88         }
89
90         /**
91          * Removes a listener for identity events.
92          *
93          * @param identityListener
94          *            The listener to remove
95          */
96         public void removeIdentityListener(IdentityListener identityListener) {
97                 identityListenerManager.removeListener(identityListener);
98         }
99
100         //
101         // ACCESSORS
102         //
103
104         /**
105          * Sets the context to filter own identities and trusted identities for.
106          *
107          * @param context
108          *            The context to filter for, or {@code null} to not filter
109          */
110         public void setContext(String context) {
111                 this.context = context;
112         }
113
114         /**
115          * Returns whether the Web of Trust plugin could be reached during the last
116          * try.
117          *
118          * @return {@code true} if the Web of Trust plugin is connected,
119          *         {@code false} otherwise
120          */
121         public boolean isConnected() {
122                 try {
123                         webOfTrustConnector.ping();
124                         return true;
125                 } catch (PluginException pe1) {
126                         /* not connected, ignore. */
127                         return false;
128                 }
129         }
130
131         /**
132          * Returns the own identity with the given ID.
133          *
134          * @param id
135          *            The ID of the own identity
136          * @return The own identity, or {@code null} if there is no such identity
137          */
138         public OwnIdentity getOwnIdentity(String id) {
139                 Set<OwnIdentity> allOwnIdentities = getAllOwnIdentities();
140                 for (OwnIdentity ownIdentity : allOwnIdentities) {
141                         if (ownIdentity.getId().equals(id)) {
142                                 return ownIdentity;
143                         }
144                 }
145                 return null;
146         }
147
148         /**
149          * Returns all own identities.
150          *
151          * @return All own identities
152          */
153         public Set<OwnIdentity> getAllOwnIdentities() {
154                 try {
155                         Set<OwnIdentity> ownIdentities = webOfTrustConnector.loadAllOwnIdentities();
156                         Map<String, OwnIdentity> newOwnIdentities = new HashMap<String, OwnIdentity>();
157                         for (OwnIdentity ownIdentity : ownIdentities) {
158                                 newOwnIdentities.put(ownIdentity.getId(), ownIdentity);
159                         }
160                         checkOwnIdentities(newOwnIdentities);
161                         return ownIdentities;
162                 } catch (PluginException pe1) {
163                         logger.log(Level.WARNING, "Could not load all own identities!", pe1);
164                         return Collections.emptySet();
165                 }
166         }
167
168         //
169         // ACTIONS
170         //
171
172         /**
173          * Adds a context to the given own identity.
174          *
175          * @param ownIdentity
176          *            The own identity
177          * @param context
178          *            The context to add
179          */
180         public void addContext(OwnIdentity ownIdentity, String context) {
181                 if (ownIdentity.hasContext(context)) {
182                         return;
183                 }
184                 try {
185                         webOfTrustConnector.addContext(ownIdentity, context);
186                         ownIdentity.addContext(context);
187                 } catch (PluginException pe1) {
188                         logger.log(Level.WARNING, "Could not add context " + context + " to OwnIdentity " + ownIdentity + ".", pe1);
189                 }
190         }
191
192         /**
193          * Removes a context from the given own identity.
194          *
195          * @param ownIdentity
196          *            The own identity
197          * @param context
198          *            The context to remove
199          */
200         public void removeContext(OwnIdentity ownIdentity, String context) {
201                 if (!ownIdentity.hasContext(context)) {
202                         return;
203                 }
204                 try {
205                         webOfTrustConnector.removeContext(ownIdentity, context);
206                         ownIdentity.removeContext(context);
207                 } catch (PluginException pe1) {
208                         logger.log(Level.WARNING, "Could not remove context " + context + " from OwnIdentity " + ownIdentity + ".", pe1);
209                 }
210         }
211
212         /**
213          * Sets the property with the given name to the given value.
214          *
215          * @param ownIdentity
216          *            The own identity
217          * @param name
218          *            The name of the property
219          * @param value
220          *            The value of the property
221          */
222         public void setProperty(OwnIdentity ownIdentity, String name, String value) {
223                 try {
224                         webOfTrustConnector.setProperty(ownIdentity, name, value);
225                         ownIdentity.setProperty(name, value);
226                 } catch (PluginException pe1) {
227                         logger.log(Level.WARNING, "Could not set property “" + name + "” to “" + value + "” for OwnIdentity: " + ownIdentity, pe1);
228                 }
229         }
230
231         /**
232          * Removes the property with the given name.
233          *
234          * @param ownIdentity
235          *            The own identity
236          * @param name
237          *            The name of the property to remove
238          */
239         public void removeProperty(OwnIdentity ownIdentity, String name) {
240                 try {
241                         webOfTrustConnector.removeProperty(ownIdentity, name);
242                         ownIdentity.removeProperty(name);
243                 } catch (PluginException pe1) {
244                         logger.log(Level.WARNING, "Could not remove property “" + name + "” from OwnIdentity: " + ownIdentity, pe1);
245                 }
246         }
247
248         //
249         // SERVICE METHODS
250         //
251
252         /**
253          * {@inheritDoc}
254          */
255         @Override
256         protected void serviceRun() {
257                 Map<String, Identity> oldIdentities = Collections.emptyMap();
258                 while (!shouldStop()) {
259                         Map<String, Identity> currentIdentities = new HashMap<String, Identity>();
260                         Map<String, OwnIdentity> currentOwnIdentities = new HashMap<String, OwnIdentity>();
261
262                         /* get all identities with the wanted context from WoT. */
263                         Set<OwnIdentity> ownIdentities;
264                         try {
265                                 ownIdentities = webOfTrustConnector.loadAllOwnIdentities();
266
267                                 /* check for changes. */
268                                 for (OwnIdentity ownIdentity : ownIdentities) {
269                                         currentOwnIdentities.put(ownIdentity.getId(), ownIdentity);
270                                 }
271                                 checkOwnIdentities(currentOwnIdentities);
272
273                                 /* now filter for context and get all identities. */
274                                 currentOwnIdentities.clear();
275                                 for (OwnIdentity ownIdentity : ownIdentities) {
276                                         if ((context != null) && !ownIdentity.hasContext(context)) {
277                                                 continue;
278                                         }
279                                         currentOwnIdentities.put(ownIdentity.getId(), ownIdentity);
280                                         for (Identity identity : webOfTrustConnector.loadTrustedIdentities(ownIdentity, context)) {
281                                                 currentIdentities.put(identity.getId(), identity);
282                                         }
283                                 }
284
285                                 /* find removed identities. */
286                                 for (Identity oldIdentity : oldIdentities.values()) {
287                                         if (!currentIdentities.containsKey(oldIdentity.getId())) {
288                                                 identityListenerManager.fireIdentityRemoved(oldIdentity);
289                                         }
290                                 }
291
292                                 /* find new identities. */
293                                 for (Identity currentIdentity : currentIdentities.values()) {
294                                         if (!oldIdentities.containsKey(currentIdentity.getId())) {
295                                                 identityListenerManager.fireIdentityAdded(currentIdentity);
296                                         }
297                                 }
298
299                                 /* check for changes in the contexts. */
300                                 for (Identity oldIdentity : oldIdentities.values()) {
301                                         if (!currentIdentities.containsKey(oldIdentity.getId())) {
302                                                 continue;
303                                         }
304                                         Identity newIdentity = currentIdentities.get(oldIdentity.getId());
305                                         Set<String> oldContexts = oldIdentity.getContexts();
306                                         Set<String> newContexts = newIdentity.getContexts();
307                                         if (oldContexts.size() != newContexts.size()) {
308                                                 identityListenerManager.fireIdentityUpdated(newIdentity);
309                                                 continue;
310                                         }
311                                         for (String oldContext : oldContexts) {
312                                                 if (!newContexts.contains(oldContext)) {
313                                                         identityListenerManager.fireIdentityUpdated(newIdentity);
314                                                         break;
315                                                 }
316                                         }
317                                 }
318
319                                 /* check for changes in the properties. */
320                                 for (Identity oldIdentity : oldIdentities.values()) {
321                                         if (!currentIdentities.containsKey(oldIdentity.getId())) {
322                                                 continue;
323                                         }
324                                         Identity newIdentity = currentIdentities.get(oldIdentity.getId());
325                                         Map<String, String> oldProperties = oldIdentity.getProperties();
326                                         Map<String, String> newProperties = newIdentity.getProperties();
327                                         if (oldProperties.size() != newProperties.size()) {
328                                                 identityListenerManager.fireIdentityUpdated(newIdentity);
329                                                 continue;
330                                         }
331                                         for (Entry<String, String> oldProperty : oldProperties.entrySet()) {
332                                                 if (!newProperties.containsKey(oldProperty.getKey()) || !newProperties.get(oldProperty.getKey()).equals(oldProperty.getValue())) {
333                                                         identityListenerManager.fireIdentityUpdated(newIdentity);
334                                                         break;
335                                                 }
336                                         }
337                                 }
338
339                                 /* remember the current set of identities. */
340                                 oldIdentities = currentIdentities;
341
342                         } catch (PluginException pe1) {
343                                 logger.log(Level.WARNING, "WoT has disappeared!", pe1);
344                         }
345
346                         /* wait a minute before checking again. */
347                         sleep(60 * 1000);
348                 }
349         }
350
351         //
352         // PRIVATE METHODS
353         //
354
355         /**
356          * Checks the given new list of own identities for added or removed own
357          * identities, as compared to {@link #currentOwnIdentities}.
358          *
359          * @param newOwnIdentities
360          *            The new own identities
361          */
362         private void checkOwnIdentities(Map<String, OwnIdentity> newOwnIdentities) {
363                 synchronized (syncObject) {
364
365                         /* find removed own identities: */
366                         for (OwnIdentity oldOwnIdentity : currentOwnIdentities.values()) {
367                                 if (!newOwnIdentities.containsKey(oldOwnIdentity.getId())) {
368                                         identityListenerManager.fireOwnIdentityRemoved(oldOwnIdentity);
369                                 }
370                         }
371
372                         /* find added own identities. */
373                         for (OwnIdentity currentOwnIdentity : newOwnIdentities.values()) {
374                                 if (!currentOwnIdentities.containsKey(currentOwnIdentity.getId())) {
375                                         identityListenerManager.fireOwnIdentityAdded(currentOwnIdentity);
376                                 }
377                         }
378
379                         currentOwnIdentities.clear();
380                         currentOwnIdentities.putAll(newOwnIdentities);
381                 }
382         }
383
384 }