diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/BrokerImpl.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/BrokerImpl.java index daad72948..b2aaebfd6 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/BrokerImpl.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/BrokerImpl.java @@ -5075,4 +5075,8 @@ public class BrokerImpl } return _store.isCached(oids, loaded); }; + + protected boolean isFlushing() { + return ((_flags & FLAG_FLUSHING) != 0); + } } diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/SingleFieldManager.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/SingleFieldManager.java index 29e2ae52d..15fa9957e 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/SingleFieldManager.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/SingleFieldManager.java @@ -782,6 +782,12 @@ class SingleFieldManager // ensure generated IDs get assigned properly if (!logical) ((StateManagerImpl)sm).assignObjectId(false, true); + + // Call preFetch on this and any related persistent fields. + // This will ensure IDs get assigned to those that need them. + if (_broker.isFlushing()) { + ((StateManagerImpl)sm).preFlush(logical, call); + } } } diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/flush/Assignment.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/flush/Assignment.java new file mode 100644 index 000000000..9c01139d7 --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/flush/Assignment.java @@ -0,0 +1,77 @@ +/* + * 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.persistence.flush; + +import java.io.Serializable; + +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.SequenceGenerator; +import javax.persistence.Table; + +@Entity +@Table(name="FL_ASSIGN") +public class Assignment implements Serializable { + + private static final long serialVersionUID = -7707299604883998179L; + + @Id + @Column(name="ASSIGN_ID") + @SequenceGenerator(name="assignIdSeq", sequenceName="FL_ASSIGN_SEQ") + @GeneratedValue(generator="assignIdSeq", strategy=GenerationType.SEQUENCE) + protected Long assignId; + + @ManyToOne(cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH}, fetch=FetchType.LAZY) + @JoinColumn(name="TOPIC_ID") + protected Topic topic; + + @Column(name="ASSIGN_TEXT") + protected String assignText; + + public Long getAssignmentId() { + return assignId; + } + + public void setAssignmentId(Long assignId) { + this.assignId = assignId; + } + + public Topic getTopic() { + return topic; + } + + public void setTopic(Topic topic) { + this.topic = topic; + } + + public String getAssignmentText() { + return assignText; + } + + public void setAssignmentText(String assignText) { + this.assignText = assignText; + } +} diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/flush/ClassPeriod.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/flush/ClassPeriod.java new file mode 100644 index 000000000..dff41b3a4 --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/flush/ClassPeriod.java @@ -0,0 +1,93 @@ +/* + * 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.persistence.flush; + +import java.io.Serializable; +import java.util.Set; + +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.SequenceGenerator; +import javax.persistence.Table; + +import org.apache.openjpa.persistence.ElementDependent; + +@Entity +@Table(name="FL_CLP") +public class ClassPeriod implements Serializable { + + private static final long serialVersionUID = -5315185851562144594L; + + @Id + @Column(name="CLP_ID") + @SequenceGenerator(name="clpIdSeq", sequenceName="FL_CLP_SEQ") + @GeneratedValue(generator="clpIdSeq", strategy=GenerationType.SEQUENCE) + protected Long clpId; + + @Column(name="CLP_TEXT") + protected String clpText; + + @ManyToOne(cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH}, fetch=FetchType.LAZY) + @JoinColumn(name="COURSE_ID") + protected Course course; + + @OneToMany(mappedBy="clp",cascade=CascadeType.ALL,fetch=FetchType.EAGER) + @ElementDependent(true) + protected Set topics; + + public Set getTopics() { + return topics; + } + + public void setTopics(Set topics) { + this.topics = topics; + } + + public Long getClassPeriodId() { + return clpId; + } + + public void setClassPeriodId(Long clpId) { + this.clpId = clpId; + } + + public Course getCourse() { + return course; + } + + public void setCourse(Course course) { + this.course = course; + } + + public String getClassPeriodText() { + return clpText; + } + + public void setClassPeriodText(String clpText) { + this.clpText = clpText; + } +} diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/flush/Course.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/flush/Course.java new file mode 100644 index 000000000..4fdac3568 --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/flush/Course.java @@ -0,0 +1,79 @@ +/* + * 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.persistence.flush; + +import java.io.Serializable; +import java.util.Set; + +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.OneToMany; +import javax.persistence.SequenceGenerator; +import javax.persistence.Table; + +import org.apache.openjpa.persistence.ElementDependent; + +@Entity +@Table(name="FL_COURSE") +public class Course implements Serializable { + + private static final long serialVersionUID = -5351948190722744801L; + + @Id + @Column(name="COURSE_ID") + @SequenceGenerator(name="courseIdSeq", sequenceName="FL_COURSE_SEQ") + @GeneratedValue(generator="courseIdSeq", strategy=GenerationType.SEQUENCE) + protected Long courseId; + + @Column(name="COURSE_TEXT") + protected String courseText; + + @OneToMany(mappedBy="course", cascade=CascadeType.ALL, fetch=FetchType.EAGER) + @ElementDependent(true) + protected Set classPeriods; + + public Long getCourseId() { + return courseId; + } + + public void setCourseId(Long courseId) { + this.courseId = courseId; + } + + public String getCourseText() { + return courseText; + } + + public void setCourseText(String courseText) { + this.courseText = courseText; + } + + public Set getClassPeriods() { + return classPeriods; + } + + public void setClassPeriods(Set classPeriods) { + this.classPeriods = classPeriods; + } +} diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/flush/SubTopic.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/flush/SubTopic.java new file mode 100644 index 000000000..032501f7f --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/flush/SubTopic.java @@ -0,0 +1,77 @@ +/* + * 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.persistence.flush; + +import java.io.Serializable; + +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.SequenceGenerator; +import javax.persistence.Table; + +@Entity +@Table(name="FL_SUBTOPIC") +public class SubTopic implements Serializable { + + private static final long serialVersionUID = 1855479005964448251L; + + @Id + @Column(name="SUBTOPIC_ID") + @SequenceGenerator(name="subtopicIdSeq", sequenceName="FL_SUBTOPIC_SEQ") + @GeneratedValue(generator="subtopicIdSeq", strategy=GenerationType.SEQUENCE) + protected Long subtopicId; + + @ManyToOne(cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH}, fetch=FetchType.LAZY) + @JoinColumn(name="TOPIC_ID") + protected Topic topic; + + @Column(name="SUBTOPIC_TEXT") + protected String subtopicText; + + public Long getSubtopicId() { + return subtopicId; + } + + public void setSubtopicId(Long subtopicId) { + this.subtopicId = subtopicId; + } + + public Topic getTopic() { + return topic; + } + + public void setTopic(Topic topic) { + this.topic = topic; + } + + public String getSubtopicText() { + return subtopicText; + } + + public void setSubtopicText(String subtopicText) { + this.subtopicText = subtopicText; + } +} diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/flush/TestCascadingFlush.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/flush/TestCascadingFlush.java new file mode 100644 index 000000000..47093164c --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/flush/TestCascadingFlush.java @@ -0,0 +1,307 @@ +/* + * 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.persistence.flush; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.HashSet; +import java.util.Set; + +import javax.persistence.EntityManager; + +import org.apache.openjpa.jdbc.conf.JDBCConfiguration; +import org.apache.openjpa.persistence.OpenJPAPersistence; +import org.apache.openjpa.persistence.test.SingleEMFTestCase; + +public class TestCascadingFlush extends SingleEMFTestCase { + + boolean supportsNativeSequence = false; + + public void setUp() { + setUp(Assignment.class, ClassPeriod.class, Course.class, SubTopic.class, Topic.class, CLEAR_TABLES); + + try { + supportsNativeSequence = ((JDBCConfiguration) emf + .getConfiguration()).getDBDictionaryInstance() + .nextSequenceQuery != null; + } catch (Throwable t) { + supportsNativeSequence = false; + } + } + + /** + * Verifies flushing a complex bidirectional domain model results in retrieval and population of all sequence-gen + * ID values. + */ + public void testCascadingFlushBasic() { + if (!supportsNativeSequence) { + return; + } + EntityManager em = emf.createEntityManager(); + Long courseId = populate(em); + em.clear(); + Course course = em.find(Course.class, courseId); + verifyCascadingFlush(em, course); + em.close(); + } + + /** + * Verifies flushing a complex bidirectional domain model results in retrieval and population of all sequence-gen + * ID values using an detached, then merged entity graph. + */ + public void testCascadingFlushDetach() { + if (!supportsNativeSequence) { + return; + } + EntityManager em = emf.createEntityManager(); + Long courseId = populate(em); + em.clear(); + Course tmpCourse = em.find(Course.class, courseId); + + Course course = OpenJPAPersistence.cast(em).detachCopy(tmpCourse); + assertNotEquals(course, tmpCourse); + verifyCascadingFlush(em, course); + em.close(); + } + + /** + * Verifies flushing a complex bidirectional domain model results in retrieval of all sequence-gen ID values using + * an serialized (resulting in detach), then merged entity graph. + */ + public void testCascadingFlushSerialize() { + if (!supportsNativeSequence) { + return; + } + EntityManager em = emf.createEntityManager(); + try { + Long courseId = populate(em); + em.clear(); + Course tmpCourse = em.find(Course.class, courseId); + Course course = null; + try { + course = (Course)roundtrip(tmpCourse, false); + } catch (Throwable t) { + fail("Failed to serialize and deserialize persistent object."); + } + assertNotEquals(course, tmpCourse); + verifyCascadingFlush(em, course); + } + finally { + if (em != null) { + em.close(); + } + } + } + + + private void verifyCascadingFlush(EntityManager em, Course course) { + try { + beginTx(em); + // Add a class period to the graph + addClassPeriod(course); + + // Merge in the new entities + Course course2 = em.merge(course); + // Flush to the database. ID's should be assigned to all elements in + // the graph. + em.flush(); + // Verify all id's are assigned + assertTrue(course2.getCourseId() > 0); + assertNotNull(course2.getClassPeriods()); + Set cps = course2.getClassPeriods(); + assertTrue(cps.size() == 2); + for (ClassPeriod cp : cps) { + assertNotNull(cp); + assertTrue(cp.getClassPeriodId() > 0); + assertEquals(cp.getCourse(), course2); + Set topics = cp.getTopics(); + assertNotNull(topics); + assertTrue(topics.size() > 0); + for (Topic t : topics) { + assertNotNull(t); + assertTrue(t.getTopicId() > 0); + Set assignments = t.getAssignments(); + assertNotNull(assignments); + assertTrue(assignments.size() == 1); + for (Assignment a : assignments) { + assertNotNull(a); + assertTrue(a.getAssignmentId() > 0); + } + Set subTopics = t.getSubTopics(); + assertNotNull(subTopics); + assertTrue(subTopics.size() == 1); + for (SubTopic s : subTopics) { + assertNotNull(s); + assertTrue(s.getSubtopicId() > 0); + } + } + } + + commitTx(em); + } catch (Exception e) { + e.printStackTrace(); + em.getTransaction().rollback(); + fail(); + } + } + + public static Long populate(EntityManager em) { + + beginTx(em); + Course course = createNewCourse(); + em.persist(course); + em.flush(); + + commitTx(em); + return course.getCourseId(); + } + + public static Course createNewCourse() { + + Course course = new Course(); + course.setCourseText("Nuclear Physics"); + + Assignment assignment1 = new Assignment(); + assignment1.setAssignmentText("Lab: Nuclear Fusion"); + + Set assignments = new HashSet(); + assignments.add(assignment1); + + SubTopic subtopic1 = new SubTopic(); + subtopic1.setSubtopicText("Nuclear Fusion"); + + Set subtopics = new HashSet(); + subtopics.add(subtopic1); + + Topic topic1 = new Topic(); + topic1.setTopicText("Fundamentals of Nuclear Energy"); + topic1.setAssignments(assignments); + topic1.setSubTopics(subtopics); + + assignment1.setTopic(topic1); + subtopic1.setTopic(topic1); + + Set topics = new HashSet(); + topics.add(topic1); + + ClassPeriod cp1 = new ClassPeriod(); + cp1.setClassPeriodText("8844: M,W,Th 8:00AM"); + cp1.setTopics(topics); + cp1.setCourse(course); + + topic1.setClassPeriod(cp1); + + Set cps = new HashSet(); + cps.add(cp1); + + course.setClassPeriods(cps); + + return course; + } + + public static void addClassPeriod(Course course) { + + + Assignment assignment = new Assignment(); + assignment.setAssignmentText("Read pages 442-645"); + + Set assignments = new HashSet(); + assignments.add(assignment); + + SubTopic subTopic = new SubTopic(); + subTopic.setSubtopicText("Newton"); + + Set subTopics = new HashSet(); + subTopics.add(subTopic); + + Topic topic = new Topic(); + topic.setTopicText("Gravity"); + topic.setSubTopics(subTopics); + topic.setAssignments(assignments); + + assignment.setTopic(topic); + subTopic.setTopic(topic); + + Set topics = new HashSet(); + topics.add(topic); + + // Add another topic + Assignment assignment2 = new Assignment(); + assignment2.setAssignmentText("Read pages 645-785"); + + Set assignments2 = new HashSet(); + assignments2.add(assignment2); + + SubTopic subTopic2 = new SubTopic(); + subTopic2.setSubtopicText("Forces"); + + Set subTopics2 = new HashSet(); + subTopics2.add(subTopic2); + + Topic topic2 = new Topic(); + topic2.setTopicText("Magnetism"); + topic2.setSubTopics(subTopics2); + topic2.setAssignments(assignments2); + + subTopic2.setTopic(topic); + subTopic2.setTopic(topic); + + topics.add(topic2); + + ClassPeriod cp2 = new ClassPeriod(); + cp2.setClassPeriodText("8846: M,W,Th 11:00AM"); + cp2.setTopics(topics); + cp2.setCourse(course); + + topic.setClassPeriod(cp2); + topic2.setClassPeriod(cp2); + + course.getClassPeriods().add(cp2); + } + + private static void beginTx(EntityManager em) { + em.getTransaction().begin(); + } + + private static void commitTx(EntityManager em) { + em.getTransaction().commit(); + } + + public static Object roundtrip(Object orig, boolean validateEquality) + throws IOException, ClassNotFoundException { + assertNotNull(orig); + + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + ObjectOutputStream out = new ObjectOutputStream(bout); + out.writeObject(orig); + ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray()); + ObjectInputStream in = new ObjectInputStream(bin); + Object result = in.readObject(); + + if (validateEquality) { + assertEquals(orig.hashCode(), result.hashCode()); + assertEquals(orig, result); + } + + return result; + } +} diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/flush/Topic.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/flush/Topic.java new file mode 100644 index 000000000..5316bdee5 --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/flush/Topic.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.persistence.flush; + +import java.io.Serializable; +import java.util.Set; + +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.SequenceGenerator; +import javax.persistence.Table; + +import org.apache.openjpa.persistence.ElementDependent; + +@Entity +@Table(name="FL_TOPIC") +public class Topic implements Serializable { + + private static final long serialVersionUID = -2570150606711529060L; + + @Id + @Column(name="TOPIC_ID") + @SequenceGenerator(name="topicIdSeq", sequenceName="TOPIC_SEQ") + @GeneratedValue(generator="topicIdSeq", strategy=GenerationType.SEQUENCE) + protected Long topicId; + + @ManyToOne(cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH}, fetch=FetchType.LAZY) + @JoinColumn(name="CLP_ID") + protected ClassPeriod clp; + + @Column(name="TOPIC_TEXT") + protected String topicText; + + @OneToMany(mappedBy="topic",cascade=CascadeType.ALL, fetch=FetchType.EAGER) + @ElementDependent(true) + protected Set assignments; + + @OneToMany(mappedBy="topic",cascade=CascadeType.ALL, fetch=FetchType.EAGER) + @ElementDependent(true) + protected Set subTopics; + + public Long getTopicId() { + return topicId; + } + + public void setTopicId(Long topicId) { + this.topicId = topicId; + } + + public ClassPeriod getClassPeriod() { + return clp; + } + + public void setClassPeriod(ClassPeriod clp) { + this.clp = clp; + } + + public String getTopicText() { + return topicText; + } + + public void setTopicText(String topicText) { + this.topicText = topicText; + } + + public Set getAssignments() { + return assignments; + } + + public void setAssignments(Set assignments) { + this.assignments = assignments; + } + + public Set getSubTopics() { + return subTopics; + } + + public void setSubTopics(Set subTopics) { + this.subTopics = subTopics; + } +}