finding an entity by its unique attribute value.


    public static <T extends BaseEntity, Y> Optional<T> findByUniqueAttribute(
            final EntityManager entityManager,
            final Class<T> entityClass,
            final Supplier<SingularAttribute<? super T, Y>> attributeSupplier,
            final Supplier<? extends Y> valueSupplier) {
        final CriteriaBuilder builder = entityManager.getCriteriaBuilder();
        final CriteriaQuery<T> criteria = builder.createQuery(entityClass);
        final Root<T> root = criteria.from(entityClass);
        criteria.select(root);
        criteria.where(builder.equal(root.get(
                attributeSupplier.get()), valueSupplier.get()));
        try {
            return Optional.of(
                    entityManager.createQuery(criteria).getSingleResult());
        } catch (final NoResultException nre) {
            return Optional.empty();
        }
    }
    public static <T extends BaseEntity, Y> Optional<T> findByUniqueAttribute2(
            @NotNull final EntityManager entityManager,
            @NotNull final Class<T> entityClass,
            @NotNull final Function<ManagedType<T>, SingularAttribute<? super T, Y>> attributeSupplier,
            @NotNull final Supplier<? extends Y> valueSupplier) {
        final CriteriaBuilder builder = entityManager.getCriteriaBuilder();
        final CriteriaQuery<T> criteria = builder.createQuery(entityClass);
        final Root<T> root = criteria.from(entityClass);
        criteria.select(root);
        criteria.where(builder.equal(
                root.get(attributeSupplier.apply(
                        entityManager.getMetamodel().entity(entityClass))),
                valueSupplier.get()));
        try {
            return Optional.of(
                    entityManager.createQuery(criteria).getSingleResult());
        } catch (final NoResultException nre) {
            return Optional.empty();
        }
    }

delegation with default methods


@Entity
public class Property extends BaseEntity {

    public static interface Owner<T extends BaseEntity & Owner<T>> {

        List<Property> getProperties();

        default void setProperties(List<Property> properties) {
            getProperties().clear();
            getProperties().addAll(properties);
        }

        @SuppressWarnings("unchecked")
        default T properties(List<Property> properties) {
            setProperties(properties);
            return (T) this;
        }

        @SuppressWarnings("unchecked")
        default T addProperty(@NotNull final Property property) {
            getProperties().add(property);
            return (T) this;
        }

        default T addProperty(@NotNull final String name,
                              @NotNull final String value) {
            return addProperty(new Property().name(name).value(value));
        }

        default Stream<Property> propertyStream() {
            return getProperties().stream();
        }

        default T filterProperties(@NotNull final java.util.function.Predicate<Property> predicate) {
            return properties(propertyStream().filter(predicate).collect(toList()));
        }
    }

    @Column(name = "NAME", nullable = false)
    private String name;

    @Column(name = "VALUE_", nullable = false)
    private String value;
}
@Entity
public class Event extends BaseEntity implements Property.Owner<Event> {

    @Override
    public List<Property> getProperties() {
        return (properties = ofNullable(properties).orElse(new ArrayList<>()));
    }

    @OneToMany(cascade = {CascadeType.ALL}, fetch = FetchType.EAGER,
               mappedBy = "event", orphanRemoval = true)
    private List<Property> properties;
}

Producing ImageIO capabilities through JAX-RS, v3


@Path("/miscellaneous/imageIoCapabilities")
public class ImageIoCapabilitiesResource extends BaseResource {

    @XmlEnum
    public static enum Feature {
        FileSuffix() {
            @Override
            void probe(final Collection<ImageIoCapability> capabilities) {
                stream(getReaderFileSuffixes()).forEach(
                        value -> capabilities.add(
                                new ImageIoCapability().feature(this)
                                .readable(true).value(value))
                );
                stream(getWriterFileSuffixes()).forEach(
                        value -> capabilities.add(
                                capabilities.stream()
                                .filter(e -> this == e.feature
                                             && value.equals(e.value))
                                .findFirst()
                                .orElseGet(() -> new ImageIoCapability()
                                        .feature(this).value(value)
                                ).writable(true)));
            }
        }, FormatName() {
            @Override
            void probe(final Collection<ImageIoCapability> capabilities) {
                stream(getReaderFormatNames()).forEach(
                        value -> capabilities.add(
                                new ImageIoCapability().feature(this)
                                .readable(true).value(value))
                );
                stream(getWriterFormatNames()).forEach(
                        value -> capabilities.add(
                                capabilities.stream()
                                .filter(e -> this == e.feature
                                             && value.equals(e.value))
                                .findFirst()
                                .orElseGet(() -> new ImageIoCapability()
                                        .feature(this).value(value)
                                ).writable(true)));
            }
        }, MIMEType() {
            @Override
            void probe(final Collection<ImageIoCapability> capabilities) {
                stream(getReaderMIMETypes()).forEach(value -> capabilities.add(
                        new ImageIoCapability().feature(this).readable(true)
                        .value(value))
                );
                stream(getWriterMIMETypes()).forEach(value -> capabilities.add(
                        capabilities.stream()
                        .filter(e -> this == e.feature && value.equals(e.value))
                        .findFirst()
                        .orElseGet(() -> new ImageIoCapability().feature(this)
                                .value(value)
                        ).writable(true)));
            }
        };

        abstract void probe(Collection<ImageIoCapability> capabilities);
    }

    @XmlAccessorType(XmlAccessType.NONE)
    @XmlRootElement
    public static class ImageIoCapability {

        @Override
        public String toString() {
            return super.toString() + "{"
                   + "feature=" + feature
                   + ", readable=" + readable
                   + ", writable=" + writable
                   + ", value=" + value
                   + "}";
        }

        @Override
        public int hashCode() {
            int hash = 5;
            hash = 83 * hash + Objects.hashCode(this.feature);
            hash = 83 * hash + Objects.hashCode(this.value);
            return hash;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            final ImageIoCapability other = (ImageIoCapability) obj;
            if (!Objects.equals(this.value, other.value)) {
                return false;
            }
            if (this.feature != other.feature) {
                return false;
            }
            return true;
        }

        private ImageIoCapability feature(final Feature feature) {
            this.feature = feature;
            return this;
        }

        private ImageIoCapability readable(final boolean readable) {
            this.readable = readable;
            return this;
        }

        private ImageIoCapability writable(final boolean writable) {
            this.writable = writable;
            return this;
        }

        private ImageIoCapability value(final String value) {
            this.value = value;
            return this;
        }

        @XmlAttribute
        private Feature feature;

        @XmlAttribute
        private boolean readable;

        @XmlAttribute
        private boolean writable;

        @XmlValue
        private String value;
    }

    @GET
    @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
    public Response read(@MatrixParam("feature") final List<Feature> features) {
        final Set<ImageIoCapability> capabilities = new HashSet<>();
        Arrays.stream(Feature.values()).forEach(f -> f.probe(capabilities));
        final List<ImageIoCapability> entity = capabilities.stream()
                .filter(c -> features.isEmpty() || features.contains(c.feature))
                .sorted(comparing((ImageIoCapability c) -> c.feature)
                        .thenComparing(c -> c.value))
                .collect(toList());
        final CacheControl cacheControl = new CacheControl();
        cacheControl.setMaxAge((int) TimeUnit.DAYS.toSeconds(1));
        return Response.ok(new GenericEntity<List<ImageIoCapability>>(entity) {
        }).cacheControl(cacheControl).build();
    }
}

using sub resources with jax-rs


Direct Initialization

그냥 새로 만들고 돌아가는 데에 필요한 값들을 직접 설정한다.

@Path("/parents")
public class ParentsResource {

    @Path("/{parentId: \\d+}/children");
    public SubResource resourceSubResource(
            @PathParam("parentId") final long parentId) {
        return new ChildrenResource()
                .parentId(parentId)
                .persistenceService(persistenceService);
    }

    @Inject
    private PersistenceService persistenceService;
}

ResourceContext

ResourceContext를 이용하여 생성한다. 필요한 값들은 알아서 주입된다.

@Path("/parents")
public class ParentsResource {

    @Path("/{parentId: \\d+}/children");
    public SubResource resourceSubResource(
            @PathParam("parentId") final long parentId) {
        return resourceContext.getResource(ChildrenResource.class);
    }

    @Context
    private ResourceContext resourceContext;

    @Inject
    private PersistenceService persistenceService;
}

Direct Injection

Sub Resource 자체를 직접 주입받아서 사용한다.

@Path("/parents")
public class ParentsResource {

    @Path("/{parentId: \\d+}/children");
    public SubResource resourceSubResource(
            @PathParam("parentId") final long parentId) {
        return childrenResource;
    }

    @Inject
    private PersistenceService persistenceService;

    @Inject
    private ChildrenResource childrenResource;
}

이 경우 주의할 점이 하나 있는데, childrenResource 개체가 주입되는 시점이 그 리소스가 실제로 사용될 시점보다 앞선다는 점이다. 다음과 같이 ChildrenResource 클래스에서 @PostContruct 같은 생명주기 어노테이션을 사용할 경우 없는 값을 사용할 수 있다.
다음 코드를 기반으로 /parents 혹은 /parents/1 까지만 호출 했을 경우에도 childrenResource 는 주입이 되고, 그렇게 주입되기 전에 childrenResource 자체의 모든 주입위치에도 값들이 주입되게 되는데 아래와 같이 에러를 발생할 소지가 있다.

public class ChildrenResource {

    @PostConstruct
    private void fetchMatrix() {
        //parentName // NullPointerExeption
        //    = children.getMatrixParameters().getFirst("parentName");
    }

    @PathParam("children")
    private PathSegment children; // maybe null

    private String parentName;
}

다음과 같이 Optional을 사용할 수도 있지만 그리 좋다고는 볼 수 없다.

        parentName = ofNullable(children)
                .map(v -> v.getMatrixParameters().getFirst("parentName"))
                .orElse(null);

ResourceContext 클래스를 괜히, 늦게나마, 만들어 놓은게 아닌 듯 하다.

a wrong hint from netbeans


https://netbeans.org/bugzilla/show_bug.cgi?id=262707

public class User {

    public void setUsername(final String username) {
        if (this.username != null) {
            throw new IllegalArgumentException(
                    "already has a username");
        }
        this.username = username;
    }

    @NotNull
    private String username;
}

NetBeans shows a hint for the line 4.

Unnecessary test for null – the expression is never null

That’s simply wrong. NetBeans see the @NotNull and he (or she) thinks the field will never null at any time.

Producing ImageIO capabilities through JAX-RS, v2


import java.util.Arrays;
import java.util.logging.Logger;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlValue;
import javax.ws.rs.core.GenericEntity;
import javax.ws.rs.core.Response;
import javax.xml.bind.annotation.XmlEnum;
import static javax.imageio.ImageIO.getReaderFileSuffixes;
import static javax.imageio.ImageIO.getReaderFormatNames;
import static javax.imageio.ImageIO.getReaderMIMETypes;
import static javax.imageio.ImageIO.getWriterFileSuffixes;
import static javax.imageio.ImageIO.getWriterFormatNames;
import static javax.imageio.ImageIO.getWriterMIMETypes;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import static java.util.stream.Collectors.toList;
import javax.ws.rs.MatrixParam;
import static java.util.Arrays.stream;
import static java.util.Comparator.comparing;
import static java.util.logging.Logger.getLogger;
import kr.co.mediamonster.ippl.ws.rs.BaseResource;

@Path("/miscellaneous/imageIoCapabilities")
public class ImageIoCapabilitiesResource extends BaseResource {

    @XmlEnum
    public static enum Feature {
        FileSuffix() {
            @Override
            void probe(final Set<ImageIoCapability> capabilities) {
                stream(getReaderFileSuffixes()).forEach(
                        value -> capabilities.add(
                                new ImageIoCapability().feature(this)
                                .readable(true).value(value))
                );
                stream(getWriterFileSuffixes()).forEach(
                        value -> capabilities.add(
                                capabilities.stream()
                                .filter(e -> this == e.feature
                                             && value.equals(e.value))
                                .findFirst()
                                .orElseGet(() -> new ImageIoCapability()
                                        .feature(this).value(value)
                                ).writable(true)));
            }
        }, FormatName() {
            @Override
            void probe(final Set<ImageIoCapability> capabilities) {
                stream(getReaderFormatNames()).forEach(
                        value -> capabilities.add(
                                new ImageIoCapability().feature(this)
                                .readable(true).value(value))
                );
                stream(getWriterFormatNames()).forEach(
                        value -> capabilities.add(
                                capabilities.stream()
                                .filter(e -> this == e.feature
                                             && value.equals(e.value))
                                .findFirst()
                                .orElseGet(() -> new ImageIoCapability()
                                        .feature(this).value(value)
                                ).writable(true)));
            }
        }, MIMEType() {
            @Override
            void probe(final Set<ImageIoCapability> capabilities) {
                stream(getReaderMIMETypes()).forEach(value -> capabilities.add(
                        new ImageIoCapability().feature(this).readable(true)
                        .value(value))
                );
                stream(getWriterMIMETypes()).forEach(value -> capabilities.add(
                        capabilities.stream()
                        .filter(e -> this == e.feature && value.equals(e.value))
                        .findFirst()
                        .orElseGet(() -> new ImageIoCapability().feature(this)
                                .value(value)
                        ).writable(true)));
            }
        };

        abstract void probe(Set<ImageIoCapability> capabilities);
    }

    @XmlAccessorType(XmlAccessType.NONE)
    @XmlRootElement
    public static class ImageIoCapability {

        @Override
        public String toString() {
            return "ImageIoCapability{"
                   + "feature=" + feature
                   + ", readable=" + readable
                   + ", writable=" + writable
                   + ", value=" + value
                   + "}";
        }

        @Override
        public int hashCode() {
            int hash = 5;
            hash = 83 * hash + Objects.hashCode(this.feature);
            hash = 83 * hash + Objects.hashCode(this.value);
            return hash;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            final ImageIoCapability other = (ImageIoCapability) obj;
            if (!Objects.equals(this.value, other.value)) {
                return false;
            }
            if (this.feature != other.feature) {
                return false;
            }
            return true;
        }

        ImageIoCapability feature(final Feature feature) {
            this.feature = feature;
            return this;
        }

        ImageIoCapability readable(final boolean readable) {
            this.readable = readable;
            return this;
        }

        ImageIoCapability writable(final boolean writable) {
            this.writable = writable;
            return this;
        }

        ImageIoCapability value(final String value) {
            this.value = value;
            return this;
        }

        @XmlAttribute
        private Feature feature;

        @XmlAttribute
        private boolean readable;

        @XmlAttribute
        private boolean writable;

        @XmlValue
        private String value;
    }

    @GET
    @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
    public Response read(
            @MatrixParam("feature") final List<Feature> features) {
        final Set<ImageIoCapability> capabilities = new HashSet<>();
        Arrays.stream(Feature.values()).forEach(f -> f.probe(capabilities));
        return Response.ok(
                new GenericEntity<List<ImageIoCapability>>(
                        capabilities.stream()
                        .filter(c -> features.isEmpty()
                                     || features.contains(c.feature))
                        .sorted(comparing((ImageIoCapability c) -> c.feature)
                                .thenComparing(c -> c.value))
                        .collect(toList())) {
        }).build();
    }
}

Producing ImageIO capabilities through JAX-RS


@Path("/miscellaneous/imageIoCapabilities")
public class ImageIoCapabilitiesResource {

    @XmlEnum
    public static enum Feature {
        FileSuffix, FormatName, MIMEType
    }

    @XmlAccessorType(XmlAccessType.NONE)
    @XmlRootElement
    public static class ImageIoCapability {

        ImageIoCapability feature(final Feature feature) {
            this.feature = feature;
            return this;
        }

        ImageIoCapability readable(final boolean readable) {
            this.readable = readable;
            return this;
        }

        ImageIoCapability writable(final boolean writable) {
            this.writable = writable;
            return this;
        }

        ImageIoCapability value(final String value) {
            this.value = value;
            return this;
        }

        @XmlAttribute
        private Feature feature;

        @XmlAttribute
        private boolean readable;

        @XmlAttribute
        private boolean writable;

        @XmlValue
        private String value;
    }

    @GET
    @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
    public Response read(@MatrixParam("feature") final Feature feature) {
        final Map<String, ImageIoCapability> map = new HashMap<>();
        stream(getReaderFileSuffixes()).forEach(
                value -> map.put(
                        Feature.FileSuffix.name() + value,
                        new ImageIoCapability().feature(Feature.FileSuffix)
                        .readable(true).value(value)));
        stream(getWriterFileSuffixes()).forEach(
                value -> ofNullable(map.putIfAbsent(
                        Feature.FileSuffix.name() + value,
                        new ImageIoCapability().feature(Feature.FileSuffix).
                        writable(true).value(value)))
                .ifPresent(previous -> previous.writable(true)));
        stream(getReaderFormatNames()).forEach(
                value -> map.put(
                        Feature.FormatName + value,
                        new ImageIoCapability().feature(Feature.FormatName)
                        .readable(true).value(value)));
        stream(getWriterFormatNames()).forEach(
                value -> ofNullable(map.putIfAbsent(
                        Feature.FormatName + value, new ImageIoCapability()
                        .feature(Feature.FormatName).writable(true)
                        .value(value)))
                .ifPresent(previous -> previous.writable(true)));
        stream(getReaderMIMETypes()).forEach(
                value -> map.putIfAbsent(
                        Feature.MIMEType + value, new ImageIoCapability()
                        .feature(Feature.MIMEType).readable(true)
                        .value(value)));
        stream(getWriterMIMETypes()).forEach(
                value -> ofNullable(map.putIfAbsent(
                        Feature.MIMEType + value,
                        new ImageIoCapability().feature(Feature.MIMEType)
                        .writable(true).value(value)))
                .ifPresent(previous -> previous.writable(true)));
        return Response.ok(
                new GenericEntity<List<ImageIoCapability>>(
                        map.values().stream()
                        .filter(c -> ofNullable(feature)
                                .map(f -> f.equals(c.feature)).orElse(true))
                        .sorted(comparing((ImageIoCapability c) -> c.feature)
                                .thenComparing(c -> c.value))
                        .collect(toList())) {
        }).build();
    }
}

$ http --json GET http://.../miscellaneous/imageIoCapabilities | python -m json.tool
[
    {
        "feature": "FileSuffix",
        "readable": true,
        "value": "wbmp",
        "writable": true
    },
    {
        "feature": "FileSuffix",
        "readable": true,
        "value": "jpeg",
        "writable": true
    },
    {
        "feature": "FileSuffix",
        "readable": true,
        "value": "bmp",
        "writable": true
    },
    {
        "feature": "FileSuffix",
        "readable": true,
        "value": "png",
        "writable": true
    },
    {
        "feature": "FileSuffix",
        "readable": true,
        "value": "jpg",
        "writable": true
    },
    {
        "feature": "FileSuffix",
        "readable": true,
        "value": "gif",
        "writable": true
    },
    {
        "feature": "FormatName",
        "readable": true,
        "value": "WBMP",
        "writable": true
    },
    {
        "feature": "FormatName",
        "readable": true,
        "value": "bmp",
        "writable": true
    },
    {
        "feature": "FormatName",
        "readable": true,
        "value": "GIF",
        "writable": true
    },
    {
        "feature": "FormatName",
        "readable": true,
        "value": "jpeg",
        "writable": true
    },
    {
        "feature": "FormatName",
        "readable": true,
        "value": "BMP",
        "writable": true
    },
    {
        "feature": "FormatName",
        "readable": true,
        "value": "gif",
        "writable": true
    },
    {
        "feature": "FormatName",
        "readable": true,
        "value": "png",
        "writable": true
    },
    {
        "feature": "FormatName",
        "readable": true,
        "value": "PNG",
        "writable": true
    },
    {
        "feature": "FormatName",
        "readable": true,
        "value": "jpg",
        "writable": true
    },
    {
        "feature": "FormatName",
        "readable": true,
        "value": "wbmp",
        "writable": true
    },
    {
        "feature": "FormatName",
        "readable": true,
        "value": "JPG",
        "writable": true
    },
    {
        "feature": "FormatName",
        "readable": true,
        "value": "JPEG",
        "writable": true
    },
    {
        "feature": "MIMEType",
        "readable": true,
        "value": "image/x-png",
        "writable": true
    },
    {
        "feature": "MIMEType",
        "readable": true,
        "value": "image/vnd.wap.wbmp",
        "writable": true
    },
    {
        "feature": "MIMEType",
        "readable": true,
        "value": "image/gif",
        "writable": true
    },
    {
        "feature": "MIMEType",
        "readable": true,
        "value": "image/bmp",
        "writable": true
    },
    {
        "feature": "MIMEType",
        "readable": true,
        "value": "image/png",
        "writable": true
    },
    {
        "feature": "MIMEType",
        "readable": true,
        "value": "image/jpeg",
        "writable": true
    }
]

$ http --json GET "http://.../miscellaneous/imageIoCapabilities;feature=MIMEType" | python -m json.tool
[
    {
        "feature": "MIMEType",
        "readable": true,
        "value": "image/x-png",
        "writable": true
    },
    {
        "feature": "MIMEType",
        "readable": true,
        "value": "image/vnd.wap.wbmp",
        "writable": true
    },
    {
        "feature": "MIMEType",
        "readable": true,
        "value": "image/gif",
        "writable": true
    },
    {
        "feature": "MIMEType",
        "readable": true,
        "value": "image/bmp",
        "writable": true
    },
    {
        "feature": "MIMEType",
        "readable": true,
        "value": "image/png",
        "writable": true
    },
    {
        "feature": "MIMEType",
        "readable": true,
        "value": "image/jpeg",
        "writable": true
    }
]