Skip to content

Commit a70b82c

Browse files
authored
devonfw/ide-urls#17: fix UrlUpdater to auto-remove broken URLs and allow self-repair (#681)
1 parent 3e6fc65 commit a70b82c

22 files changed

+739
-212
lines changed
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package com.devonfw.tools.ide.io;
2+
3+
import java.net.URI;
4+
import java.net.http.HttpClient.Version;
5+
import java.net.http.HttpHeaders;
6+
import java.net.http.HttpRequest;
7+
import java.net.http.HttpResponse;
8+
import java.util.Collections;
9+
import java.util.Optional;
10+
import javax.net.ssl.SSLSession;
11+
12+
/**
13+
* Implementation of {@link HttpResponse} in case of an {@link Throwable error} to prevent sub-sequent {@link NullPointerException}s.
14+
*/
15+
public class HttpErrorResponse implements HttpResponse<Throwable> {
16+
17+
private final Throwable error;
18+
19+
private final HttpRequest request;
20+
21+
private final URI uri;
22+
private static final HttpHeaders NO_HEADERS = HttpHeaders.of(Collections.emptyMap(), (x, y) -> true);
23+
24+
/**
25+
* @param error the {@link Throwable} that was preventing the HTTP request for {@link #body()}.
26+
* @param request the {@link HttpRequest} for {@link #request()}.
27+
* @param uri the {@link URI} for {@link #uri()}.
28+
*/
29+
public HttpErrorResponse(Throwable error, HttpRequest request, URI uri) {
30+
super();
31+
this.error = error;
32+
this.request = request;
33+
this.uri = uri;
34+
}
35+
36+
@Override
37+
public int statusCode() {
38+
return -1;
39+
}
40+
41+
@Override
42+
public HttpRequest request() {
43+
return this.request;
44+
}
45+
46+
@Override
47+
public Optional<HttpResponse<Throwable>> previousResponse() {
48+
return Optional.empty();
49+
}
50+
51+
@Override
52+
public HttpHeaders headers() {
53+
return NO_HEADERS;
54+
}
55+
56+
@Override
57+
public Throwable body() {
58+
return this.error;
59+
}
60+
61+
@Override
62+
public Optional<SSLSession> sslSession() {
63+
return Optional.empty();
64+
}
65+
66+
@Override
67+
public URI uri() {
68+
return this.uri;
69+
}
70+
71+
@Override
72+
public Version version() {
73+
return Version.HTTP_2;
74+
}
75+
}

cli/src/main/java/com/devonfw/tools/ide/tool/eclipse/EclipseJavaUrlUpdater.java

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,7 @@
66
public class EclipseJavaUrlUpdater extends EclipseUrlUpdater {
77

88
@Override
9-
protected String getEdition() {
10-
11-
return "eclipse";
12-
}
13-
14-
@Override
9+
// getEdition() must still return "eclipse"
1510
protected String getEclipseEdition() {
1611

1712
return "java";

cli/src/main/java/com/devonfw/tools/ide/tool/eclipse/EclipseUrlUpdater.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@
1313
*/
1414
public abstract class EclipseUrlUpdater extends WebsiteUrlUpdater {
1515

16-
private static final String[] MIRRORS = { "https://ftp.snt.utwente.nl/pub/software/eclipse/technology/epp/downloads",
17-
"https://ftp.osuosl.org/pub/eclipse/technology/epp/downloads",
18-
"https://archive.eclipse.org/technology/epp/downloads" };
16+
private static final String[] MIRRORS = {
17+
"https://archive.eclipse.org/technology/epp/downloads",
18+
"https://ftp.osuosl.org/pub/eclipse/technology/epp/downloads" };
1919

2020
@Override
2121
protected String getTool() {
@@ -24,7 +24,7 @@ protected String getTool() {
2424
}
2525

2626
/**
27-
* @return the eclipse edition name.
27+
* @return the eclipse edition name. May be different from {@link #getEdition()} allowing a different edition name (e.g. eclipse) for IDEasy.
2828
*/
2929
protected String getEclipseEdition() {
3030

@@ -79,7 +79,6 @@ protected String getVersionUrl() {
7979
@Override
8080
protected Pattern getVersionPattern() {
8181

82-
// return Pattern.compile("\\d{4}-\\d{2}(\\s\\w{2})?");
8382
return Pattern.compile("\\d{4}-\\d{2}");
8483
}
8584

cli/src/main/java/com/devonfw/tools/ide/url/UpdateInitiator.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ public static void main(String[] args) {
3232

3333
String pathToRepo = args[0];
3434
Instant expirationTime = null;
35+
String selectedTool = null;
3536

3637
if (args.length < 2) {
3738
logger.warn("Timeout was not set, setting timeout to infinite instead.");
@@ -44,6 +45,9 @@ public static void main(String[] args) {
4445
logger.error("Error: Provided timeout format is not valid.", e);
4546
System.exit(1);
4647
}
48+
if (args.length > 2) {
49+
selectedTool = args[2];
50+
}
4751
}
4852

4953
Path repoPath = Path.of(pathToRepo);
@@ -56,7 +60,11 @@ public static void main(String[] args) {
5660
UrlFinalReport urlFinalReport = new UrlFinalReport();
5761

5862
UpdateManager updateManager = new UpdateManager(repoPath, urlFinalReport, expirationTime);
59-
updateManager.updateAll();
63+
if (selectedTool == null) {
64+
updateManager.updateAll();
65+
} else {
66+
updateManager.update(selectedTool);
67+
}
6068

6169
logger.info(urlFinalReport.toString());
6270
}

cli/src/main/java/com/devonfw/tools/ide/url/model/AbstractUrlArtifactWithParent.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,8 @@ public P getParent() {
3131
return this.parent;
3232
}
3333

34+
@Override
35+
public void delete() {
36+
getParent().deleteChild(getName());
37+
}
3438
}

cli/src/main/java/com/devonfw/tools/ide/url/model/UrlArtifactWithParent.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,12 @@ public interface UrlArtifactWithParent<P extends UrlFolder<?>> extends UrlArtifa
1313
* @return the parent {@link UrlFolder} owning this artifact as child.
1414
*/
1515
P getParent();
16+
17+
18+
/**
19+
* Physically deletes this artifact with all its potential children from the disc. Will also remove it from its {@link #getParent() parent}.
20+
*/
21+
default void delete() {
22+
getParent().deleteChild(getName());
23+
}
1624
}

cli/src/main/java/com/devonfw/tools/ide/url/model/file/AbstractUrlFile.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import java.nio.file.Files;
44

5+
import org.jline.utils.Log;
6+
57
import com.devonfw.tools.ide.url.model.AbstractUrlArtifactWithParent;
68
import com.devonfw.tools.ide.url.model.folder.AbstractUrlFolder;
79
import com.devonfw.tools.ide.url.model.folder.UrlFolder;
@@ -54,6 +56,7 @@ public void load(boolean recursive) {
5456
public void save() {
5557

5658
if (this.modified) {
59+
Log.debug("Saving {}", getPath());
5760
doSave();
5861
this.modified = false;
5962
}

cli/src/main/java/com/devonfw/tools/ide/url/model/file/UrlStatusFile.java

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import java.io.BufferedWriter;
55
import java.nio.file.Files;
66
import java.nio.file.Path;
7-
import java.nio.file.StandardOpenOption;
87

98
import com.devonfw.tools.ide.json.JsonMapping;
109
import com.devonfw.tools.ide.url.model.file.json.StatusJson;
@@ -23,7 +22,7 @@ public class UrlStatusFile extends AbstractUrlFile<UrlVersion> {
2322

2423
private static final ObjectMapper MAPPER = JsonMapping.create();
2524

26-
private StatusJson statusJson = new StatusJson();
25+
private StatusJson statusJson;
2726

2827
/**
2928
* The constructor.
@@ -33,6 +32,7 @@ public class UrlStatusFile extends AbstractUrlFile<UrlVersion> {
3332
public UrlStatusFile(UrlVersion parent) {
3433

3534
super(parent, STATUS_JSON);
35+
this.statusJson = new StatusJson();
3636
}
3737

3838
/**
@@ -70,11 +70,23 @@ protected void doLoad() {
7070
@Override
7171
protected void doSave() {
7272

73-
try (BufferedWriter writer = Files.newBufferedWriter(getPath(), StandardOpenOption.CREATE)) {
73+
try (BufferedWriter writer = Files.newBufferedWriter(getPath())) {
7474
MAPPER.writeValue(writer, this.statusJson);
7575
} catch (Exception e) {
7676
throw new IllegalStateException("Failed to save file " + getPath(), e);
7777
}
7878
}
7979

80+
/**
81+
* Performs a cleanup and removes all unused entries.
82+
*
83+
* @see StatusJson#cleanup()
84+
*/
85+
public void cleanup() {
86+
87+
boolean changed = this.statusJson.cleanup();
88+
if (changed) {
89+
this.modified = true;
90+
}
91+
}
8092
}

cli/src/main/java/com/devonfw/tools/ide/url/model/file/json/StatusJson.java

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package com.devonfw.tools.ide.url.model.file.json;
22

3+
import java.util.Iterator;
34
import java.util.LinkedHashMap;
45
import java.util.Map;
6+
import java.util.Map.Entry;
57

68
import com.devonfw.tools.ide.url.model.file.UrlStatusFile;
79

@@ -27,7 +29,7 @@ public StatusJson() {
2729

2830
/**
2931
* @return {@code true} if this file has been created manually and the containing version folder shall be ignored by the automatic update process,
30-
* {@code false} otherwise.
32+
* {@code false} otherwise.
3133
*/
3234
public boolean isManual() {
3335

@@ -58,13 +60,70 @@ public void setUrls(Map<Integer, UrlStatus> urlStatuses) {
5860
this.urls = urlStatuses;
5961
}
6062

63+
/**
64+
* @param url the URL to get the {@link UrlStatus} for.
65+
* @return the existing {@link UrlStatus} for the given URL or a {@code null} if not found.
66+
*/
67+
public UrlStatus getStatus(String url) {
68+
69+
return getStatus(url, false);
70+
}
71+
6172
/**
6273
* @param url the URL to get or create the {@link UrlStatus} for.
6374
* @return the existing {@link UrlStatus} for the given URL or a new {@link UrlStatus} associated with the given URL.
6475
*/
6576
public UrlStatus getOrCreateUrlStatus(String url) {
6677

78+
return getStatus(url, true);
79+
}
80+
81+
/**
82+
* @param url the URL to get or create the {@link UrlStatus} for.
83+
* @param create {@code true} for {@link #getOrCreateUrlStatus(String)} and {@code false} for {@link #getStatus(String)}.
84+
* @return the existing {@link UrlStatus} for the given URL or {@code null} or created status according to {@code create} flag.
85+
*/
86+
public UrlStatus getStatus(String url, boolean create) {
87+
88+
UrlStatus urlStatus;
89+
Integer key = computeKey(url);
90+
if (create) {
91+
urlStatus = this.urls.computeIfAbsent(key, hash -> new UrlStatus());
92+
} else {
93+
urlStatus = this.urls.get(key);
94+
}
95+
if (urlStatus != null) {
96+
urlStatus.markStillUsed();
97+
}
98+
return urlStatus;
99+
}
100+
101+
static Integer computeKey(String url) {
67102
Integer key = Integer.valueOf(url.hashCode());
68-
return this.urls.computeIfAbsent(key, hash -> new UrlStatus());
103+
return key;
104+
}
105+
106+
public void remove(String url) {
107+
108+
this.urls.remove(computeKey(url));
109+
}
110+
111+
/**
112+
* Performs a cleanup and removes all unused entries.
113+
*
114+
* @return {@code true} if something changed during cleanup, {@code false} otherwise.
115+
*/
116+
public boolean cleanup() {
117+
118+
boolean changed = false;
119+
Iterator<Entry<Integer, UrlStatus>> entryIterator = this.urls.entrySet().iterator();
120+
while (entryIterator.hasNext()) {
121+
Entry<Integer, UrlStatus> entry = entryIterator.next();
122+
if (!entry.getValue().checkStillUsed()) {
123+
entryIterator.remove();
124+
changed = true;
125+
}
126+
}
127+
return changed;
69128
}
70129
}

cli/src/main/java/com/devonfw/tools/ide/url/model/file/json/UrlStatus.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ public class UrlStatus {
99

1010
private UrlStatusState error;
1111

12+
private transient boolean stillUsed;
13+
1214
/**
1315
* The constructor.
1416
*/
@@ -48,4 +50,32 @@ public void setError(UrlStatusState error) {
4850

4951
this.error = error;
5052
}
53+
54+
/**
55+
* @return {@code true} if entirely empty, {@code false} otherwise.
56+
*/
57+
public boolean checkEmpty() {
58+
59+
return (this.error == null) && (this.success == null);
60+
}
61+
62+
/**
63+
* ATTENTION: This is not a standard getter (isStillUsed()) since otherwise Jackson will write it to JSON.
64+
*
65+
* @return {@code true} if still {@link StatusJson#getOrCreateUrlStatus(String) used}, {@code false} otherwise (if the entire version has been processed, it
66+
* will be removed via {@link StatusJson#cleanup()}.
67+
*/
68+
public boolean checkStillUsed() {
69+
70+
return this.stillUsed;
71+
}
72+
73+
/**
74+
* Sets {@link #checkStillUsed()} to {@code true}.
75+
*/
76+
void markStillUsed() {
77+
78+
this.stillUsed = true;
79+
}
80+
5181
}

0 commit comments

Comments
 (0)