1 package eu.ehri.project.tools;
2
3 import com.google.common.base.Preconditions;
4 import com.google.common.collect.Lists;
5 import com.tinkerpop.blueprints.CloseableIterable;
6 import com.tinkerpop.frames.FramedGraph;
7 import eu.ehri.project.core.GraphManager;
8 import eu.ehri.project.core.GraphManagerFactory;
9 import eu.ehri.project.definitions.EventTypes;
10 import eu.ehri.project.exceptions.ItemNotFound;
11 import eu.ehri.project.exceptions.SerializationError;
12 import eu.ehri.project.exceptions.ValidationError;
13 import eu.ehri.project.models.EntityClass;
14 import eu.ehri.project.models.base.Accessible;
15 import eu.ehri.project.models.base.Actioner;
16 import eu.ehri.project.persistence.ActionManager;
17 import eu.ehri.project.persistence.Bundle;
18 import eu.ehri.project.persistence.BundleManager;
19 import eu.ehri.project.persistence.Serializer;
20 import org.slf4j.Logger;
21 import org.slf4j.LoggerFactory;
22
23 import java.util.List;
24 import java.util.Optional;
25
26
27
28
29
30
31
32
33 public class FindReplace {
34
35 private static final Logger logger = LoggerFactory.getLogger(FindReplace.class);
36
37 private final FramedGraph<?> graph;
38 private final boolean commit;
39 private final int maxItems;
40 private final GraphManager manager;
41 private final ActionManager actionManager;
42 private final Serializer depSerializer;
43 private final BundleManager dao;
44
45 public FindReplace(FramedGraph<?> graph, boolean commit, int maxItems) {
46 this.graph = graph;
47 this.commit = commit;
48 this.maxItems = maxItems;
49 this.manager = GraphManagerFactory.getInstance(graph);
50 this.actionManager = new ActionManager(graph);
51 this.depSerializer = new Serializer.Builder(graph).dependentOnly().build();
52 this.dao = new BundleManager(graph);
53 }
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68 public List<List<String>> findAndReplace(
69 EntityClass contentType, EntityClass entityType,
70 String property, String textToFind, String replacement,
71 Actioner actioner, String logMessage) throws ValidationError {
72
73 Preconditions.checkNotNull(entityType, "Entity type cannot be null.");
74 Preconditions.checkNotNull(property, "Property name cannot be null.");
75 Preconditions.checkNotNull(textToFind, "Text to find cannot be null.");
76 Preconditions.checkArgument(!commit || replacement != null,
77 "Replacement text cannot be null if committing a replacement value.");
78 Preconditions.checkArgument(!commit || logMessage != null,
79 "Log message cannot be null if committing a replacement value.");
80
81 logger.info("Find: '{}'", textToFind);
82 logger.info("Replace: '{}'", replacement);
83
84 List<List<String>> todo = Lists.newArrayList();
85
86 EventTypes eventType = contentType.equals(entityType)
87 ? EventTypes.modification
88 : EventTypes.modifyDependent;
89 ActionManager.EventContext context = actionManager
90 .newEventContext(actioner, eventType, Optional.ofNullable(logMessage));
91
92 try (CloseableIterable<Accessible> entities = manager.getEntities(contentType, Accessible.class)) {
93 for (Accessible entity : entities) {
94 if (todo.size() >= maxItems) {
95 break;
96 }
97
98 Bundle bundle = depSerializer.entityToBundle(entity);
99 List<List<String>> matches = Lists.newArrayList();
100 bundle.map(d -> {
101 if (d.getType().equals(entityType)) {
102 Object v = d.getDataValue(property);
103 if (find(textToFind, v)) {
104 matches.add(Lists.newArrayList(entity.getId(),
105 d.getId(), v.toString()));
106 }
107 }
108 return d;
109 });
110
111 if (!matches.isEmpty()) {
112 todo.addAll(matches);
113 logger.info("Found in {}", entity.getId());
114
115 if (commit) {
116 context.createVersion(entity);
117 context.addSubjects(entity);
118
119 Bundle newBundle = bundle.map(d -> {
120 if (d.getType().equals(entityType)) {
121 Object newValue = replace(textToFind, replacement, d.getDataValue(property));
122 return d.withDataValue(property, newValue);
123 }
124 return d;
125 });
126 dao.update(newBundle, Accessible.class);
127 }
128 }
129 }
130 } catch (SerializationError | ItemNotFound e) {
131 throw new RuntimeException(e);
132 }
133
134 if (commit && !context.getSubjects().isEmpty()) {
135 context.commit();
136 }
137
138 return todo;
139 }
140
141 private boolean find(String needle, Object data) {
142 if (data != null) {
143 if (data instanceof List) {
144 for (Object v : ((List) data)) {
145 if (find(needle, v)) {
146 return true;
147 }
148 }
149 return false;
150 } else if (data instanceof String) {
151 return ((String) data).contains(needle);
152 }
153 }
154 return false;
155 }
156
157 private Object replace(String original, String replacement, Object data) {
158 if (data != null) {
159 if (data instanceof List) {
160 List<Object> newList = Lists.newArrayListWithExpectedSize(((List) data).size());
161 for (Object v : ((List) data)) {
162 newList.add(replace(original, replacement, v));
163 }
164 return newList;
165 } else if (data instanceof String) {
166 return ((String) data).replace(original, replacement);
167 }
168 }
169 return data;
170 }
171 }