Use unique IDs for images
authorDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Fri, 24 Jul 2015 08:39:21 +0000 (10:39 +0200)
committerDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Fri, 24 Jul 2015 18:47:50 +0000 (20:47 +0200)
24 files changed:
src/main/java/net/pterodactylus/sone/core/ConfigurationSoneParser.java
src/main/java/net/pterodactylus/sone/core/Core.java
src/main/java/net/pterodactylus/sone/data/Image.java
src/main/java/net/pterodactylus/sone/data/Profile.java
src/main/java/net/pterodactylus/sone/data/Sone.java
src/main/java/net/pterodactylus/sone/data/impl/IdOnlySone.java
src/main/java/net/pterodactylus/sone/data/impl/ImageImpl.java
src/main/java/net/pterodactylus/sone/data/impl/SoneImpl.java
src/main/java/net/pterodactylus/sone/template/BuildIdFilter.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/template/ProfileAccessor.java
src/main/java/net/pterodactylus/sone/web/DeleteImagePage.java
src/main/java/net/pterodactylus/sone/web/EditAlbumPage.java
src/main/java/net/pterodactylus/sone/web/EditImagePage.java
src/main/java/net/pterodactylus/sone/web/EditProfilePage.java
src/main/java/net/pterodactylus/sone/web/WebInterface.java
src/main/resources/templates/editProfile.html
src/main/resources/templates/include/head.html
src/main/resources/templates/include/soneMenu.html
src/main/resources/templates/include/viewPost.html
src/main/resources/templates/include/viewReply.html
src/main/resources/templates/insert/sone.xml
src/test/java/net/pterodactylus/sone/core/FreenetInterfaceTest.java
src/test/java/net/pterodactylus/sone/core/SoneParserTest.java
src/test/java/net/pterodactylus/sone/template/BuildIdFilterTest.java [new file with mode: 0644]

index a29856b..02c10db 100644 (file)
@@ -271,7 +271,7 @@ public class ConfigurationSoneParser {
                                        .setHeight(height)
                                        .update();
                        album.addImage(image);
-                       images.put(image.getId(), image);
+                       images.put(imageId, image);
                }
        }
 
index 8e00e23..d0b31d0 100644 (file)
@@ -1530,7 +1530,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                                                continue;
                                        }
                                        String imagePrefix = sonePrefix + "/Images/" + imageCounter++;
-                                       configuration.getStringValue(imagePrefix + "/ID").setValue(image.getId());
+                                       configuration.getStringValue(imagePrefix + "/ID").setValue(image.getInternalId());
                                        configuration.getStringValue(imagePrefix + "/Album").setValue(album.getInternalId());
                                        configuration.getStringValue(imagePrefix + "/Key").setValue(image.getKey());
                                        configuration.getStringValue(imagePrefix + "/Title").setValue(image.getTitle());
index 22ddc29..f2ef88d 100644 (file)
@@ -30,6 +30,7 @@ public interface Image extends Identified, Fingerprintable {
         * @return The ID of this image
         */
        String getId();
+       String getInternalId();
 
        /**
         * Returns the Sone this image belongs to.
index 4970cf9..679196b 100644 (file)
@@ -243,7 +243,7 @@ public class Profile implements Fingerprintable {
                        return this;
                }
                checkArgument(avatar.getSone().equals(sone), "avatar must belong to Sone");
-               this.avatar = avatar.getId();
+               this.avatar = avatar.getInternalId();
                return this;
        }
 
index e64a388..4d0905f 100644 (file)
@@ -35,6 +35,7 @@ import net.pterodactylus.sone.freenet.wot.Identity;
 import net.pterodactylus.sone.freenet.wot.OwnIdentity;
 import net.pterodactylus.sone.template.SoneAccessor;
 
+import com.google.common.base.Optional;
 import freenet.keys.FreenetURI;
 
 import com.google.common.base.Function;
@@ -502,6 +503,7 @@ public interface Sone extends Identified, Fingerprintable, Comparable<Sone> {
         * @return The root album of this Sone
         */
        Album getRootAlbum();
+       Optional<Image> getImageByInternalId(final String internalId);
 
        /**
         * Returns Sone-specific options.
index e9a0c57..85402ce 100644 (file)
@@ -9,6 +9,7 @@ import java.util.Set;
 
 import net.pterodactylus.sone.data.Album;
 import net.pterodactylus.sone.data.Client;
+import net.pterodactylus.sone.data.Image;
 import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.data.PostReply;
 import net.pterodactylus.sone.data.Profile;
@@ -16,6 +17,7 @@ import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.data.SoneOptions;
 import net.pterodactylus.sone.freenet.wot.Identity;
 
+import com.google.common.base.Optional;
 import freenet.keys.FreenetURI;
 
 import com.google.common.base.Objects;
@@ -219,6 +221,11 @@ public class IdOnlySone implements Sone {
        }
 
        @Override
+       public Optional<Image> getImageByInternalId(String internalId) {
+               return Optional.absent();
+       }
+
+       @Override
        public SoneOptions getOptions() {
                return null;
        }
index 2df98b1..b385cfd 100644 (file)
@@ -25,6 +25,7 @@ import static com.google.common.base.Preconditions.checkState;
 import java.util.UUID;
 
 import net.pterodactylus.sone.data.Album;
+import net.pterodactylus.sone.data.IdBuilder;
 import net.pterodactylus.sone.data.Image;
 import net.pterodactylus.sone.data.Sone;
 
@@ -39,6 +40,8 @@ import com.google.common.hash.Hashing;
  */
 public class ImageImpl implements Image {
 
+       private final IdBuilder idBuilder = new IdBuilder();
+
        /** The ID of the image. */
        private final String id;
 
@@ -88,6 +91,11 @@ public class ImageImpl implements Image {
 
        @Override
        public String getId() {
+               return idBuilder.buildId(sone.getId(), id);
+       }
+
+       @Override
+       public String getInternalId() {
                return id;
        }
 
index f7ebfb1..97fe8eb 100644 (file)
@@ -30,9 +30,11 @@ import java.util.Set;
 import java.util.concurrent.CopyOnWriteArraySet;
 import java.util.logging.Level;
 import java.util.logging.Logger;
+import javax.annotation.Nullable;
 
 import net.pterodactylus.sone.data.Album;
 import net.pterodactylus.sone.data.Client;
+import net.pterodactylus.sone.data.Image;
 import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.data.PostReply;
 import net.pterodactylus.sone.data.Profile;
@@ -44,6 +46,9 @@ import net.pterodactylus.sone.database.Database;
 import net.pterodactylus.sone.freenet.wot.Identity;
 import net.pterodactylus.sone.freenet.wot.OwnIdentity;
 
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.common.collect.FluentIterable;
 import freenet.keys.FreenetURI;
 
 import com.google.common.hash.Hasher;
@@ -584,6 +589,16 @@ public class SoneImpl implements Sone {
                return rootAlbum;
        }
 
+       @Override
+       public Optional<Image> getImageByInternalId(final String internalId) {
+               return FluentIterable.from(toAllImages.apply(this)).filter(new Predicate<Image>() {
+                       @Override
+                       public boolean apply(@Nullable Image input) {
+                               return (input != null) && input.getInternalId().equals(internalId);
+                       }
+               }).first();
+       }
+
        /**
         * Returns Sone-specific options.
         *
diff --git a/src/main/java/net/pterodactylus/sone/template/BuildIdFilter.java b/src/main/java/net/pterodactylus/sone/template/BuildIdFilter.java
new file mode 100644 (file)
index 0000000..321743a
--- /dev/null
@@ -0,0 +1,57 @@
+package net.pterodactylus.sone.template;
+
+import java.util.Map;
+
+import javax.annotation.Nullable;
+
+import net.pterodactylus.sone.data.IdBuilder;
+import net.pterodactylus.sone.data.Sone;
+import net.pterodactylus.util.template.Filter;
+import net.pterodactylus.util.template.TemplateContext;
+
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+
+/**
+ * Filter that {@link IdBuilder builds IDs} from a piped-in element ID and a Sone or Sone ID given as parameter “sone.”
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class BuildIdFilter implements Filter {
+
+       private final IdBuilder idBuilder = new IdBuilder();
+
+       @Override
+       public Object format(TemplateContext templateContext, Object data, Map<String, Object> parameters) {
+               Optional<String> soneId = getSoneId(parameters);
+               if (!soneId.isPresent()) {
+                       return null;
+               }
+               Optional<String> elementId = Optional.fromNullable(data).transform(getStringValue());
+               if (!elementId.isPresent()) {
+                       return null;
+               }
+               return idBuilder.buildId(soneId.get(), elementId.get());
+       }
+
+       private Optional<String> getSoneId(Map<String, Object> parameters) {
+               Object soneObject = parameters.get("sone");
+               if (soneObject instanceof String) {
+                       return Optional.of((String) soneObject);
+               } else if (soneObject instanceof Sone) {
+                       return Optional.of(((Sone) soneObject).getId());
+               }
+               return Optional.absent();
+       }
+
+       private Function<? super Object, String> getStringValue() {
+               return new Function<Object, String>() {
+                       @Nullable
+                       @Override
+                       public String apply(Object input) {
+                               return (input != null) ? input.toString() : null;
+                       }
+               };
+       }
+
+}
index 762bc14..ce29caa 100644 (file)
@@ -65,7 +65,7 @@ public class ProfileAccessor extends ReflectionAccessor {
                        if (avatarId == null) {
                                return null;
                        }
-                       if (core.getImage(avatarId, false) == null) {
+                       if (!currentSone.getImageByInternalId(avatarId).isPresent()) {
                                /* avatar ID but no matching image? show nothing. */
                                return null;
                        }
index 77f3ab8..9d50bfb 100644 (file)
@@ -17,6 +17,7 @@
 
 package net.pterodactylus.sone.web;
 
+import net.pterodactylus.sone.data.IdBuilder;
 import net.pterodactylus.sone.data.Image;
 import net.pterodactylus.sone.web.page.FreenetRequest;
 import net.pterodactylus.util.template.Template;
@@ -52,7 +53,8 @@ public class DeleteImagePage extends SoneTemplatePage {
        @Override
        protected void processTemplate(FreenetRequest request, TemplateContext templateContext) throws RedirectException {
                super.processTemplate(request, templateContext);
-               String imageId = (request.getMethod() == Method.POST) ? request.getHttpRequest().getPartAsStringFailsafe("image", 36) : request.getHttpRequest().getParam("image");
+               String imageId = (request.getMethod() == Method.POST) ? request.getHttpRequest().getPartAsStringFailsafe("image", IdBuilder.ID_STRING_LENGTH)
+                       : request.getHttpRequest().getParam("image");
                Image image = webInterface.getCore().getImage(imageId, false);
                if (image == null) {
                        throw new RedirectException("invalid.html");
index eca15de..aab4614 100644 (file)
@@ -69,7 +69,7 @@ public class EditAlbumPage extends SoneTemplatePage {
                                webInterface.getCore().touchConfiguration();
                                throw new RedirectException("imageBrowser.html?album=" + album.getParent().getId());
                        }
-                       String albumImageId = request.getHttpRequest().getPartAsStringFailsafe("album-image", 36);
+                       String albumImageId = request.getHttpRequest().getPartAsStringFailsafe("album-image", IdBuilder.ID_STRING_LENGTH);
                        if (webInterface.getCore().getImage(albumImageId, false) == null) {
                                albumImageId = null;
                        }
index 178add1..304b19c 100644 (file)
@@ -17,6 +17,7 @@
 
 package net.pterodactylus.sone.web;
 
+import net.pterodactylus.sone.data.IdBuilder;
 import net.pterodactylus.sone.data.Image;
 import net.pterodactylus.sone.text.TextFilter;
 import net.pterodactylus.sone.web.page.FreenetRequest;
@@ -54,7 +55,7 @@ public class EditImagePage extends SoneTemplatePage {
        protected void processTemplate(FreenetRequest request, TemplateContext templateContext) throws RedirectException {
                super.processTemplate(request, templateContext);
                if (request.getMethod() == Method.POST) {
-                       String imageId = request.getHttpRequest().getPartAsStringFailsafe("image", 36);
+                       String imageId = request.getHttpRequest().getPartAsStringFailsafe("image", IdBuilder.ID_STRING_LENGTH);
                        String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256);
                        Image image = webInterface.getCore().getImage(imageId, false);
                        if (image == null) {
index 162d637..47fd8c3 100644 (file)
@@ -22,6 +22,7 @@ import static net.pterodactylus.sone.utils.NumberParsers.parseInt;
 
 import java.util.List;
 
+import net.pterodactylus.sone.data.IdBuilder;
 import net.pterodactylus.sone.data.Profile;
 import net.pterodactylus.sone.data.Profile.DuplicateField;
 import net.pterodactylus.sone.data.Profile.Field;
@@ -80,12 +81,12 @@ public class EditProfilePage extends SoneTemplatePage {
                                birthDay = parseInt(request.getHttpRequest().getPartAsStringFailsafe("birth-day", 256).trim(), null);
                                birthMonth = parseInt(request.getHttpRequest().getPartAsStringFailsafe("birth-month", 256).trim(), null);
                                birthYear = parseInt(request.getHttpRequest().getPartAsStringFailsafe("birth-year", 256).trim(), null);
-                               avatarId = request.getHttpRequest().getPartAsStringFailsafe("avatarId", 36);
+                               avatarId = request.getHttpRequest().getPartAsStringFailsafe("avatarId", IdBuilder.ID_STRING_LENGTH);
                                profile.setFirstName(firstName.length() > 0 ? firstName : null);
                                profile.setMiddleName(middleName.length() > 0 ? middleName : null);
                                profile.setLastName(lastName.length() > 0 ? lastName : null);
                                profile.setBirthDay(birthDay).setBirthMonth(birthMonth).setBirthYear(birthYear);
-                               profile.setAvatar(webInterface.getCore().getImage(avatarId, false));
+                               profile.setAvatar(currentSone.getImageByInternalId(avatarId).orNull());
                                for (Field field : fields) {
                                        String value = request.getHttpRequest().getPartAsStringFailsafe("field-" + field.getId(), 400);
                                        String filteredValue = filter(request.getHttpRequest().getHeader("Host"), value);
index fbba8e0..95c8a3f 100644 (file)
@@ -76,6 +76,7 @@ import net.pterodactylus.sone.main.ReparseFilter;
 import net.pterodactylus.sone.main.SonePlugin;
 import net.pterodactylus.sone.notify.ListNotification;
 import net.pterodactylus.sone.template.AlbumAccessor;
+import net.pterodactylus.sone.template.BuildIdFilter;
 import net.pterodactylus.sone.template.CollectionAccessor;
 import net.pterodactylus.sone.template.CssClassNameFilter;
 import net.pterodactylus.sone.template.HttpRequestAccessor;
@@ -285,6 +286,7 @@ public class WebInterface {
                templateContextFactory.addFilter("unique", new UniqueElementFilter());
                templateContextFactory.addFilter("mod", new ModFilter());
                templateContextFactory.addFilter("paginate", new PaginationFilter());
+               templateContextFactory.addFilter("build-id", new BuildIdFilter());
                templateContextFactory.addProvider(TemplateProvider.TEMPLATE_CONTEXT_PROVIDER);
                templateContextFactory.addProvider(new ClassPathTemplateProvider(WebInterface.class, "/templates/"));
                templateContextFactory.addTemplateObject("webInterface", this);
index ddc08a9..af0bb8d 100644 (file)
                        </li>
                        <%foreach currentSone.allImages image>
                                <li>
-                                       <input type="radio" name="avatarId" value="<%image.id|html>"<%if avatarId|match value=image.id> checked="checked"<%/if>/>
+                                       <input type="radio" name="avatarId" value="<%image.internalId|html>"<%if avatarId|match value=image.internalId> checked="checked"<%/if>/>
                                        <div class="post-avatar"><% image|image-link max-width==48 max-height==48 mode==enlarge title=image.title></div>
                                </li>
                        <%/foreach>
index 13b564d..74091ca 100644 (file)
@@ -44,7 +44,7 @@
                                <a class="picture" href="index.html">
                                        <%ifnull !currentSone>
                                                <%ifnull !currentSone.profile.avatar>
-                                                       <%currentSone.profile.avatar|image-link max-width==80 max-height==80 mode==enlarge title=="Profile Avatar">
+                                                       <%currentSone.profile.avatar|build-id sone=currentSone|image-link max-width==80 max-height==80 mode==enlarge title=="Profile Avatar">
                                                <%else>
                                                        <img src="/WebOfTrust/GetIdenticon?identity=<% currentSone.id|html>&amp;width=80&amp;height=80" width="80" height="80" alt="Profile Avatar" />
                                                <%/if>
index fc51e79..a93b0a7 100644 (file)
@@ -2,7 +2,7 @@
        <div class="sone-menu-id hidden"><%sone.id|html></div>
        <div class="avatar menu-avatar">
                <%ifnull !sone.profile.avatar>
-                       <%sone.profile.avatar|image-link max-width==64 max-height==64 mode==enlarge title=sone.niceName>
+                       <%sone.profile.avatar|build-id sone=sone|image-link max-width==64 max-height==64 mode==enlarge title=sone.niceName>
                <%else>
                        <img src="/WebOfTrust/GetIdenticon?identity=<%sone.id|html>&amp;width=128&amp;height=128" width="64" height="64" alt="Avatar Image" />
                <%/if>
index 1c53d42..9a24671 100644 (file)
@@ -7,7 +7,7 @@
        <div class="avatar post-avatar" >
                <%if post.loaded>
                        <%ifnull !post.sone.profile.avatar>
-                               <%post.sone.profile.avatar|image-link max-width==48 max-height==48 mode==enlarge title=="Avatar Image">
+                               <%post.sone.profile.avatar|build-id sone=post.sone|image-link max-width==48 max-height==48 mode==enlarge title=="Avatar Image">
                        <%else>
                                <img src="/WebOfTrust/GetIdenticon?identity=<% post.sone.id|html>&amp;width=96&amp;height=96" width="48" height="48" alt="Avatar Image" />
                        <%/if>
index 5e2ed7d..2e56275 100644 (file)
@@ -6,7 +6,7 @@
        <%include include/soneMenu.html class=="sone-reply-menu" sone=reply.sone>
        <div class="avatar reply-avatar">
                <%ifnull !reply.sone.profile.avatar>
-                       <% reply.sone.profile.avatar|image-link max-width==36 max-height==36 mode==enlarge title=="Avatar Image">
+                       <% reply.sone.profile.avatar|build-id sone=reply.sone|image-link max-width==36 max-height==36 mode==enlarge title=="Avatar Image">
                <%else>
                        <img src="/WebOfTrust/GetIdenticon?identity=<% reply.sone.id|html>&amp;width=72&height=72" width="36" height="36" alt="Avatar Image" />
                <%/if>
index 747ad51..10e57eb 100644 (file)
                        <%/if>
                        <title><%album.title|xml></title>
                        <description><%album.description|xml></description>
-                       <album-image><%album.albumImage.id|xml></album-image>
+                       <album-image><%album.albumImage.internalId|xml></album-image>
                        <%foreach album.images image>
                        <%first>
                        <images>
                                <%/first>
                                <image>
-                                       <id><%image.id|xml></id>
+                                       <id><%image.internalId|xml></id>
                                        <creation-time><%image.creationTime|xml></creation-time>
                                        <key><%image.key|xml></key>
                                        <title><%image.title|xml></title>
index 091c93f..3caeb79 100644 (file)
@@ -159,6 +159,7 @@ public class FreenetInterfaceTest {
                byte[] imageData = new byte[] { 1, 2, 3, 4 };
                temporaryImage.setImageData(imageData);
                Image image = new ImageImpl("image-id");
+               image.modify().setSone(sone).update();
                InsertToken insertToken = freenetInterface.new InsertToken(image);
                InsertContext insertContext = mock(InsertContext.class);
                when(highLevelSimpleClient.getInsertContext(anyBoolean())).thenReturn(insertContext);
@@ -178,6 +179,7 @@ public class FreenetInterfaceTest {
                byte[] imageData = new byte[] { 1, 2, 3, 4 };
                temporaryImage.setImageData(imageData);
                Image image = new ImageImpl("image-id");
+               image.modify().setSone(sone).update();
                InsertToken insertToken = freenetInterface.new InsertToken(image);
                InsertContext insertContext = mock(InsertContext.class);
                when(highLevelSimpleClient.getInsertContext(anyBoolean())).thenReturn(insertContext);
index dc19195..6986e36 100644 (file)
@@ -30,6 +30,7 @@ import java.util.Set;
 import net.pterodactylus.sone.data.Album;
 import net.pterodactylus.sone.data.Album.Modifier;
 import net.pterodactylus.sone.data.Client;
+import net.pterodactylus.sone.data.IdBuilder;
 import net.pterodactylus.sone.data.Image;
 import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.data.PostReply;
@@ -435,8 +436,8 @@ public class SoneParserTest {
                when(imageBuilder.withId(anyString())).thenAnswer(new Answer<ImageBuilder>() {
                        @Override
                        public ImageBuilder answer(InvocationOnMock invocation) {
-                               when(image.getId()).thenReturn(
-                                               (String) invocation.getArguments()[0]);
+                               when(image.getId()).thenReturn(new IdBuilder().buildId("identity", (String) invocation.getArguments()[0]));
+                               when(image.getInternalId()).thenReturn((String) invocation.getArguments()[0]);
                                return imageBuilder;
                        }
                });
@@ -818,7 +819,8 @@ public class SoneParserTest {
                assertThat(sone.getRootAlbum().getAlbums(), hasSize(1));
                assertThat(sone.getRootAlbum().getAlbums().get(0).getImages(), hasSize(1));
                Image image = sone.getRootAlbum().getAlbums().get(0).getImages().get(0);
-               assertThat(image.getId(), is("image-id"));
+               assertThat(image.getId(), is(new IdBuilder().buildId("identity", "image-id")));
+               assertThat(image.getInternalId(), is("image-id"));
                assertThat(image.getCreationTime(), is(1407197508000L));
                assertThat(image.getKey(), is("KSK@GPLv3.txt"));
                assertThat(image.getTitle(), is("image-title"));
diff --git a/src/test/java/net/pterodactylus/sone/template/BuildIdFilterTest.java b/src/test/java/net/pterodactylus/sone/template/BuildIdFilterTest.java
new file mode 100644 (file)
index 0000000..d8d7bac
--- /dev/null
@@ -0,0 +1,51 @@
+package net.pterodactylus.sone.template;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.nullValue;
+import static org.mockito.Mockito.when;
+
+import net.pterodactylus.sone.data.Sone;
+import net.pterodactylus.util.template.TemplateContext;
+
+import com.google.common.collect.ImmutableMap;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+/**
+ * Unit test for {@link BuildIdFilter}.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class BuildIdFilterTest {
+
+       private static final String SONE_ID = "~Yp72VX0c6FLDvgIzip5wIvaGIIrjKcKvnX~pTaMKXs";
+       private static final String ELEMENT_ID = "88CC70AE-E853-4EEE-B245-E4C55F40DDDF";
+       private static final Object EXPECTED_ID = "139a629a13f6a2c4191fb19ecead7e57335ea3deb2a971b88d5e004378c4daad";
+
+       private final BuildIdFilter buildIdFilter = new BuildIdFilter();
+       private final TemplateContext templateContext = null;
+
+       @Test
+       public void filterBuildsCorrectIdsWithSoneAsString() {
+               assertThat(buildIdFilter.format(templateContext, ELEMENT_ID, ImmutableMap.<String, Object>of("sone", SONE_ID)), is(EXPECTED_ID));
+       }
+
+       @Test
+       public void filterBuildsCorrectIdsWithSoneAsSone() {
+               Sone sone = Mockito.mock(Sone.class);
+               when(sone.getId()).thenReturn(SONE_ID);
+               assertThat(buildIdFilter.format(templateContext, ELEMENT_ID, ImmutableMap.<String, Object>of("sone", sone)), is(EXPECTED_ID));
+       }
+
+       @Test
+       public void filterReturnsNullIfSoneNotPresent() {
+               assertThat(buildIdFilter.format(templateContext, ELEMENT_ID, ImmutableMap.<String, Object>of()), nullValue());
+       }
+
+       @Test
+       public void filterReturnsNullIfElementNotPresent() {
+               assertThat(buildIdFilter.format(templateContext, null, ImmutableMap.<String, Object>of("sone", SONE_ID)), nullValue());
+       }
+
+}