21b2213d21c0b5f91de79d2c75e5792f238d45af
[Sone.git] / src / main / java / net / pterodactylus / sone / core / Core.java
1 /*
2  * Sone - Core.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.core;
19
20 import java.net.MalformedURLException;
21 import java.util.ArrayList;
22 import java.util.Collections;
23 import java.util.HashMap;
24 import java.util.HashSet;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Set;
28 import java.util.logging.Level;
29 import java.util.logging.Logger;
30
31 import net.pterodactylus.sone.core.Options.DefaultOption;
32 import net.pterodactylus.sone.core.Options.Option;
33 import net.pterodactylus.sone.core.Options.OptionWatcher;
34 import net.pterodactylus.sone.data.Post;
35 import net.pterodactylus.sone.data.Reply;
36 import net.pterodactylus.sone.data.Sone;
37 import net.pterodactylus.sone.freenet.wot.Identity;
38 import net.pterodactylus.sone.freenet.wot.IdentityListener;
39 import net.pterodactylus.sone.freenet.wot.IdentityManager;
40 import net.pterodactylus.sone.freenet.wot.OwnIdentity;
41 import net.pterodactylus.util.config.Configuration;
42 import net.pterodactylus.util.config.ConfigurationException;
43 import net.pterodactylus.util.logging.Logging;
44 import net.pterodactylus.util.number.Numbers;
45 import freenet.keys.FreenetURI;
46
47 /**
48  * The Sone core.
49  *
50  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
51  */
52 public class Core implements IdentityListener {
53
54         /**
55          * Enumeration for the possible states of a {@link Sone}.
56          *
57          * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
58          */
59         public enum SoneStatus {
60
61                 /** The Sone is unknown, i.e. not yet downloaded. */
62                 unknown,
63
64                 /** The Sone is idle, i.e. not being downloaded or inserted. */
65                 idle,
66
67                 /** The Sone is currently being inserted. */
68                 inserting,
69
70                 /** The Sone is currently being downloaded. */
71                 downloading,
72         }
73
74         /** The logger. */
75         private static final Logger logger = Logging.getLogger(Core.class);
76
77         /** The options. */
78         private final Options options = new Options();
79
80         /** The configuration. */
81         private final Configuration configuration;
82
83         /** The identity manager. */
84         private final IdentityManager identityManager;
85
86         /** Interface to freenet. */
87         private final FreenetInterface freenetInterface;
88
89         /** The Sone downloader. */
90         private final SoneDownloader soneDownloader;
91
92         /** The Sones’ statuses. */
93         /* synchronize access on itself. */
94         private final Map<Sone, SoneStatus> soneStatuses = new HashMap<Sone, SoneStatus>();
95
96         /** Sone inserters. */
97         /* synchronize access on this on localSones. */
98         private final Map<Sone, SoneInserter> soneInserters = new HashMap<Sone, SoneInserter>();
99
100         /** All local Sones. */
101         /* synchronize access on this on itself. */
102         private Map<String, Sone> localSones = new HashMap<String, Sone>();
103
104         /** All remote Sones. */
105         /* synchronize access on this on itself. */
106         private Map<String, Sone> remoteSones = new HashMap<String, Sone>();
107
108         /** All posts. */
109         private Map<String, Post> posts = new HashMap<String, Post>();
110
111         /** All replies. */
112         private Map<String, Reply> replies = new HashMap<String, Reply>();
113
114         /**
115          * Creates a new core.
116          *
117          * @param configuration
118          *            The configuration of the core
119          * @param freenetInterface
120          *            The freenet interface
121          * @param identityManager
122          *            The identity manager
123          */
124         public Core(Configuration configuration, FreenetInterface freenetInterface, IdentityManager identityManager) {
125                 this.configuration = configuration;
126                 this.freenetInterface = freenetInterface;
127                 this.identityManager = identityManager;
128                 this.soneDownloader = new SoneDownloader(this, freenetInterface);
129         }
130
131         //
132         // ACCESSORS
133         //
134
135         /**
136          * Returns the options used by the core.
137          *
138          * @return The options of the core
139          */
140         public Options getOptions() {
141                 return options;
142         }
143
144         /**
145          * Returns the identity manager used by the core.
146          *
147          * @return The identity manager
148          */
149         public IdentityManager getIdentityManager() {
150                 return identityManager;
151         }
152
153         /**
154          * Returns the status of the given Sone.
155          *
156          * @param sone
157          *            The Sone to get the status for
158          * @return The status of the Sone
159          */
160         public SoneStatus getSoneStatus(Sone sone) {
161                 synchronized (soneStatuses) {
162                         return soneStatuses.get(sone);
163                 }
164         }
165
166         /**
167          * Sets the status of the given Sone.
168          *
169          * @param sone
170          *            The Sone to set the status of
171          * @param soneStatus
172          *            The status to set
173          */
174         public void setSoneStatus(Sone sone, SoneStatus soneStatus) {
175                 synchronized (soneStatuses) {
176                         soneStatuses.put(sone, soneStatus);
177                 }
178         }
179
180         /**
181          * Returns all Sones, remote and local.
182          *
183          * @return All Sones
184          */
185         public Set<Sone> getSones() {
186                 Set<Sone> allSones = new HashSet<Sone>();
187                 allSones.addAll(getLocalSones());
188                 allSones.addAll(getRemoteSones());
189                 return allSones;
190         }
191
192         /**
193          * Returns the Sone with the given ID, regardless whether it’s local or
194          * remote.
195          *
196          * @param id
197          *            The ID of the Sone to get
198          * @return The Sone with the given ID, or {@code null} if there is no such
199          *         Sone
200          */
201         public Sone getSone(String id) {
202                 Sone sone = getRemoteSone(id);
203                 if (sone != null) {
204                         return sone;
205                 }
206                 sone = getLocalSone(id);
207                 return sone;
208         }
209
210         /**
211          * Returns whether the given Sone is a local Sone.
212          *
213          * @param sone
214          *            The Sone to check for its locality
215          * @return {@code true} if the given Sone is local, {@code false} otherwise
216          */
217         public boolean isLocalSone(Sone sone) {
218                 synchronized (localSones) {
219                         return localSones.containsKey(sone.getId());
220                 }
221         }
222
223         /**
224          * Returns all local Sones.
225          *
226          * @return All local Sones
227          */
228         public Set<Sone> getLocalSones() {
229                 synchronized (localSones) {
230                         return new HashSet<Sone>(localSones.values());
231                 }
232         }
233
234         /**
235          * Returns the local Sone with the given ID.
236          *
237          * @param id
238          *            The ID of the Sone to get
239          * @return The Sone, or {@code null} if there is no Sone with the given ID
240          */
241         public Sone getLocalSone(String id) {
242                 synchronized (localSones) {
243                         return localSones.get(id);
244                 }
245         }
246
247         /**
248          * Returns all remote Sones.
249          *
250          * @return All remote Sones
251          */
252         public Set<Sone> getRemoteSones() {
253                 synchronized (remoteSones) {
254                         return new HashSet<Sone>(remoteSones.values());
255                 }
256         }
257
258         /**
259          * Returns the remote Sone with the given ID.
260          *
261          * @param id
262          *            The ID of the remote Sone to get
263          * @return The Sone, or {@code null} if there is no such Sone
264          */
265         public Sone getRemoteSone(String id) {
266                 synchronized (remoteSones) {
267                         return remoteSones.get(id);
268                 }
269         }
270
271         /**
272          * Returns whether the given Sone is a remote Sone.
273          *
274          * @param sone
275          *            The Sone to check
276          * @return {@code true} if the given Sone is a remote Sone, {@code false}
277          *         otherwise
278          */
279         public boolean isRemoteSone(Sone sone) {
280                 synchronized (remoteSones) {
281                         return remoteSones.containsKey(sone.getId());
282                 }
283         }
284
285         /**
286          * Returns the post with the given ID.
287          *
288          * @param postId
289          *            The ID of the post to get
290          * @return The post, or {@code null} if there is no such post
291          */
292         public Post getPost(String postId) {
293                 synchronized (posts) {
294                         return posts.get(postId);
295                 }
296         }
297
298         /**
299          * Returns the reply with the given ID.
300          *
301          * @param replyId
302          *            The ID of the reply to get
303          * @return The reply, or {@code null} if there is no such reply
304          */
305         public Reply getReply(String replyId) {
306                 synchronized (replies) {
307                         return replies.get(replyId);
308                 }
309         }
310
311         /**
312          * Returns all replies for the given post, order ascending by time.
313          *
314          * @param post
315          *            The post to get all replies for
316          * @return All replies for the given post
317          */
318         public List<Reply> getReplies(Post post) {
319                 Set<Sone> sones = getSones();
320                 List<Reply> replies = new ArrayList<Reply>();
321                 for (Sone sone : sones) {
322                         for (Reply reply : sone.getReplies()) {
323                                 if (reply.getPost().equals(post)) {
324                                         replies.add(reply);
325                                 }
326                         }
327                 }
328                 Collections.sort(replies, Reply.TIME_COMPARATOR);
329                 return replies;
330         }
331
332         /**
333          * Returns all Sones that have liked the given post.
334          *
335          * @param post
336          *            The post to get the liking Sones for
337          * @return The Sones that like the given post
338          */
339         public Set<Sone> getLikes(Post post) {
340                 Set<Sone> sones = new HashSet<Sone>();
341                 for (Sone sone : getSones()) {
342                         if (sone.getLikedPostIds().contains(post.getId())) {
343                                 sones.add(sone);
344                         }
345                 }
346                 return sones;
347         }
348
349         /**
350          * Returns all Sones that have liked the given reply.
351          *
352          * @param reply
353          *            The reply to get the liking Sones for
354          * @return The Sones that like the given reply
355          */
356         public Set<Sone> getLikes(Reply reply) {
357                 Set<Sone> sones = new HashSet<Sone>();
358                 for (Sone sone : getSones()) {
359                         if (sone.getLikedPostIds().contains(reply.getId())) {
360                                 sones.add(sone);
361                         }
362                 }
363                 return sones;
364         }
365
366         //
367         // ACTIONS
368         //
369
370         /**
371          * Adds a local Sone from the given ID which has to be the ID of an own
372          * identity.
373          *
374          * @param id
375          *            The ID of an own identity to add a Sone for
376          * @return The added (or already existing) Sone
377          */
378         public Sone addLocalSone(String id) {
379                 synchronized (localSones) {
380                         if (localSones.containsKey(id)) {
381                                 logger.log(Level.FINE, "Tried to add known local Sone: %s", id);
382                                 return localSones.get(id);
383                         }
384                         OwnIdentity ownIdentity = identityManager.getOwnIdentity(id);
385                         if (ownIdentity == null) {
386                                 logger.log(Level.INFO, "Invalid Sone ID: %s", id);
387                                 return null;
388                         }
389                         return addLocalSone(ownIdentity);
390                 }
391         }
392
393         /**
394          * Adds a local Sone from the given own identity.
395          *
396          * @param ownIdentity
397          *            The own identity to create a Sone from
398          * @return The added (or already existing) Sone
399          */
400         public Sone addLocalSone(OwnIdentity ownIdentity) {
401                 if (ownIdentity == null) {
402                         logger.log(Level.WARNING, "Given OwnIdentity is null!");
403                         return null;
404                 }
405                 synchronized (localSones) {
406                         if (localSones.containsKey(ownIdentity.getId())) {
407                                 logger.log(Level.FINE, "Tried to add known local Sone: %s", ownIdentity);
408                                 return localSones.get(ownIdentity.getId());
409                         }
410                         String latestEdition = ownIdentity.getProperty("Sone.LatestEdition");
411                         final Sone sone = new Sone(ownIdentity).setInsertUri(getSoneUri(ownIdentity.getInsertUri(), latestEdition)).setRequestUri(getSoneUri(ownIdentity.getRequestUri(), latestEdition));
412                         /* TODO - load posts ’n stuff */
413                         localSones.put(ownIdentity.getId(), sone);
414                         SoneInserter soneInserter = new SoneInserter(this, freenetInterface, sone);
415                         soneInserters.put(sone, soneInserter);
416                         soneInserter.start();
417                         setSoneStatus(sone, SoneStatus.idle);
418                         new Thread(new Runnable() {
419
420                                 @Override
421                                 @SuppressWarnings("synthetic-access")
422                                 public void run() {
423                                         soneDownloader.fetchSone(sone);
424                                 }
425
426                         }, "Sone Downloader").start();
427                         return sone;
428                 }
429         }
430
431         /**
432          * Creates a new Sone for the given own identity.
433          *
434          * @param ownIdentity
435          *            The own identity to create a Sone for
436          * @return The created Sone
437          */
438         public Sone createSone(OwnIdentity ownIdentity) {
439                 identityManager.addContext(ownIdentity, "Sone");
440                 Sone sone = addLocalSone(ownIdentity);
441                 synchronized (sone) {
442                         /* mark as modified so that it gets inserted immediately. */
443                         sone.setModificationCounter(sone.getModificationCounter() + 1);
444                 }
445                 return sone;
446         }
447
448         /**
449          * Adds the Sone of the given identity.
450          *
451          * @param identity
452          *            The identity whose Sone to add
453          * @return The added or already existing Sone
454          */
455         public Sone addRemoteSone(Identity identity) {
456                 if (identity == null) {
457                         logger.log(Level.WARNING, "Given Identity is null!");
458                         return null;
459                 }
460                 synchronized (remoteSones) {
461                         if (remoteSones.containsKey(identity.getId())) {
462                                 logger.log(Level.FINE, "Identity already exists: %s", identity);
463                                 return remoteSones.get(identity.getId());
464                         }
465                         Sone sone = new Sone(identity);
466                         sone.setRequestUri(getSoneUri(identity.getRequestUri(), identity.getProperty("Sone.LatestEdition")));
467                         remoteSones.put(identity.getId(), sone);
468                         soneDownloader.addSone(sone);
469                         setSoneStatus(sone, SoneStatus.idle);
470                         return sone;
471                 }
472         }
473
474         /**
475          * Updates the stores Sone with the given Sone.
476          *
477          * @param sone
478          *            The updated Sone
479          */
480         public void updateSone(Sone sone) {
481                 if (isRemoteSone(sone)) {
482                         Sone storedSone = getRemoteSone(sone.getId());
483                         if (!(sone.getTime() > storedSone.getTime())) {
484                                 logger.log(Level.FINE, "Downloaded Sone %s is not newer than stored Sone %s.", new Object[] { sone, storedSone });
485                                 return;
486                         }
487                         synchronized (posts) {
488                                 for (Post post : storedSone.getPosts()) {
489                                         posts.remove(post.getId());
490                                 }
491                                 for (Post post : sone.getPosts()) {
492                                         posts.put(post.getId(), post);
493                                 }
494                         }
495                         synchronized (replies) {
496                                 for (Reply reply : storedSone.getReplies()) {
497                                         replies.remove(reply.getId());
498                                 }
499                                 for (Reply reply : sone.getReplies()) {
500                                         replies.put(reply.getId(), reply);
501                                 }
502                         }
503                         synchronized (storedSone) {
504                                 storedSone.setTime(sone.getTime());
505                                 storedSone.setProfile(sone.getProfile());
506                                 storedSone.setPosts(sone.getPosts());
507                                 storedSone.setReplies(sone.getReplies());
508                                 storedSone.setLikePostIds(sone.getLikedPostIds());
509                                 storedSone.setLikeReplyIds(sone.getLikedReplyIds());
510                                 storedSone.updateUris(sone.getRequestUri().getEdition());
511                         }
512                         saveSone(storedSone);
513                 }
514         }
515
516         /**
517          * Deletes the given Sone. This will remove the Sone from the
518          * {@link #getLocalSone(String) local Sones}, stops its {@link SoneInserter}
519          * and remove the context from its identity.
520          *
521          * @param sone
522          *            The Sone to delete
523          */
524         public void deleteSone(Sone sone) {
525                 if (!(sone.getIdentity() instanceof OwnIdentity)) {
526                         logger.log(Level.WARNING, "Tried to delete Sone of non-own identity: %s", sone);
527                         return;
528                 }
529                 synchronized (localSones) {
530                         if (!localSones.containsKey(sone.getId())) {
531                                 logger.log(Level.WARNING, "Tried to delete non-local Sone: %s", sone);
532                                 return;
533                         }
534                         localSones.remove(sone.getId());
535                         soneInserters.remove(sone.getId()).stop();
536                 }
537                 identityManager.removeContext((OwnIdentity) sone.getIdentity(), "Sone");
538         }
539
540         /**
541          * Saves the given Sone. This will persist all local settings for the given
542          * Sone, such as the friends list and similar, private options.
543          *
544          * @param sone
545          *            The Sone to save
546          */
547         public void saveSone(Sone sone) {
548                 if (!isLocalSone(sone)) {
549                         logger.log(Level.FINE, "Tried to save non-local Sone: %s", sone);
550                 }
551                 /* TODO - implement saving. */
552         }
553
554         /**
555          * Creates a new post.
556          *
557          * @param sone
558          *            The Sone that creates the post
559          * @param text
560          *            The text of the post
561          */
562         public void createPost(Sone sone, String text) {
563                 createPost(sone, System.currentTimeMillis(), text);
564         }
565
566         /**
567          * Creates a new post.
568          *
569          * @param sone
570          *            The Sone that creates the post
571          * @param time
572          *            The time of the post
573          * @param text
574          *            The text of the post
575          */
576         public void createPost(Sone sone, long time, String text) {
577                 if (!isLocalSone(sone)) {
578                         logger.log(Level.FINE, "Tried to create post for non-local Sone: %s", sone);
579                         return;
580                 }
581                 Post post = new Post(sone, time, text);
582                 synchronized (posts) {
583                         posts.put(post.getId(), post);
584                 }
585                 sone.addPost(post);
586                 saveSone(sone);
587         }
588
589         /**
590          * Deletes the given post.
591          *
592          * @param post
593          *            The post to delete
594          */
595         public void deletePost(Post post) {
596                 if (!isLocalSone(post.getSone())) {
597                         logger.log(Level.WARNING, "Tried to delete post of non-local Sone: %s", post.getSone());
598                         return;
599                 }
600                 post.getSone().removePost(post);
601                 synchronized (posts) {
602                         posts.remove(post.getId());
603                 }
604                 saveSone(post.getSone());
605         }
606
607         /**
608          * Creates a new reply.
609          *
610          * @param sone
611          *            The Sone that creates the reply
612          * @param post
613          *            The post that this reply refers to
614          * @param text
615          *            The text of the reply
616          */
617         public void createReply(Sone sone, Post post, String text) {
618                 createReply(sone, post, System.currentTimeMillis(), text);
619         }
620
621         /**
622          * Creates a new reply.
623          *
624          * @param sone
625          *            The Sone that creates the reply
626          * @param post
627          *            The post that this reply refers to
628          * @param time
629          *            The time of the reply
630          * @param text
631          *            The text of the reply
632          */
633         public void createReply(Sone sone, Post post, long time, String text) {
634                 if (!isLocalSone(sone)) {
635                         logger.log(Level.FINE, "Tried to create reply for non-local Sone: %s", sone);
636                         return;
637                 }
638                 Reply reply = new Reply(sone, post, System.currentTimeMillis(), text);
639                 synchronized (replies) {
640                         replies.put(reply.getId(), reply);
641                 }
642                 sone.addReply(reply);
643                 saveSone(sone);
644         }
645
646         /**
647          * Deletes the given reply.
648          *
649          * @param reply
650          *            The reply to delete
651          */
652         public void deleteReply(Reply reply) {
653                 Sone sone = reply.getSone();
654                 if (!isLocalSone(sone)) {
655                         logger.log(Level.FINE, "Tried to delete non-local reply: %s", reply);
656                         return;
657                 }
658                 synchronized (replies) {
659                         replies.remove(reply.getId());
660                 }
661                 sone.removeReply(reply);
662                 saveSone(sone);
663         }
664
665         /**
666          * Starts the core.
667          */
668         public void start() {
669                 loadConfiguration();
670         }
671
672         /**
673          * Stops the core.
674          */
675         public void stop() {
676                 synchronized (localSones) {
677                         for (SoneInserter soneInserter : soneInserters.values()) {
678                                 soneInserter.stop();
679                         }
680                 }
681                 saveConfiguration();
682         }
683
684         //
685         // PRIVATE METHODS
686         //
687
688         /**
689          * Loads the configuration.
690          */
691         @SuppressWarnings("unchecked")
692         private void loadConfiguration() {
693                 /* create options. */
694                 options.addIntegerOption("InsertionDelay", new DefaultOption<Integer>(60, new OptionWatcher<Integer>() {
695
696                         @Override
697                         public void optionChanged(Option<Integer> option, Integer oldValue, Integer newValue) {
698                                 SoneInserter.setInsertionDelay(newValue);
699                         }
700
701                 }));
702                 options.addBooleanOption("ClearOnNextRestart", new DefaultOption<Boolean>(false));
703                 options.addBooleanOption("ReallyClearOnNextRestart", new DefaultOption<Boolean>(false));
704
705                 /* read options from configuration. */
706                 options.getBooleanOption("ClearOnNextRestart").set(configuration.getBooleanValue("Option/ClearOnNextRestart").getValue(null));
707                 options.getBooleanOption("ReallyClearOnNextRestart").set(configuration.getBooleanValue("Option/ReallyClearOnNextRestart").getValue(null));
708                 boolean clearConfiguration = options.getBooleanOption("ClearOnNextRestart").get() && options.getBooleanOption("ReallyClearOnNextRestart").get();
709                 options.getBooleanOption("ClearOnNextRestart").set(null);
710                 options.getBooleanOption("ReallyClearOnNextRestart").set(null);
711                 if (clearConfiguration) {
712                         /* stop loading the configuration. */
713                         return;
714                 }
715
716                 options.getIntegerOption("InsertionDelay").set(configuration.getIntValue("Option/InsertionDelay").getValue(null));
717
718         }
719
720         /**
721          * Saves the current options.
722          */
723         private void saveConfiguration() {
724                 /* store the options first. */
725                 try {
726                         configuration.getIntValue("Option/InsertionDelay").setValue(options.getIntegerOption("InsertionDelay").getReal());
727                         configuration.getBooleanValue("Option/ClearOnNextRestart").setValue(options.getBooleanOption("ClearOnNextRestart").getReal());
728                         configuration.getBooleanValue("Option/ReallyClearOnNextRestart").setValue(options.getBooleanOption("ReallyClearOnNextRestart").getReal());
729                 } catch (ConfigurationException ce1) {
730                         logger.log(Level.SEVERE, "Could not store configuration!", ce1);
731                 }
732         }
733
734         /**
735          * Generate a Sone URI from the given URI and latest edition.
736          *
737          * @param uriString
738          *            The URI to derive the Sone URI from
739          * @param latestEditionString
740          *            The latest edition as a {@link String}, or {@code null}
741          * @return The derived URI
742          */
743         private FreenetURI getSoneUri(String uriString, String latestEditionString) {
744                 try {
745                         FreenetURI uri = new FreenetURI(uriString).setDocName("Sone").setMetaString(new String[0]).setSuggestedEdition(Numbers.safeParseLong(latestEditionString, (long) 0));
746                         return uri;
747                 } catch (MalformedURLException mue1) {
748                         logger.log(Level.WARNING, "Could not create Sone URI from URI: " + uriString, mue1);
749                         return null;
750                 }
751         }
752
753         //
754         // INTERFACE IdentityListener
755         //
756
757         /**
758          * {@inheritDoc}
759          */
760         @Override
761         public void ownIdentityAdded(OwnIdentity ownIdentity) {
762                 logger.log(Level.FINEST, "Adding OwnIdentity: " + ownIdentity);
763                 if (ownIdentity.hasContext("Sone")) {
764                         addLocalSone(ownIdentity);
765                 }
766         }
767
768         /**
769          * {@inheritDoc}
770          */
771         @Override
772         public void ownIdentityRemoved(OwnIdentity ownIdentity) {
773                 /* TODO */
774         }
775
776         /**
777          * {@inheritDoc}
778          */
779         @Override
780         public void identityAdded(Identity identity) {
781                 logger.log(Level.FINEST, "Adding Identity: " + identity);
782                 addRemoteSone(identity);
783         }
784
785         /**
786          * {@inheritDoc}
787          */
788         @Override
789         public void identityUpdated(Identity identity) {
790                 /* TODO */
791         }
792
793         /**
794          * {@inheritDoc}
795          */
796         @Override
797         public void identityRemoved(Identity identity) {
798                 /* TODO */
799         }
800
801 }