Skip to content

Commit 07e5f4b

Browse files
committed
Add replication file generation and replication from files
This adds a facility to generate replication files in the "classical" OSM directory layout style and a facility to consume such files from a remote location via http.
1 parent 35ae356 commit 07e5f4b

10 files changed

+1010
-256
lines changed

pom.xml

Lines changed: 238 additions & 241 deletions
Large diffs are not rendered by default.

src/main/java/de/komoot/photon/App.java

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package de.komoot.photon;
22

3-
43
import com.beust.jcommander.JCommander;
54
import com.beust.jcommander.ParameterException;
5+
6+
import de.komoot.photon.elasticsearch.ReplicationUpdater;
7+
import de.komoot.photon.elasticsearch.Replicatior;
68
import de.komoot.photon.elasticsearch.Server;
79
import de.komoot.photon.nominatim.NominatimConnector;
810
import de.komoot.photon.nominatim.NominatimUpdater;
@@ -11,12 +13,12 @@
1113
import spark.Request;
1214
import spark.Response;
1315

16+
import java.io.File;
1417
import java.io.FileNotFoundException;
1518
import java.io.IOException;
1619

1720
import static spark.Spark.*;
1821

19-
2022
@Slf4j
2123
public class App {
2224

@@ -43,6 +45,11 @@ public static void main(String[] rawArgs) throws Exception {
4345
return;
4446
}
4547

48+
if (args.isReplicate()) {
49+
startReplicationUpdate(args);
50+
return;
51+
}
52+
4653
boolean shutdownES = false;
4754
final Server esServer = new Server(args).start();
4855
try {
@@ -63,11 +70,11 @@ public static void main(String[] rawArgs) throws Exception {
6370
// no special action specified -> normal mode: start search API
6471
startApi(args, esClient);
6572
} finally {
66-
if (shutdownES) esServer.shutdown();
73+
if (shutdownES)
74+
esServer.shutdown();
6775
}
6876
}
6977

70-
7178
/**
7279
* dump elastic search index and create a new and empty one
7380
*
@@ -83,7 +90,6 @@ private static void startRecreatingIndex(Server esServer) {
8390
log.info("deleted photon index and created an empty new one.");
8491
}
8592

86-
8793
/**
8894
* take nominatim data and dump it to json
8995
*
@@ -93,7 +99,8 @@ private static void startJsonDump(CommandLineArgs args) {
9399
try {
94100
final String filename = args.getJsonDump();
95101
final JsonDumper jsonDumper = new JsonDumper(filename, args.getLanguages());
96-
NominatimConnector nominatimConnector = new NominatimConnector(args.getHost(), args.getPort(), args.getDatabase(), args.getUser(), args.getPassword());
102+
NominatimConnector nominatimConnector = new NominatimConnector(args.getHost(), args.getPort(), args.getDatabase(), args.getUser(),
103+
args.getPassword());
97104
nominatimConnector.setImporter(jsonDumper);
98105
nominatimConnector.readEntireDatabase(args.getCountryCodes().split(","));
99106
log.info("json dump was created: " + filename);
@@ -102,7 +109,6 @@ private static void startJsonDump(CommandLineArgs args) {
102109
}
103110
}
104111

105-
106112
/**
107113
* take nominatim data to fill elastic search index
108114
*
@@ -126,11 +132,22 @@ private static void startNominatimImport(CommandLineArgs args, Server esServer,
126132
log.info("imported data from nominatim to photon with languages: " + args.getLanguages());
127133
}
128134

135+
/**
136+
* Run generation of replication files once
137+
*
138+
* @param args the arguments from the command line
139+
*/
140+
private static void startReplicationUpdate(CommandLineArgs args) {
141+
final NominatimUpdater nominatimUpdater = new NominatimUpdater(args.getHost(), args.getPort(), args.getDatabase(), args.getUser(), args.getPassword());
142+
Updater updater = new ReplicationUpdater(new File(args.getReplicationDirectory()), null);
143+
nominatimUpdater.setUpdater(updater);
144+
nominatimUpdater.update();
145+
}
129146

130147
/**
131148
* start api to accept search requests via http
132149
*
133-
* @param args
150+
* @param args the arguments from the command line
134151
* @param esNodeClient
135152
*/
136153
private static void startApi(CommandLineArgs args, Client esNodeClient) {
@@ -146,11 +163,19 @@ private static void startApi(CommandLineArgs args, Client esNodeClient) {
146163
// setup update API
147164
final NominatimUpdater nominatimUpdater = new NominatimUpdater(args.getHost(), args.getPort(), args.getDatabase(), args.getUser(), args.getPassword());
148165
Updater updater = new de.komoot.photon.elasticsearch.Updater(esNodeClient, args.getLanguages());
149-
nominatimUpdater.setUpdater(updater);
150-
151-
get("/nominatim-update", (Request request, Response response) -> {
152-
new Thread(() -> nominatimUpdater.update()).start();
153-
return "nominatim update started (more information in console output) ...";
154-
});
166+
String replicationUrl = args.getReplicationUrl();
167+
if (replicationUrl == null) {
168+
if (args.isGenerateFiles()) {
169+
updater = new ReplicationUpdater(new File(args.getReplicationDirectory()), updater);
170+
}
171+
nominatimUpdater.setUpdater(updater);
172+
get("/nominatim-update", (Request request, Response response) -> {
173+
new Thread(() -> nominatimUpdater.update()).start();
174+
return "nominatim update started (more information in console output) ...";
175+
});
176+
} else {
177+
Replicatior replicator = new Replicatior(new File(args.getReplicationDirectory()), args.getReplicationUrl(), args.getReplicationInterval(), updater);
178+
replicator.start();
179+
}
155180
}
156181
}

src/main/java/de/komoot/photon/CommandLineArgs.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,22 @@ public class CommandLineArgs {
5555

5656
@Parameter(names = "-listen-ip", description = "listen to address (default '0.0.0.0')")
5757
private String listenIp = "0.0.0.0";
58-
58+
59+
@Parameter(names = "-replicate", description = "generate a replication file")
60+
private boolean replicate = false;
61+
62+
@Parameter(names = "-replication-dir", description = "directory for replication files (default '.')")
63+
private String replicationDirectory = new File(".").getAbsolutePath();
64+
65+
@Parameter(names = "-generate-files", description = "generate replication files while updating from Nominatim")
66+
private boolean generateFiles = false;
67+
68+
@Parameter(names = "-replication-url", description = "url of base directory for file based replication (default none)")
69+
private String replicationUrl = null;
70+
71+
@Parameter(names = "-replication-interval", description = "interval between trying to read replication files in minutes (default 60 minutes)")
72+
private int replicationInterval = 60;
73+
5974
@Parameter(names = "-h", description = "show help / usage")
6075
private boolean usage = false;
6176
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package de.komoot.photon.elasticsearch;
2+
3+
import de.komoot.photon.PhotonDoc;
4+
5+
/**
6+
* Container class for an update action and a PhotonDoc
7+
*
8+
* @author Simon
9+
*
10+
*/
11+
public class PhotonAction {
12+
13+
public enum ACTION {CREATE, DELETE, UPDATE, UPDATE_OR_CREATE};
14+
15+
public ACTION action;
16+
17+
public long id;
18+
19+
public PhotonDoc doc;
20+
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
package de.komoot.photon.elasticsearch;
2+
3+
import java.io.BufferedInputStream;
4+
import java.io.ByteArrayOutputStream;
5+
import java.io.IOException;
6+
import java.io.InputStream;
7+
import java.net.HttpURLConnection;
8+
import java.net.URL;
9+
import java.nio.charset.Charset;
10+
import java.nio.file.Files;
11+
import java.nio.file.Path;
12+
import java.util.Date;
13+
14+
import javax.annotation.Nonnull;
15+
16+
import org.json.JSONObject;
17+
18+
import de.komoot.photon.utils.DateFormatter;
19+
import lombok.Getter;
20+
import lombok.Setter;
21+
22+
/**
23+
* Container for the replication state
24+
*
25+
* @author Simon
26+
*
27+
*/
28+
@Getter
29+
@Setter
30+
public class ReplicationState {
31+
32+
private static final String TIMESTAMP_KEY = "timestamp";
33+
private static final String FORMAT_KEY = "format";
34+
private static final String SEQUENCE_NUMBER_KEY = "sequenceNumber";
35+
36+
final long sequenceNumber;
37+
final String format;
38+
final Date timeStamp;
39+
40+
final DateFormatter dateFormatter;
41+
42+
/**
43+
* Construct a new instance
44+
*
45+
* @param sequenceNumber the current sequence number
46+
* @param format the format of the replication file
47+
* @param timeStamp the current time
48+
*/
49+
public ReplicationState(long sequenceNumber, @Nonnull String format, @Nonnull Date timeStamp) {
50+
this.sequenceNumber = sequenceNumber;
51+
this.format = format;
52+
this.timeStamp = timeStamp;
53+
dateFormatter = new DateFormatter();
54+
}
55+
56+
/**
57+
* Read the replication state from a local file
58+
*
59+
* @param stateFilePath the Path to the file
60+
* @return a ReplicationState instance
61+
* @throws IOException if something goes wrong
62+
*/
63+
@Nonnull
64+
public static ReplicationState readState(@Nonnull Path stateFilePath) throws IOException {
65+
String content = new String(Files.readAllBytes(stateFilePath), Charset.forName("UTF-8"));
66+
JSONObject state = new JSONObject(content);
67+
68+
return new ReplicationState(state.getLong(SEQUENCE_NUMBER_KEY), state.getString(FORMAT_KEY), null);
69+
}
70+
71+
/**
72+
* Read the replication state from an url
73+
*
74+
* @param urlString the url from the file
75+
* @return a ReplicationState instance
76+
* @throws IOException if something goes wrong
77+
*/
78+
@Nonnull
79+
public static ReplicationState readState(@Nonnull String urlString) throws IOException {
80+
URL url = new URL(urlString);
81+
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
82+
urlConnection.setRequestProperty("User-Agent", "Photon");
83+
urlConnection.setInstanceFollowRedirects(true);
84+
try {
85+
InputStream in = new BufferedInputStream(urlConnection.getInputStream());
86+
ByteArrayOutputStream result = new ByteArrayOutputStream();
87+
byte[] buffer = new byte[1024];
88+
int length;
89+
while ((length = in.read(buffer)) != -1) {
90+
result.write(buffer, 0, length);
91+
}
92+
93+
JSONObject state = new JSONObject(result.toString("UTF-8"));
94+
return new ReplicationState(state.getLong(SEQUENCE_NUMBER_KEY), state.getString(FORMAT_KEY), null);
95+
} finally {
96+
urlConnection.disconnect();
97+
}
98+
}
99+
100+
/**
101+
* Get a JSON representation of the replication state
102+
*
103+
* @return a JSONObject containg the state
104+
*/
105+
@Nonnull
106+
public JSONObject getJson() {
107+
final JSONObject state = new JSONObject();
108+
state.put(SEQUENCE_NUMBER_KEY, sequenceNumber);
109+
state.put(FORMAT_KEY, format);
110+
state.put(TIMESTAMP_KEY, dateFormatter.format(timeStamp));
111+
return state;
112+
}
113+
114+
/**
115+
* Get a JSON representation of the replication state as a String
116+
*
117+
* @return a String containing JSON
118+
*/
119+
@Nonnull
120+
public String toJsonString() {
121+
return getJson().toString(4);
122+
}
123+
}

0 commit comments

Comments
 (0)