1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.index.incremental;
20
21 import javax.inject.Named;
22 import javax.inject.Singleton;
23
24 import java.io.File;
25 import java.io.IOException;
26 import java.text.ParseException;
27 import java.text.SimpleDateFormat;
28 import java.util.ArrayList;
29 import java.util.Date;
30 import java.util.HashSet;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.Map.Entry;
34 import java.util.Properties;
35 import java.util.Set;
36 import java.util.TimeZone;
37 import java.util.TreeMap;
38
39 import org.apache.lucene.document.Document;
40 import org.apache.lucene.index.IndexReader;
41 import org.apache.lucene.index.MultiBits;
42 import org.apache.lucene.util.Bits;
43 import org.apache.maven.index.ArtifactInfo;
44 import org.apache.maven.index.context.IndexingContext;
45 import org.apache.maven.index.packer.IndexPackingRequest;
46 import org.apache.maven.index.updater.IndexUpdateRequest;
47 import org.codehaus.plexus.util.StringUtils;
48 import org.slf4j.Logger;
49 import org.slf4j.LoggerFactory;
50
51 @Singleton
52 @Named
53 public class DefaultIncrementalHandler implements IncrementalHandler {
54
55 private final Logger logger = LoggerFactory.getLogger(getClass());
56
57 protected Logger getLogger() {
58 return logger;
59 }
60
61 public List<Integer> getIncrementalUpdates(IndexPackingRequest request, Properties properties) throws IOException {
62 getLogger().debug("Handling Incremental Updates");
63
64 if (!validateProperties(properties)) {
65 getLogger().debug("Invalid properties found, resetting them and doing no incremental packing.");
66 return null;
67 }
68
69
70
71 List<Integer> chunk = getIndexChunk(request, parse(properties.getProperty(IndexingContext.INDEX_TIMESTAMP)));
72
73 getLogger().debug("Found " + chunk.size() + " differences to put in incremental index.");
74
75
76 if (chunk.size() > 0) {
77 updateProperties(properties, request);
78 }
79
80 cleanUpIncrementalChunks(request, properties);
81
82 return chunk;
83 }
84
85 public List<String> loadRemoteIncrementalUpdates(
86 IndexUpdateRequest request, Properties localProperties, Properties remoteProperties) throws IOException {
87 List<String> filenames = null;
88
89 if (canRetrieveAllChunks(localProperties, remoteProperties)) {
90 filenames = new ArrayList<>();
91
92 int maxCounter = Integer.parseInt(remoteProperties.getProperty(IndexingContext.INDEX_CHUNK_COUNTER));
93 int currentCounter = Integer.parseInt(localProperties.getProperty(IndexingContext.INDEX_CHUNK_COUNTER));
94
95
96 currentCounter++;
97
98 while (currentCounter <= maxCounter) {
99 filenames.add(IndexingContext.INDEX_FILE_PREFIX + "." + currentCounter++ + ".gz");
100 }
101 }
102
103 return filenames;
104 }
105
106 private boolean validateProperties(Properties properties) {
107 if (properties == null || properties.isEmpty()) {
108 return false;
109 }
110
111 if (properties.getProperty(IndexingContext.INDEX_TIMESTAMP) == null) {
112 return false;
113 }
114
115 if (parse(properties.getProperty(IndexingContext.INDEX_TIMESTAMP)) == null) {
116 return false;
117 }
118
119 initializeProperties(properties);
120
121 return true;
122 }
123
124 public void initializeProperties(Properties properties) {
125 if (properties.getProperty(IndexingContext.INDEX_CHAIN_ID) == null) {
126 properties.setProperty(IndexingContext.INDEX_CHAIN_ID, Long.toString(new Date().getTime()));
127 properties.remove(IndexingContext.INDEX_CHUNK_COUNTER);
128 }
129
130 if (properties.getProperty(IndexingContext.INDEX_CHUNK_COUNTER) == null) {
131 properties.setProperty(IndexingContext.INDEX_CHUNK_COUNTER, "0");
132 }
133 }
134
135 private List<Integer> getIndexChunk(IndexPackingRequest request, Date timestamp) throws IOException {
136 final List<Integer> chunk = new ArrayList<>();
137 final IndexReader r = request.getIndexReader();
138 Bits liveDocs = MultiBits.getLiveDocs(r);
139 for (int i = 0; i < r.maxDoc(); i++) {
140 if (liveDocs == null || liveDocs.get(i)) {
141 Document d = r.document(i);
142
143 String lastModified = d.get(ArtifactInfo.LAST_MODIFIED);
144
145 if (lastModified != null) {
146 Date t = new Date(Long.parseLong(lastModified));
147
148
149 if (t.after(timestamp)) {
150 chunk.add(i);
151 }
152 }
153 }
154 }
155
156 return chunk;
157 }
158
159 private void updateProperties(Properties properties, IndexPackingRequest request) throws IOException {
160 Set<Object> keys = new HashSet<>(properties.keySet());
161 Map<Integer, String> dataMap = new TreeMap<>();
162
163
164 for (Object key : keys) {
165 String sKey = (String) key;
166
167 if (sKey.startsWith(IndexingContext.INDEX_CHUNK_PREFIX)) {
168 Integer count = Integer.valueOf(sKey.substring(IndexingContext.INDEX_CHUNK_PREFIX.length()));
169 String value = properties.getProperty(sKey);
170
171 dataMap.put(count, value);
172 properties.remove(key);
173 }
174 }
175
176 String val = properties.getProperty(IndexingContext.INDEX_CHUNK_COUNTER);
177
178 int i = 0;
179
180 for (Entry<Integer, String> entry : dataMap.entrySet()) {
181
182 if (i >= (request.getMaxIndexChunks() - 1)) {
183 break;
184 }
185
186 properties.put(IndexingContext.INDEX_CHUNK_PREFIX + (entry.getKey() + 1), entry.getValue());
187
188 i++;
189 }
190
191 int nextValue = Integer.parseInt(val) + 1;
192
193
194 properties.put(IndexingContext.INDEX_CHUNK_PREFIX + "0", Integer.toString(nextValue));
195 properties.put(IndexingContext.INDEX_CHUNK_COUNTER, Integer.toString(nextValue));
196 }
197
198 private void cleanUpIncrementalChunks(IndexPackingRequest request, Properties properties) {
199 File[] files = request.getTargetDir().listFiles((dir, name) -> {
200 String[] parts = name.split("\\.");
201 return parts.length == 3 && parts[0].equals(IndexingContext.INDEX_FILE_PREFIX) && parts[2].equals("gz");
202 });
203
204 for (File file : files) {
205 String[] parts = file.getName().split("\\.");
206
207 boolean found = false;
208 for (Entry<Object, Object> entry : properties.entrySet()) {
209 if (entry.getKey().toString().startsWith(IndexingContext.INDEX_CHUNK_PREFIX)
210 && entry.getValue().equals(parts[1])) {
211 found = true;
212 break;
213 }
214 }
215
216 if (!found) {
217 file.delete();
218 }
219 }
220 }
221
222 private Date parse(String s) {
223 try {
224 SimpleDateFormat df = new SimpleDateFormat(IndexingContext.INDEX_TIME_FORMAT);
225 df.setTimeZone(TimeZone.getTimeZone("GMT"));
226 return df.parse(s);
227 } catch (ParseException e) {
228 return null;
229 }
230 }
231
232 private boolean canRetrieveAllChunks(Properties localProps, Properties remoteProps) {
233
234 if (localProps == null) {
235 return false;
236 }
237
238 String localChainId = localProps.getProperty(IndexingContext.INDEX_CHAIN_ID);
239 String remoteChainId = remoteProps.getProperty(IndexingContext.INDEX_CHAIN_ID);
240
241
242 if (StringUtils.isEmpty(localChainId) || !localChainId.equals(remoteChainId)) {
243 return false;
244 }
245
246 String counterProp = localProps.getProperty(IndexingContext.INDEX_CHUNK_COUNTER);
247
248
249
250 if (StringUtils.isEmpty(counterProp) || !StringUtils.isNumeric(counterProp)) {
251 return false;
252 }
253
254 int currentLocalCounter = Integer.parseInt(counterProp);
255
256
257
258 for (Object key : remoteProps.keySet()) {
259 String sKey = (String) key;
260
261 if (sKey.startsWith(IndexingContext.INDEX_CHUNK_PREFIX)) {
262 String value = remoteProps.getProperty(sKey);
263
264
265 if (Integer.toString(currentLocalCounter).equals(value)
266 || Integer.toString(currentLocalCounter + 1).equals(value)) {
267 return true;
268 }
269 }
270 }
271
272 return false;
273 }
274 }