View Javadoc

1   package eu.ehri.project.tools;
2   
3   import com.fasterxml.jackson.databind.JsonNode;
4   import com.fasterxml.jackson.databind.ObjectMapper;
5   import com.fasterxml.jackson.databind.node.ArrayNode;
6   import com.fasterxml.jackson.databind.node.ObjectNode;
7   import com.google.common.base.Preconditions;
8   import com.google.common.collect.ImmutableMap;
9   import com.tinkerpop.blueprints.Vertex;
10  import com.tinkerpop.frames.FramedGraph;
11  import eu.ehri.project.core.GraphManager;
12  import eu.ehri.project.core.GraphManagerFactory;
13  import eu.ehri.project.definitions.Entities;
14  import eu.ehri.project.definitions.Ontology;
15  import eu.ehri.project.exceptions.ItemNotFound;
16  import eu.ehri.project.models.annotations.EntityType;
17  import eu.ehri.project.models.idgen.GenericIdGenerator;
18  import eu.ehri.project.persistence.ActionManager;
19  import eu.ehri.project.persistence.Bundle;
20  import org.joda.time.DateTime;
21  import org.slf4j.Logger;
22  import org.slf4j.LoggerFactory;
23  
24  import java.io.IOException;
25  import java.util.Iterator;
26  import java.util.Map;
27  import java.util.UUID;
28  
29  /**
30   * Single-use code to upgrade the DB schema from version
31   * 1 to version 2.
32   * <p>
33   * Ideally, we'd use some declarative script for this, and were
34   * it not for the JSON data in Version nodes that represents
35   * prior incarnations of items, we could.
36   * <p>
37   * This function de-serializes the serialized data, upgrades it
38   * to the new format, and serializes it again.
39   */
40  public class DbUpgrader1to2 {
41      public static final Map<String, String> changeMap = ImmutableMap.<String, String>builder()
42              .put("historicalAgent", "HistoricalAgent")
43              .put("repository", "Repository")
44              .put("documentaryUnit", "DocumentaryUnit")
45              .put("virtualUnit", "VirtualUnit")
46              .put("systemEvent", "SystemEvent")
47              .put("documentDescription", "DocumentaryUnitDescription")
48              .put("repositoryDescription", "RepositoryDescription")
49              .put("historicalAgentDescription", "HistoricalAgentDescription")
50              .put("group", "Group")
51              .put("country", "Country")
52              .put("userProfile", "UserProfile")
53              .put("datePeriod", "DatePeriod")
54              .put("annotation", "Annotation")
55              .put("address", "Address")
56              .put("property", "UnknownProperty")
57              .put("permission", "Permission")
58              .put("permissionGrant", "PermissionGrant")
59              .put("contentType", "ContentType")
60              .put("version", "Version")
61              .put("link", "Link")
62              .put("cvocVocabulary", "CvocVocabulary")
63              .put("cvocConcept", "CvocConcept")
64              .put("cvocConceptDescription", "CvocConceptDescription")
65              .put("system", "System")
66              .put("maintenanceEvent", "MaintenanceEvent")
67              .put("relationship", "AccessPoint")
68              .put("authoritativeSet", "AuthoritativeSet")
69              .put("eventLink", "EventLink")
70              .build();
71  
72      public static final String OLD_ID_KEY = "__ID__";
73      public static final String OLD_TYPE_KEY = "__ISA__";
74  
75      private static final ObjectMapper jsonMapper = new ObjectMapper();
76      private static final Logger logger = LoggerFactory.getLogger(DbUpgrader1to2.class);
77  
78      private final FramedGraph<?> graph;
79      private final GraphManager manager;
80      private final OnChange onChange;
81  
82      public DbUpgrader1to2(FramedGraph<?> graph, OnChange onChange) {
83          this.graph = graph;
84          this.manager = GraphManagerFactory.getInstance(graph);
85          this.onChange = onChange;
86      }
87  
88      public interface OnChange {
89          void changed();
90      }
91  
92      public DbUpgrader1to2 setDbSchemaVersion() {
93          try {
94              Vertex vertex = manager.getVertex(ActionManager.GLOBAL_EVENT_ROOT);
95              vertex.setProperty("DB_SCHEMA", "2");
96              vertex.setProperty("DB_SCHEMA_DATE", DateTime.now().toString());
97              onChange.changed();
98              return this;
99          } catch (ItemNotFound e) {
100             throw new RuntimeException("Global Event Root not found!", e);
101         }
102     }
103 
104     public DbUpgrader1to2 upgradeIdAndTypeKeys() {
105         for (Vertex v : graph.getVertices()) {
106             Object oldId = v.getProperty(OLD_ID_KEY);
107             if (oldId != null) {
108                 v.setProperty(EntityType.ID_KEY, oldId);
109                 v.removeProperty(OLD_ID_KEY);
110             }
111             Object oldType = v.getProperty(OLD_TYPE_KEY);
112             if (oldType != null) {
113                 v.setProperty(EntityType.TYPE_KEY, oldType);
114                 v.removeProperty(OLD_TYPE_KEY);
115             }
116 
117             onChange.changed();
118         }
119         return this;
120     }
121 
122     public DbUpgrader1to2 setIdAndTypeOnEventLinks() {
123         for (Vertex v : graph.getVertices()) {
124             String dt = v.getProperty(ActionManager.DEBUG_TYPE);
125             if (ActionManager.EVENT_LINK.equalsIgnoreCase(dt)) {
126                 String id = manager.getId(v);
127                 if (id == null) {
128                     UUID timeBasedUUID = GenericIdGenerator.getTimeBasedUUID();
129                     v.setProperty(EntityType.ID_KEY, timeBasedUUID.toString());
130                     v.setProperty(EntityType.TYPE_KEY, Entities.EVENT_LINK);
131                     onChange.changed();
132                 }
133             }
134         }
135         return this;
136     }
137 
138     public DbUpgrader1to2 upgradeTypeValues() throws IOException {
139         for (Vertex v : graph.getVertices()) {
140             String oldType = v.getProperty(EntityType.TYPE_KEY);
141             if (changeMap.containsKey(oldType)) {
142                 String newType = changeMap.get(oldType);
143                 v.setProperty(EntityType.TYPE_KEY, newType);
144 
145                 // Content types have the type name as their ID, so rename
146                 // those at the same time.
147                 if (newType.equals(Entities.CONTENT_TYPE)) {
148                     String oldTypeId = v.getProperty(EntityType.ID_KEY);
149                     String newTypeId = changeMap.get(oldTypeId);
150                     v.setProperty(EntityType.ID_KEY, newTypeId);
151                 } else if (newType.equals(Entities.VERSION)) {
152                     String entityData = v.getProperty(Ontology.VERSION_ENTITY_DATA);
153                     JsonNode node = jsonMapper.readTree(entityData);
154                     if (!node.isObject()) {
155                         throw new RuntimeException("Unexpected JSON object: " + node.getNodeType());
156                     }
157                     ObjectNode before = jsonMapper.valueToTree(node);
158                     ObjectNode updated = upgradeNode(before);
159                     String after = jsonMapper.writeValueAsString(updated);
160 
161                     manager.setProperty(v, Ontology.VERSION_ENTITY_CLASS, newType);
162                     manager.setProperty(v, Ontology.VERSION_ENTITY_DATA, after);
163                 }
164 
165                 onChange.changed();
166             }
167         }
168         return this;
169     }
170 
171     /**
172      * Recursively updates types in a JSON bundle structure,
173      * returning a new node.
174      *
175      * @param beforeNode the original JSON
176      * @return the transformed JSON
177      */
178     public static ObjectNode upgradeNode(ObjectNode beforeNode) {
179         ObjectNode node = beforeNode.deepCopy();
180         JsonNode typeNode = node.path(Bundle.TYPE_KEY);
181         String typeText = typeNode.asText();
182         if (typeNode.isTextual() && changeMap.containsKey(typeText)) {
183             logger.trace("Renaming type key: {} -> {}", typeText, changeMap.get(typeText));
184             node.set(Bundle.TYPE_KEY, jsonMapper.valueToTree(changeMap.get(typeText)));
185             JsonNode rels = node.path(Bundle.REL_KEY);
186             if (!rels.isMissingNode()) {
187                 Preconditions.checkState(rels.isObject(), "Relations is not a map: " + rels.getNodeType());
188                 ObjectNode relObject = jsonMapper.valueToTree(rels);
189                 Iterator<Map.Entry<String, JsonNode>> fields = relObject.fields();
190                 while (fields.hasNext()) {
191                     Map.Entry<String, JsonNode> next = fields.next();
192                     JsonNode listNode = next.getValue();
193                     Preconditions.checkState(listNode.isArray(), "Relations contains a list");
194                     ArrayNode relList = jsonMapper.valueToTree(listNode);
195                     for (int i = 0; i < relList.size(); i++) {
196                         ObjectNode out = upgradeNode(jsonMapper.<ObjectNode>valueToTree(relList.path(i)));
197                         relList.set(i, out);
198                     }
199                     relObject.set(next.getKey(), relList);
200                 }
201                 node.set(Bundle.REL_KEY, relObject);
202             }
203         }
204         return node;
205     }
206 }