diff --git a/openjpa-examples/image-gallery/pom.xml b/openjpa-examples/image-gallery/pom.xml
new file mode 100644
index 000000000..42f91583a
--- /dev/null
+++ b/openjpa-examples/image-gallery/pom.xml
@@ -0,0 +1,169 @@
+
+
+
+
+ 4.0.0
+
+
+ org.apache.openjpa
+ openjpa-parent
+ 2.1.0-SNAPSHOT
+
+
+ org.apache.openjpa.openjpa-examples
+ image-gallery
+ jar
+
+ Apache OpenJPA :: Examples - image-gallery
+ http://openjpa.apache.org/samples.html
+
+
+ UTF-8
+ DefaultLevel=WARN
+ ../../openjpa-project/checkstyle.xml
+ 256m
+ 1024m
+ -Xmx${test.jvm.maxheapsize} -XX:MaxPermSize=${test.jvm.maxpermsize}
+ ${test.jvm.arguments}
+
+
+
+
+
+
+
+ commons-lang
+ commons-lang
+ 2.4
+ test
+
+
+ commons-beanutils
+ commons-beanutils
+ 1.8.3
+ test
+
+
+ org.apache.geronimo.specs
+ geronimo-validation_1.0_spec
+ 1.0
+
+
+ org.apache.bval
+ org.apache.bval.bundle
+ 0.1-incubating-SNAPSHOT
+ test
+
+
+ org.apache.openjpa
+ openjpa-all
+ ${pom.version}
+
+
+ org.apache.derby
+ derby
+ 10.5.3.0_1
+ test
+
+
+ junit
+ junit
+ 3.8.1
+ test
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 2.1
+
+
+ 1.6
+
+ -proc:none
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ 2.3
+
+
+
+ true
+ true
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 2.4.3
+
+ ${surefire.jvm.args}
+ false
+ false
+ true
+
+
+
+
+
+
+
+ org.codehaus.mojo
+ openjpa-maven-plugin
+ 1.1
+
+ org/apache/openjpa/examples/gallery/model/*.class
+ true
+ true
+ src/test/resources/META-INF/persistence.xml
+
+
+
+ enhancer
+ process-classes
+
+ enhance
+
+
+
+
+
+ org.apache.openjpa
+ openjpa-all
+ ${pom.version}
+
+
+
+
+
+
diff --git a/openjpa-examples/image-gallery/src/main/java/org/apache/openjpa/example/gallery/ImageType.java b/openjpa-examples/image-gallery/src/main/java/org/apache/openjpa/example/gallery/ImageType.java
new file mode 100644
index 000000000..4dd40d249
--- /dev/null
+++ b/openjpa-examples/image-gallery/src/main/java/org/apache/openjpa/example/gallery/ImageType.java
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.openjpa.example.gallery;
+
+public enum ImageType {
+ JPEG,
+ GIF
+}
diff --git a/openjpa-examples/image-gallery/src/main/java/org/apache/openjpa/example/gallery/constraint/ImageConstraint.java b/openjpa-examples/image-gallery/src/main/java/org/apache/openjpa/example/gallery/constraint/ImageConstraint.java
new file mode 100644
index 000000000..0d7b4dc55
--- /dev/null
+++ b/openjpa-examples/image-gallery/src/main/java/org/apache/openjpa/example/gallery/constraint/ImageConstraint.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.openjpa.example.gallery.constraint;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import javax.validation.Constraint;
+import javax.validation.Payload;
+
+import org.apache.openjpa.example.gallery.ImageType;
+
+/**
+ * Type-level annotation used to specify an image constraint. Uses
+ * ImageValidator to perform the validation.
+ */
+@Documented
+@Constraint(validatedBy = ImageValidator.class)
+@Target({ TYPE })
+@Retention(RUNTIME)
+public @interface ImageConstraint {
+ String message() default "Image data is not a supported format.";
+ Class>[] groups() default {};
+ Class extends Payload>[] payload() default {};
+ ImageType[] value() default { ImageType.GIF, ImageType.JPEG };
+}
diff --git a/openjpa-examples/image-gallery/src/main/java/org/apache/openjpa/example/gallery/constraint/ImageContent.java b/openjpa-examples/image-gallery/src/main/java/org/apache/openjpa/example/gallery/constraint/ImageContent.java
new file mode 100644
index 000000000..024c1cad2
--- /dev/null
+++ b/openjpa-examples/image-gallery/src/main/java/org/apache/openjpa/example/gallery/constraint/ImageContent.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.openjpa.example.gallery.constraint;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import javax.validation.Constraint;
+import javax.validation.Payload;
+
+import org.apache.openjpa.example.gallery.ImageType;
+
+/**
+ * Attribute-level annotation used to specify an image content constraint. Uses
+ * ImageContentValidator to perform the validation.
+ */
+@Documented
+@Constraint(validatedBy = ImageContentValidator.class)
+@Target({ METHOD, FIELD })
+@Retention(RUNTIME)
+public @interface ImageContent {
+ String message() default "Image data is not a supported format.";
+ Class>[] groups() default {};
+ Class extends Payload>[] payload() default {};
+ ImageType[] value() default { ImageType.GIF, ImageType.JPEG };
+}
diff --git a/openjpa-examples/image-gallery/src/main/java/org/apache/openjpa/example/gallery/constraint/ImageContentValidator.java b/openjpa-examples/image-gallery/src/main/java/org/apache/openjpa/example/gallery/constraint/ImageContentValidator.java
new file mode 100644
index 000000000..6b0917e12
--- /dev/null
+++ b/openjpa-examples/image-gallery/src/main/java/org/apache/openjpa/example/gallery/constraint/ImageContentValidator.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.openjpa.example.gallery.constraint;
+
+import java.util.Arrays;
+import java.util.List;
+
+import javax.validation.ConstraintValidator;
+import javax.validation.ConstraintValidatorContext;
+
+import org.apache.openjpa.example.gallery.ImageType;
+
+/**
+ * Simple check that file format is of a supported type
+ */
+public class ImageContentValidator implements ConstraintValidator {
+
+ private List allowedTypes = null;
+ /**
+ * Configure the constraint validator based on the image
+ * types it should support.
+ * @param constraint the constraint definition
+ */
+ public void initialize(ImageContent constraint) {
+ allowedTypes = Arrays.asList(constraint.value());
+ }
+
+ /**
+ * Validate a specified value.
+ */
+ public boolean isValid(byte[] value, ConstraintValidatorContext context) {
+ if (value == null) {
+ return false;
+ }
+ // Verify the GIF header is either GIF87 or GIF89
+ if (allowedTypes.contains(ImageType.GIF)) {
+ String gifHeader = new String(value, 0, 6);
+ if (value.length >= 6 &&
+ (gifHeader.equalsIgnoreCase("GIF87a") ||
+ gifHeader.equalsIgnoreCase("GIF89a"))) {
+ return true;
+ }
+ }
+ // Verify the JPEG begins with SOI & ends with EOI
+ if (allowedTypes.contains(ImageType.JPEG)) {
+ if (value.length >= 4 &&
+ value[0] == 0xff && value[1] == 0xd8 &&
+ value[value.length - 2] == 0xff &&
+ value[value.length -1] == 0xd9) {
+ return true;
+ }
+ }
+ // Unknown file format
+ return false;
+ }
+}
diff --git a/openjpa-examples/image-gallery/src/main/java/org/apache/openjpa/example/gallery/constraint/ImageGroup.java b/openjpa-examples/image-gallery/src/main/java/org/apache/openjpa/example/gallery/constraint/ImageGroup.java
new file mode 100644
index 000000000..6cf7ada16
--- /dev/null
+++ b/openjpa-examples/image-gallery/src/main/java/org/apache/openjpa/example/gallery/constraint/ImageGroup.java
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.openjpa.example.gallery.constraint;
+
+/**
+ * Simple interface used to specify validation group: ImageGroup
+ */
+public interface ImageGroup {
+
+}
diff --git a/openjpa-examples/image-gallery/src/main/java/org/apache/openjpa/example/gallery/constraint/ImageValidator.java b/openjpa-examples/image-gallery/src/main/java/org/apache/openjpa/example/gallery/constraint/ImageValidator.java
new file mode 100644
index 000000000..105e18250
--- /dev/null
+++ b/openjpa-examples/image-gallery/src/main/java/org/apache/openjpa/example/gallery/constraint/ImageValidator.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.openjpa.example.gallery.constraint;
+
+import java.util.Arrays;
+import java.util.List;
+
+import javax.validation.ConstraintValidator;
+import javax.validation.ConstraintValidatorContext;
+
+import org.apache.openjpa.example.gallery.ImageType;
+import org.apache.openjpa.example.gallery.model.Image;
+
+/**
+ * Simple validator used to verify that image data is of a supported type
+ */
+public class ImageValidator implements ConstraintValidator {
+
+ private List allowedTypes = null;
+ /**
+ * Configure the constraint validator based on the image
+ * types it should support.
+ * @param constraint the constraint definition
+ */
+ public void initialize(ImageConstraint constraint) {
+ allowedTypes = Arrays.asList(constraint.value());
+ }
+
+ /**
+ * Validate a specified value.
+ */
+ public boolean isValid(Image value, ConstraintValidatorContext context) {
+ // JSR-303 best practice. Promotes the use of @NotNull to perform
+ // null checking.
+ if (value == null) {
+ return true;
+ }
+
+ // All these values will be pre-validated with @NotNull constraints
+ // so they are safe to use
+ byte[] data = value.getData();
+ String fileName = value.getFileName();
+ ImageType type = value.getType();
+
+ // Verify the GIF type is correct5, has the correct extension and
+ // the data header is either GIF87 or GIF89
+ if (allowedTypes.contains(ImageType.GIF) &&
+ type == ImageType.GIF &&
+ fileName.endsWith(".gif")) {
+ if (data != null && data.length >= 6) {
+ String gifHeader = new String(data, 0, 6);
+ if (gifHeader.equalsIgnoreCase("GIF87a") ||
+ gifHeader.equalsIgnoreCase("GIF89a")) {
+ return true;
+ }
+ }
+ }
+ // Verify the JPEG type is correct, has the correct extension and
+ // the data begins with SOI & ends with EOI markers
+ if (allowedTypes.contains(ImageType.JPEG) &&
+ value.getType() == ImageType.JPEG &&
+ (fileName.endsWith(".jpg") ||
+ fileName.endsWith(".jpeg"))) {
+ if (data.length >= 4 &&
+ data[0] == 0xff && data[1] == 0xd8 &&
+ data[data.length - 2] == 0xff &&
+ data[data.length - 1] == 0xd9) {
+ return true;
+ }
+ }
+ // Unknown file format
+ return false;
+ }
+}
diff --git a/openjpa-examples/image-gallery/src/main/java/org/apache/openjpa/example/gallery/constraint/SequencedImageGroup.java b/openjpa-examples/image-gallery/src/main/java/org/apache/openjpa/example/gallery/constraint/SequencedImageGroup.java
new file mode 100644
index 000000000..421e63cfa
--- /dev/null
+++ b/openjpa-examples/image-gallery/src/main/java/org/apache/openjpa/example/gallery/constraint/SequencedImageGroup.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.openjpa.example.gallery.constraint;
+
+import javax.validation.GroupSequence;
+import javax.validation.groups.Default;
+
+/**
+ * Sequenced validation group definition. When this validation group is
+ * used constraints within the Default group will validate first. If
+ * successful, constraints within the ImageGroup will validate next.
+ */
+@GroupSequence({Default.class, ImageGroup.class})
+public interface SequencedImageGroup {
+
+}
diff --git a/openjpa-examples/image-gallery/src/main/java/org/apache/openjpa/example/gallery/model/Album.java b/openjpa-examples/image-gallery/src/main/java/org/apache/openjpa/example/gallery/model/Album.java
new file mode 100644
index 000000000..731aa1df1
--- /dev/null
+++ b/openjpa-examples/image-gallery/src/main/java/org/apache/openjpa/example/gallery/model/Album.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.openjpa.example.gallery.model;
+
+import java.util.List;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.OneToMany;
+import javax.persistence.ManyToMany;
+import javax.validation.constraints.NotNull;
+
+/**
+ * Album entity. Contains references to zero or more images and zero or more
+ * image creators/authors.
+ */
+@Entity
+public class Album {
+
+ @Id
+ @GeneratedValue
+ private long id;
+
+ @NotNull
+ private String name;
+
+ @OneToMany
+ private List images;
+
+ @ManyToMany
+ private List creator;
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ public long getId() {
+ return id;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setImages(List images) {
+ this.images = images;
+ }
+
+ public List getImages() {
+ return images;
+ }
+
+ public void setCreator(List creator) {
+ this.creator = creator;
+ }
+
+ public List getCreator() {
+ return creator;
+ }
+}
diff --git a/openjpa-examples/image-gallery/src/main/java/org/apache/openjpa/example/gallery/model/Creator.java b/openjpa-examples/image-gallery/src/main/java/org/apache/openjpa/example/gallery/model/Creator.java
new file mode 100644
index 000000000..419b61920
--- /dev/null
+++ b/openjpa-examples/image-gallery/src/main/java/org/apache/openjpa/example/gallery/model/Creator.java
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.openjpa.example.gallery.model;
+
+import java.util.List;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.ManyToMany;
+import javax.persistence.OneToMany;
+import javax.persistence.OrderBy;
+import javax.validation.constraints.NotNull;
+
+/**
+ * Creator entity. Contains a reference to one or more images authored by
+ * an instance of the creator.
+ */
+@Entity
+public class Creator {
+
+ @Id
+ @GeneratedValue
+ private long id;
+
+ @NotNull(message="Photographer's first name must be specified.")
+ private String firstName;
+
+ @NotNull(message="Photographer's last name must be specified.")
+ private String lastName;
+
+ @OneToMany
+ @OrderBy("fileName")
+ private List images;
+
+ @ManyToMany
+ private List albums;
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ public long getId() {
+ return id;
+ }
+
+ public void setFirstName(String firstName) {
+ this.firstName = firstName;
+ }
+
+ public String getFirstName() {
+ return firstName;
+ }
+
+ public void setLastName(String lastName) {
+ this.lastName = lastName;
+ }
+
+ public String getLastName() {
+ return lastName;
+ }
+
+ public void setImages(List images) {
+ this.images = images;
+ }
+
+ public List getImages() {
+ return images;
+ }
+
+ public List getAlbums() {
+ return albums;
+ }
+
+ public void setAlbums(List albums) {
+ this.albums = albums;
+ }
+}
diff --git a/openjpa-examples/image-gallery/src/main/java/org/apache/openjpa/example/gallery/model/Image.java b/openjpa-examples/image-gallery/src/main/java/org/apache/openjpa/example/gallery/model/Image.java
new file mode 100644
index 000000000..78fcaf298
--- /dev/null
+++ b/openjpa-examples/image-gallery/src/main/java/org/apache/openjpa/example/gallery/model/Image.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.openjpa.example.gallery.model;
+
+import javax.persistence.Embedded;
+import javax.persistence.Entity;
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.ManyToOne;
+import javax.validation.Valid;
+import javax.validation.constraints.NotNull;
+
+import org.apache.openjpa.example.gallery.ImageType;
+import org.apache.openjpa.example.gallery.constraint.ImageConstraint;
+import org.apache.openjpa.example.gallery.constraint.ImageGroup;
+
+/**
+ * Image entity which makes use of several BV constraints.
+ */
+@Entity
+@ImageConstraint(groups=ImageGroup.class)
+public class Image {
+
+ private long id;
+ private ImageType type;
+ private String fileName;
+ private byte[] data;
+ private Location location;
+ private Creator creator;
+
+ @Id
+ @GeneratedValue
+ public long getId() {
+ return id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ @NotNull(message="Image type must be specified.")
+ @Enumerated(EnumType.STRING)
+ public ImageType getType() {
+ return type;
+ }
+
+ public void setType(ImageType type) {
+ this.type = type;
+ }
+
+ @NotNull(message="Image file name must not be null.")
+ public String getFileName() {
+ return fileName;
+ }
+
+ public void setFileName(String fileName) {
+ this.fileName = fileName;
+ }
+
+ @NotNull(message="Image data must not be null.")
+ public byte[] getData() {
+ return data;
+ }
+
+ public void setData(byte[] data) {
+ this.data = data;
+ }
+
+ @Valid
+ @Embedded
+ public Location getLocation() {
+ return location;
+ }
+
+ public void setLocation(Location location) {
+ this.location = location;
+ }
+
+ @ManyToOne
+ public Creator getCreator() {
+ return creator;
+ }
+
+ public void setCreator(Creator creator) {
+ this.creator = creator;
+ }
+}
diff --git a/openjpa-examples/image-gallery/src/main/java/org/apache/openjpa/example/gallery/model/Location.java b/openjpa-examples/image-gallery/src/main/java/org/apache/openjpa/example/gallery/model/Location.java
new file mode 100644
index 000000000..b8e144ff0
--- /dev/null
+++ b/openjpa-examples/image-gallery/src/main/java/org/apache/openjpa/example/gallery/model/Location.java
@@ -0,0 +1,72 @@
+package org.apache.openjpa.example.gallery.model;
+
+import javax.persistence.Column;
+import javax.persistence.Embeddable;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Pattern;
+import javax.validation.constraints.Size;
+
+/**
+ * Location embeddable with several BV constraints applied.
+ */
+@Embeddable
+public class Location {
+
+ @NotNull(message="City must be specified.")
+ private String city;
+
+ private String street;
+
+ private String state;
+
+ @NotNull(message="Country must be specified.")
+ @Size(message="Country must be 50 characters or less.", max=50)
+ @Column(length=50)
+ private String country;
+
+ @Size(message="Zip code must be 10 characters or less.", max=10)
+ @Pattern(message="Zip code must be 5 digits or use the 5+4 format.",
+ regexp="^\\d{5}(([\\-]|[\\+])\\d{4})?$")
+ @Column(length=10)
+ private String zipCode;
+
+ public void setCity(String city) {
+ this.city = city;
+ }
+
+ public String getCity() {
+ return city;
+ }
+
+ public void setStreet(String street) {
+ this.street = street;
+ }
+
+ public String getStreet() {
+ return street;
+ }
+
+ public void setState(String state) {
+ this.state = state;
+ }
+
+ public String getState() {
+ return state;
+ }
+
+ public void setCountry(String country) {
+ this.country = country;
+ }
+
+ public String getCountry() {
+ return country;
+ }
+
+ public void setZipCode(String zipCode) {
+ this.zipCode = zipCode;
+ }
+
+ public String getZipCode() {
+ return zipCode;
+ }
+}
diff --git a/openjpa-examples/image-gallery/src/test/java/org/apache/openjpa/example/gallery/TestJPAValidation.java b/openjpa-examples/image-gallery/src/test/java/org/apache/openjpa/example/gallery/TestJPAValidation.java
new file mode 100644
index 000000000..5e80f5686
--- /dev/null
+++ b/openjpa-examples/image-gallery/src/test/java/org/apache/openjpa/example/gallery/TestJPAValidation.java
@@ -0,0 +1,122 @@
+package org.apache.openjpa.example.gallery;
+
+import java.util.Set;
+
+import javax.persistence.EntityManager;
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.Persistence;
+import javax.validation.ConstraintViolation;
+import javax.validation.ConstraintViolationException;
+
+import org.apache.openjpa.example.gallery.model.Image;
+import org.apache.openjpa.example.gallery.model.Location;
+
+public class TestJPAValidation extends junit.framework.TestCase {
+
+ /**
+ * Shows usage of BV constraints with JPA at pre-update, pre-remove,
+ * and pre-persist lifecycle events.
+ */
+ public void testValidation() {
+ EntityManagerFactory emf =
+ Persistence.createEntityManagerFactory("BeanValidation");
+ EntityManager em = emf.createEntityManager();
+
+ // Create a valid location
+ Location loc = new Location();
+ loc.setCity("Rochester");
+ loc.setState("MN");
+ loc.setZipCode("55901");
+ loc.setCountry("USA");
+
+ // Create an Image with non-matching type and file extension
+ Image img = new Image();
+ img.setType(ImageType.JPEG);
+ img.setFileName("Winter_01.gif");
+ loadImage(img);
+ img.setLocation(loc);
+
+ // *** PERSIST ***
+ try {
+ em.getTransaction().begin();
+ System.out.println("Persisting an entity with non-matching extension and type");
+ em.persist(img);
+ fail();
+ } catch (ConstraintViolationException cve) {
+ // Transaction was marked for rollback, roll it back and
+ // start a new TX
+ em.getTransaction().rollback();
+ handleConstraintViolation(cve);
+ em.getTransaction().begin();
+ System.out.println("Fixing the file type and re-attempting the persist.");
+ img.setType(ImageType.GIF);
+ em.persist(img);
+ em.getTransaction().commit();
+ System.out.println("Persist was successful");
+ }
+
+ // *** UPDATE ***
+ try {
+ em.getTransaction().begin();
+ // Modify the file name to a non-matching file name
+ // and commit to trigger an update
+ System.out.println("Modifying file name to use an extension that does not");
+ System.out.println("match the file type. This will cause a CVE.");
+ img.setFileName("Winter_01.jpg");
+ em.getTransaction().commit();
+ fail();
+ } catch (ConstraintViolationException cve) {
+ // Handle the exception. The commit failed so the transaction
+ // was already rolled back.
+ System.out.println("Update failed as expected");
+ handleConstraintViolation(cve);
+ }
+ // The update failure caused img to be detached. It must be merged back
+ // into the persistence context.
+ img = em.merge(img);
+
+ // *** REMOVE ***
+ em.getTransaction().begin();
+ try {
+ // Remove the type and commit to trigger removal
+ System.out.println("Setting the type to an invalid type. This will cause a");
+ System.out.println("validation exception upon removal");
+ img.setType(null);
+ em.remove(img);
+ em.getTransaction().commit();
+ fail();
+ } catch (ConstraintViolationException cve) {
+ // Rollback the active transaction and handle the exception
+ em.getTransaction().rollback();
+ System.out.println("Remove failed as expected");
+ handleConstraintViolation(cve);
+ }
+ em.close();
+ emf.close();
+ System.out.println("Done");
+ }
+
+ // Handles constraint violations by printing out violation information
+ private static void handleConstraintViolation(ConstraintViolationException cve) {
+ Set> cvs = cve.getConstraintViolations();
+ for (ConstraintViolation> cv : cvs) {
+ System.out.println("------------------------------------------------");
+ System.out.println("Violation: " + cv.getMessage());
+ System.out.println("Entity: " + cv.getRootBeanClass().getSimpleName());
+ // The violation occurred on a leaf bean (embeddable)
+ if (cv.getLeafBean() != null && cv.getRootBean() != cv.getLeafBean()) {
+ System.out.println("Embeddable: " + cv.getLeafBean().getClass().getSimpleName());
+ }
+ System.out.println("Attribute: " + cv.getPropertyPath());
+ System.out.println("Invalid value: " + cv.getInvalidValue());
+ System.out.println("------------------------------------------------");
+ }
+ }
+
+ // Mock image loading utility... simply loads the GIF89a header to satisfy the
+ // constraint validator.
+ private static void loadImage(Image img) {
+ img.setData(new byte[] { 'G', 'I', 'F', '8', '9', 'a' });
+ }
+
+}
diff --git a/openjpa-examples/image-gallery/src/test/resources/META-INF/persistence.xml b/openjpa-examples/image-gallery/src/test/resources/META-INF/persistence.xml
new file mode 100644
index 000000000..c4914cd66
--- /dev/null
+++ b/openjpa-examples/image-gallery/src/test/resources/META-INF/persistence.xml
@@ -0,0 +1,50 @@
+
+
+
+
+
+ Example persistence unit for Bean Validation
+ org.apache.openjpa.example.gallery.model.Album
+ org.apache.openjpa.example.gallery.model.Creator
+ org.apache.openjpa.example.gallery.model.Image
+ org.apache.openjpa.example.gallery.model.Location
+ true
+ CALLBACK
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file