1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.search.backend.remoterepository.internal;
20
21 import java.io.BufferedReader;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.io.InputStreamReader;
25 import java.nio.charset.StandardCharsets;
26 import java.util.ArrayList;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Objects;
30 import java.util.Properties;
31
32 import org.apache.maven.search.Record;
33 import org.apache.maven.search.SearchRequest;
34 import org.apache.maven.search.backend.remoterepository.Context;
35 import org.apache.maven.search.backend.remoterepository.RecordFactory;
36 import org.apache.maven.search.backend.remoterepository.RemoteRepositorySearchBackend;
37 import org.apache.maven.search.backend.remoterepository.RemoteRepositorySearchResponse;
38 import org.apache.maven.search.backend.remoterepository.RemoteRepositorySearchTransport;
39 import org.apache.maven.search.backend.remoterepository.ResponseExtractor;
40 import org.apache.maven.search.support.SearchBackendSupport;
41 import org.jsoup.Jsoup;
42 import org.jsoup.nodes.Document;
43 import org.jsoup.parser.Parser;
44
45 import static java.util.Objects.requireNonNull;
46
47
48
49
50
51
52
53 public class RemoteRepositorySearchBackendImpl extends SearchBackendSupport implements RemoteRepositorySearchBackend {
54 private final String baseUri;
55
56 private final RemoteRepositorySearchTransport transport;
57
58 private final ResponseExtractor responseExtractor;
59
60 private final Map<String, String> commonHeaders;
61
62 protected enum State {
63 G,
64 GA,
65 GAV,
66 GAVCE,
67 GAVCE1
68 }
69
70
71
72
73 public RemoteRepositorySearchBackendImpl(
74 String backendId,
75 String repositoryId,
76 String baseUri,
77 RemoteRepositorySearchTransport transport,
78 ResponseExtractor responseExtractor) {
79 super(backendId, repositoryId);
80 this.baseUri = requireNonNull(baseUri);
81 this.transport = requireNonNull(transport);
82 this.responseExtractor = requireNonNull(responseExtractor);
83
84 this.commonHeaders = Map.of(
85 "User-Agent",
86 "Apache-Maven-Search-RR/" + discoverVersion() + " "
87 + transport.getClass().getSimpleName());
88 }
89
90 private String discoverVersion() {
91 Properties properties = new Properties();
92 InputStream inputStream = getClass()
93 .getClassLoader()
94 .getResourceAsStream(
95 "org/apache/maven/search/backend/smo/internal/remoterepository-version.properties");
96 if (inputStream != null) {
97 try (InputStream is = inputStream) {
98 properties.load(is);
99 } catch (IOException e) {
100
101 }
102 }
103 return properties.getProperty("version", "unknown");
104 }
105
106 @Override
107 public String getBaseUri() {
108 return baseUri;
109 }
110
111 @Override
112 public RemoteRepositorySearchResponse search(SearchRequest searchRequest) throws IOException {
113 Context context = new Context(searchRequest);
114 String uri = baseUri;
115 State state = null;
116 if (context.getGroupId() != null) {
117 uri += context.getGroupId().replace('.', '/') + "/";
118 state = State.G;
119 if (context.getArtifactId() != null) {
120 uri += context.getArtifactId() + "/";
121 state = State.GA;
122 if (context.getVersion() == null) {
123 uri += "maven-metadata.xml";
124 } else {
125 uri += context.getVersion() + "/";
126 state = State.GAV;
127 if (context.getFileExtension() != null) {
128
129 uri += context.getArtifactId() + "-" + context.getVersion();
130 if (context.getClassifier() != null) {
131 uri += "-" + context.getClassifier();
132 }
133 uri += "." + context.getFileExtension();
134 state = State.GAVCE;
135 if (context.getSha1() != null) {
136 state = State.GAVCE1;
137 }
138 }
139 }
140 }
141 }
142 if (state == null) {
143 throw new IllegalArgumentException("Unsupported Query: " + searchRequest.getQuery());
144 }
145
146 int totalHits = 0;
147 List<Record> page = new ArrayList<>(searchRequest.getPaging().getPageSize());
148 RecordFactory recordFactory = new RecordFactory(this);
149 Document document = null;
150 if (state.ordinal() < State.GAVCE.ordinal()) {
151 Parser parser = state == State.GA ? Parser.xmlParser() : Parser.htmlParser();
152 try (RemoteRepositorySearchTransport.Response response = transport.get(uri, commonHeaders)) {
153 if (response.getCode() == 200) {
154 document = Jsoup.parse(response.getBody(), StandardCharsets.UTF_8.name(), uri, parser);
155 }
156 }
157
158 if (document == null) {
159 throw new IOException("Unexpected response from: " + uri);
160 }
161
162 switch (state) {
163 case G:
164 totalHits = responseExtractor.populateG(context, document, recordFactory, page);
165 break;
166 case GA:
167 totalHits = responseExtractor.populateGA(context, document, recordFactory, page);
168 break;
169 case GAV:
170 totalHits = responseExtractor.populateGAV(context, document, recordFactory, page);
171 break;
172 default:
173 throw new IllegalStateException("State" + state);
174 }
175 } else {
176 try (RemoteRepositorySearchTransport.Response response = transport.head(uri, commonHeaders)) {
177 if (response.getCode() == 200) {
178 boolean matches = context.getSha1() == null;
179 if (context.getSha1() != null) {
180 try (RemoteRepositorySearchTransport.Response sha1Response =
181 transport.get(uri + ".sha1", commonHeaders)) {
182 if (response.getCode() == 200) {
183 try (InputStream body = sha1Response.getBody()) {
184 String remoteSha1 = readChecksum(body);
185 matches = Objects.equals(context.getSha1(), remoteSha1);
186 }
187 }
188 }
189 }
190 if (matches) {
191 page.add(recordFactory.create(
192 context.getGroupId(),
193 context.getArtifactId(),
194 context.getVersion(),
195 context.getClassifier(),
196 context.getFileExtension()));
197 totalHits = 1;
198 }
199 }
200 }
201 }
202 return new RemoteRepositorySearchResponseImpl(searchRequest, totalHits, page, uri, document);
203 }
204
205 private static String readChecksum(InputStream inputStream) throws IOException {
206 String checksum = "";
207 try (BufferedReader br = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8), 512)) {
208 while (true) {
209 String line = br.readLine();
210 if (line == null) {
211 break;
212 }
213 line = line.trim();
214 if (line.length() > 0) {
215 checksum = line;
216 break;
217 }
218 }
219 }
220
221 if (checksum.matches(".+= [0-9A-Fa-f]+")) {
222 int lastSpacePos = checksum.lastIndexOf(' ');
223 checksum = checksum.substring(lastSpacePos + 1);
224 } else {
225 int spacePos = checksum.indexOf(' ');
226
227 if (spacePos != -1) {
228 checksum = checksum.substring(0, spacePos);
229 }
230 }
231
232 return checksum;
233 }
234 }