1 package org.apache.maven.search.backend.smo.internal;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.io.IOException;
23 import java.io.UnsupportedEncodingException;
24 import java.net.URLEncoder;
25 import java.nio.charset.StandardCharsets;
26 import java.util.ArrayList;
27 import java.util.Collections;
28 import java.util.HashMap;
29 import java.util.HashSet;
30 import java.util.List;
31 import java.util.Map;
32
33 import com.google.gson.JsonArray;
34 import com.google.gson.JsonElement;
35 import com.google.gson.JsonObject;
36 import com.google.gson.JsonParser;
37 import com.google.gson.JsonPrimitive;
38 import org.apache.maven.search.MAVEN;
39 import org.apache.maven.search.Record;
40 import org.apache.maven.search.SearchRequest;
41 import org.apache.maven.search.backend.smo.SmoSearchBackend;
42 import org.apache.maven.search.backend.smo.SmoSearchResponse;
43 import org.apache.maven.search.request.BooleanQuery;
44 import org.apache.maven.search.request.Field;
45 import org.apache.maven.search.request.FieldQuery;
46 import org.apache.maven.search.request.Paging;
47 import org.apache.maven.search.request.Query;
48 import org.apache.maven.search.support.SearchBackendSupport;
49
50 import static java.util.Objects.requireNonNull;
51
52 public class SmoSearchBackendImpl extends SearchBackendSupport implements SmoSearchBackend
53 {
54 public static final String DEFAULT_BACKEND_ID = "central-smo";
55
56 public static final String DEFAULT_REPOSITORY_ID = "central";
57
58 public static final String DEFAULT_SMO_URI = "https://search.maven.org/solrsearch/select";
59
60 private static final Map<Field, String> FIELD_TRANSLATION;
61
62 static
63 {
64 HashMap<Field, String> map = new HashMap<>();
65 map.put( MAVEN.GROUP_ID, "g" );
66 map.put( MAVEN.ARTIFACT_ID, "a" );
67 map.put( MAVEN.VERSION, "v" );
68 map.put( MAVEN.CLASSIFIER, "l" );
69 map.put( MAVEN.PACKAGING, "p" );
70 map.put( MAVEN.CLASS_NAME, "c" );
71 map.put( MAVEN.FQ_CLASS_NAME, "fc" );
72 map.put( MAVEN.SHA1, "1" );
73 FIELD_TRANSLATION = Collections.unmodifiableMap( map );
74 }
75
76 private final String smoUri;
77
78 private final SmoSearchTransportSupport transportSupport;
79
80
81
82
83 public SmoSearchBackendImpl()
84 {
85 this( DEFAULT_BACKEND_ID, DEFAULT_REPOSITORY_ID, DEFAULT_SMO_URI, new UrlConnectionSmoSearchTransport() );
86 }
87
88
89
90
91 public SmoSearchBackendImpl( String backendId, String repositoryId, String smoUri,
92 SmoSearchTransportSupport transportSupport )
93 {
94 super( backendId, repositoryId );
95 this.smoUri = requireNonNull( smoUri );
96 this.transportSupport = requireNonNull( transportSupport );
97 }
98
99 @Override
100 public String getSmoUri()
101 {
102 return smoUri;
103 }
104
105 @Override
106 public SmoSearchResponse search( SearchRequest searchRequest ) throws IOException
107 {
108 String searchUri = toURI( searchRequest );
109 String payload = transportSupport.fetch( searchRequest, searchUri );
110 JsonObject raw = JsonParser.parseString( payload ).getAsJsonObject();
111 List<Record> page = new ArrayList<>( searchRequest.getPaging().getPageSize() );
112 int totalHits = populateFromRaw( raw, page );
113 return new SmoSearchResponseImpl( searchRequest, totalHits, page, searchUri, payload );
114 }
115
116 private String toURI( SearchRequest searchRequest ) throws UnsupportedEncodingException
117 {
118 Paging paging = searchRequest.getPaging();
119 HashSet<Field> searchedFields = new HashSet<>();
120 String smoQuery = toSMOQuery( searchedFields, searchRequest.getQuery() );
121 smoQuery += "&start=" + paging.getPageSize() * paging.getPageOffset();
122 smoQuery += "&rows=" + paging.getPageSize();
123 smoQuery += "&wt=json";
124 if ( searchedFields.contains( MAVEN.GROUP_ID ) && searchedFields.contains( MAVEN.ARTIFACT_ID ) )
125 {
126 smoQuery += "&core=gav";
127 }
128 return smoUri + "?q=" + smoQuery;
129 }
130
131 private String toSMOQuery( HashSet<Field> searchedFields, Query query ) throws UnsupportedEncodingException
132 {
133 if ( query instanceof BooleanQuery.And )
134 {
135 BooleanQuery bq = (BooleanQuery) query;
136 return toSMOQuery( searchedFields, bq.getLeft() ) + "%20AND%20"
137 + toSMOQuery( searchedFields, bq.getRight() );
138 }
139 else if ( query instanceof FieldQuery )
140 {
141 FieldQuery fq = (FieldQuery) query;
142 String smoFieldName = FIELD_TRANSLATION.get( fq.getField() );
143 if ( smoFieldName != null )
144 {
145 searchedFields.add( fq.getField() );
146 return smoFieldName + ":" + encodeQueryParameterValue( fq.getValue() );
147 }
148 else
149 {
150 throw new IllegalArgumentException( "Unsupported SMO field: " + fq.getField() );
151 }
152 }
153 return encodeQueryParameterValue( query.getValue() );
154 }
155
156 private String encodeQueryParameterValue( String parameterValue ) throws UnsupportedEncodingException
157 {
158 return URLEncoder.encode( parameterValue, StandardCharsets.UTF_8.name() )
159 .replace( "+", "%20" );
160 }
161
162 private int populateFromRaw( JsonObject raw, List<Record> page )
163 {
164 JsonObject response = raw.getAsJsonObject( "response" );
165 Number numFound = response.get( "numFound" ).getAsNumber();
166
167 JsonArray docs = response.getAsJsonArray( "docs" );
168 for ( JsonElement doc : docs )
169 {
170 page.add( convert( (JsonObject) doc ) );
171 }
172 return numFound.intValue();
173 }
174
175 private Record convert( JsonObject doc )
176 {
177 HashMap<Field, Object> result = new HashMap<>();
178
179 mayPut( result, MAVEN.GROUP_ID, mayGet( "g", doc ) );
180 mayPut( result, MAVEN.ARTIFACT_ID, mayGet( "a", doc ) );
181 String version = mayGet( "v", doc );
182 if ( version == null )
183 {
184 version = mayGet( "latestVersion", doc );
185 }
186 mayPut( result, MAVEN.VERSION, version );
187 mayPut( result, MAVEN.PACKAGING, mayGet( "p", doc ) );
188 mayPut( result, MAVEN.CLASSIFIER, mayGet( "l", doc ) );
189
190
191 Number versionCount = doc.has( "versionCount" ) ? doc.get( "versionCount" ).getAsNumber() : null;
192 if ( versionCount != null )
193 {
194 mayPut( result, MAVEN.VERSION_COUNT, versionCount.intValue() );
195 }
196
197 JsonArray ec = doc.getAsJsonArray( "ec" );
198 if ( ec != null )
199 {
200 result.put( MAVEN.HAS_SOURCE, ec.contains( EC_SOURCE_JAR ) );
201 result.put( MAVEN.HAS_JAVADOC, ec.contains( EC_JAVADOC_JAR ) );
202
203 }
204
205 return new Record(
206 getBackendId(),
207 getRepositoryId(),
208 doc.has( "id" ) ? doc.get( "id" ).getAsString() : null,
209 doc.has( "timestamp" ) ? doc.get( "timestamp" ).getAsLong() : null,
210 result
211 );
212 }
213
214 private static final JsonPrimitive EC_SOURCE_JAR = new JsonPrimitive( "-sources.jar" );
215
216 private static final JsonPrimitive EC_JAVADOC_JAR = new JsonPrimitive( "-javadoc.jar" );
217
218 private static String mayGet( String field, JsonObject object )
219 {
220 return object.has( field ) ? object.get( field ).getAsString() : null;
221 }
222
223 private static void mayPut( Map<Field, Object> result, Field fieldName, Object value )
224 {
225 if ( value == null )
226 {
227 return;
228 }
229 if ( value instanceof String && ( (String) value ).trim().isEmpty() )
230 {
231 return;
232 }
233 result.put( fieldName, value );
234 }
235 }