001package org.apache.archiva.rest.services;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *   http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import org.apache.archiva.common.utils.VersionComparator;
023import org.apache.archiva.indexer.search.RepositorySearch;
024import org.apache.archiva.indexer.search.RepositorySearchException;
025import org.apache.archiva.indexer.search.SearchFields;
026import org.apache.archiva.indexer.search.SearchResultHit;
027import org.apache.archiva.indexer.search.SearchResultLimits;
028import org.apache.archiva.indexer.search.SearchResults;
029import org.apache.archiva.maven2.model.Artifact;
030import org.apache.archiva.metadata.model.ArtifactMetadata;
031import org.apache.archiva.metadata.repository.MetadataRepository;
032import org.apache.archiva.metadata.repository.MetadataRepositoryException;
033import org.apache.archiva.metadata.repository.RepositorySession;
034import org.apache.archiva.metadata.repository.RepositorySessionFactory;
035import org.apache.archiva.rest.api.model.ChecksumSearch;
036import org.apache.archiva.rest.api.model.GroupIdList;
037import org.apache.archiva.rest.api.model.SearchRequest;
038import org.apache.archiva.rest.api.model.StringList;
039import org.apache.archiva.rest.api.services.ArchivaRestServiceException;
040import org.apache.archiva.rest.api.services.SearchService;
041import org.apache.commons.collections.ListUtils;
042import org.apache.commons.lang.StringUtils;
043import org.springframework.stereotype.Service;
044
045import javax.inject.Inject;
046import javax.ws.rs.core.Response;
047import java.net.URI;
048import java.util.ArrayList;
049import java.util.Arrays;
050import java.util.Collection;
051import java.util.Collections;
052import java.util.HashSet;
053import java.util.List;
054import java.util.Set;
055import java.util.TreeMap;
056
057/**
058 * @author Olivier Lamy
059 */
060@Service( "searchService#rest" )
061public class DefaultSearchService
062    extends AbstractRestService
063    implements SearchService
064{
065
066    private static final String LATEST_KEYWORD = "LATEST";
067
068    @Inject
069    private RepositorySearch repositorySearch;
070
071    @Inject
072    private RepositorySessionFactory repositorySessionFactory;
073
074    @Override
075    public List<Artifact> quickSearch( String queryString )
076        throws ArchivaRestServiceException
077    {
078        if ( StringUtils.isBlank( queryString ) )
079        {
080            return Collections.emptyList();
081        }
082
083        SearchResultLimits limits = new SearchResultLimits( 0 );
084        try
085        {
086            SearchResults searchResults =
087                repositorySearch.search( getPrincipal(), getObservableRepos(), queryString, limits,
088                                         Collections.<String>emptyList() );
089            return getArtifacts( searchResults );
090
091        }
092        catch ( RepositorySearchException e )
093        {
094            log.error( e.getMessage(), e );
095            throw new ArchivaRestServiceException( e.getMessage(), e );
096        }
097    }
098
099    @Override
100    public List<Artifact> quickSearchWithRepositories( SearchRequest searchRequest )
101        throws ArchivaRestServiceException
102    {
103        String queryString = searchRequest.getQueryTerms();
104        if ( StringUtils.isBlank( queryString ) )
105        {
106            return Collections.emptyList();
107        }
108        List<String> repositories = searchRequest.getRepositories();
109        if ( repositories == null || repositories.isEmpty() )
110        {
111            repositories = getObservableRepos();
112        }
113        SearchResultLimits limits =
114            new SearchResultLimits( searchRequest.getPageSize(), searchRequest.getSelectedPage() );
115        try
116        {
117            SearchResults searchResults = repositorySearch.search( getPrincipal(), repositories, queryString, limits,
118                                                                   Collections.<String>emptyList() );
119            return getArtifacts( searchResults );
120
121        }
122        catch ( RepositorySearchException e )
123        {
124            log.error( e.getMessage(), e );
125            throw new ArchivaRestServiceException( e.getMessage(), e );
126        }
127    }
128
129    @Override
130    public List<Artifact> getArtifactVersions( String groupId, String artifactId, String packaging )
131        throws ArchivaRestServiceException
132    {
133        if ( StringUtils.isBlank( groupId ) || StringUtils.isBlank( artifactId ) )
134        {
135            return Collections.emptyList();
136        }
137        SearchFields searchField = new SearchFields();
138        searchField.setGroupId( groupId );
139        searchField.setArtifactId( artifactId );
140        searchField.setPackaging( StringUtils.isBlank( packaging ) ? "jar" : packaging );
141        searchField.setRepositories( getObservableRepos() );
142
143        try
144        {
145            SearchResults searchResults = repositorySearch.search( getPrincipal(), searchField, null );
146            return getArtifacts( searchResults );
147        }
148        catch ( RepositorySearchException e )
149        {
150            log.error( e.getMessage(), e );
151            throw new ArchivaRestServiceException( e.getMessage(), e );
152        }
153    }
154
155    @Override
156    public List<Artifact> searchArtifacts( SearchRequest searchRequest )
157        throws ArchivaRestServiceException
158    {
159        if ( searchRequest == null )
160        {
161            return Collections.emptyList();
162        }
163        SearchFields searchField = getModelMapper().map( searchRequest, SearchFields.class );
164        SearchResultLimits limits = new SearchResultLimits( 0 );
165        limits.setPageSize( searchRequest.getPageSize() );
166
167        // if no repos set we use ones available for the user
168        if ( searchField.getRepositories() == null || searchField.getRepositories().isEmpty() )
169        {
170            searchField.setRepositories( getObservableRepos() );
171        }
172
173        try
174        {
175            SearchResults searchResults = repositorySearch.search( getPrincipal(), searchField, limits );
176            return getArtifacts( searchResults );
177        }
178        catch ( RepositorySearchException e )
179        {
180            log.error( e.getMessage(), e );
181            throw new ArchivaRestServiceException( e.getMessage(), e );
182        }
183    }
184
185    @Override
186    public GroupIdList getAllGroupIds( List<String> selectedRepos )
187        throws ArchivaRestServiceException
188    {
189        List<String> observableRepos = getObservableRepos();
190        List<String> repos = ListUtils.intersection( observableRepos, selectedRepos );
191        if ( repos == null || repos.isEmpty() )
192        {
193            return new GroupIdList( Collections.<String>emptyList() );
194        }
195        try
196        {
197            return new GroupIdList( new ArrayList<>( repositorySearch.getAllGroupIds( getPrincipal(), repos ) ) );
198        }
199        catch ( RepositorySearchException e )
200        {
201            log.error( e.getMessage(), e );
202            throw new ArchivaRestServiceException( e.getMessage(), e );
203        }
204
205    }
206
207
208    public List<Artifact> getArtifactByChecksum( ChecksumSearch checksumSearch )
209        throws ArchivaRestServiceException
210    {
211
212        // if no repos set we use ones available for the user
213        if ( checksumSearch.getRepositories() == null || checksumSearch.getRepositories().isEmpty() )
214        {
215            checksumSearch.setRepositories( getObservableRepos() );
216        }
217
218        RepositorySession repositorySession = repositorySessionFactory.createSession();
219
220        MetadataRepository metadataRepository = repositorySession.getRepository();
221
222        Set<Artifact> artifactSet = new HashSet<>();
223
224        try
225        {
226            for ( String repoId : checksumSearch.getRepositories() )
227            {
228                Collection<ArtifactMetadata> artifactMetadatas =
229                    metadataRepository.getArtifactsByChecksum( repoId, checksumSearch.getChecksum() );
230                artifactSet.addAll( buildArtifacts( artifactMetadatas, repoId ) );
231            }
232
233            return new ArrayList<>( artifactSet );
234
235        }
236        catch ( MetadataRepositoryException e )
237        {
238            log.error( e.getMessage(), e );
239            throw new ArchivaRestServiceException( e.getMessage(), e );
240        }
241        finally
242        {
243            repositorySession.closeQuietly();
244        }
245
246
247    }
248
249    @Override
250    public StringList getObservablesRepoIds()
251        throws ArchivaRestServiceException
252    {
253        return new StringList( getObservableRepos() );
254    }
255
256    @Override
257    public Response redirectToArtifactFile( String repositoryId, String groupId, String artifactId, String version,
258                                            String packaging, String classifier )
259        throws ArchivaRestServiceException
260    {
261        try
262        {
263            // validate query
264
265            if ( StringUtils.isEmpty( groupId ) )
266            {
267                return Response.status( new Response.StatusType()
268                {
269                    @Override
270                    public int getStatusCode()
271                    {
272                        return Response.Status.BAD_REQUEST.getStatusCode();
273                    }
274
275                    @Override
276                    public Response.Status.Family getFamily()
277                    {
278                        return Response.Status.BAD_REQUEST.getFamily();
279                    }
280
281                    @Override
282                    public String getReasonPhrase()
283                    {
284                        return "groupId mandatory";
285                    }
286                } ).build();
287            }
288
289            if ( StringUtils.isEmpty( version ) )
290            {
291                return Response.status( new Response.StatusType()
292                {
293                    @Override
294                    public int getStatusCode()
295                    {
296                        return Response.Status.BAD_REQUEST.getStatusCode();
297                    }
298
299                    @Override
300                    public Response.Status.Family getFamily()
301                    {
302                        return Response.Status.BAD_REQUEST.getFamily();
303                    }
304
305                    @Override
306                    public String getReasonPhrase()
307                    {
308                        return "version mandatory";
309                    }
310                } ).build();
311            }
312
313            if ( StringUtils.isEmpty( artifactId ) )
314            {
315                return Response.status( new Response.StatusType()
316                {
317                    @Override
318                    public int getStatusCode()
319                    {
320                        return Response.Status.BAD_REQUEST.getStatusCode();
321                    }
322
323                    @Override
324                    public Response.Status.Family getFamily()
325                    {
326                        return Response.Status.BAD_REQUEST.getFamily();
327                    }
328
329                    @Override
330                    public String getReasonPhrase()
331                    {
332                        return "artifactId mandatory";
333                    }
334                } ).build();
335            }
336
337            SearchFields searchField = new SearchFields();
338            searchField.setGroupId( groupId );
339            searchField.setArtifactId( artifactId );
340            searchField.setPackaging( StringUtils.isBlank( packaging ) ? "jar" : packaging );
341            if ( !StringUtils.equals( version, LATEST_KEYWORD ) )
342            {
343                searchField.setVersion( version );
344            }
345            searchField.setClassifier( classifier );
346            List<String> userRepos = getObservablesRepoIds().getStrings();
347            searchField.setRepositories(
348                StringUtils.isEmpty( repositoryId ) ? userRepos : Arrays.asList( repositoryId ) );
349            searchField.setExactSearch( true );
350            SearchResults searchResults = repositorySearch.search( getPrincipal(), searchField, null );
351            List<Artifact> artifacts = getArtifacts( searchResults );
352
353            if ( artifacts.isEmpty() )
354            {
355                return Response.status( new Response.StatusType()
356                {
357                    @Override
358                    public int getStatusCode()
359                    {
360                        return Response.Status.NO_CONTENT.getStatusCode();
361                    }
362
363                    @Override
364                    public Response.Status.Family getFamily()
365                    {
366                        return Response.Status.NO_CONTENT.getFamily();
367                    }
368
369                    @Override
370                    public String getReasonPhrase()
371                    {
372                        return "your query doesn't return any artifact";
373                    }
374                } ).build();
375            }
376
377            // TODO improve that with querying lucene with null value for classifier
378            // so simple loop and retain only artifact with null classifier
379            if ( classifier == null )
380            {
381                List<Artifact> filteredArtifacts = new ArrayList<>( artifacts.size() );
382                for ( Artifact artifact : artifacts )
383                {
384                    if ( artifact.getClassifier() == null )
385                    {
386                        filteredArtifacts.add( artifact );
387                    }
388                }
389
390                artifacts = filteredArtifacts;
391            }
392
393            // TODO return json result of the query ?
394            if ( artifacts.size() > 1 && !StringUtils.equals( version, LATEST_KEYWORD ) )
395            {
396                return Response.status( new Response.StatusType()
397                {
398                    @Override
399                    public int getStatusCode()
400                    {
401                        return Response.Status.BAD_REQUEST.getStatusCode();
402                    }
403
404                    @Override
405                    public Response.Status.Family getFamily()
406                    {
407                        return Response.Status.BAD_REQUEST.getFamily();
408                    }
409
410                    @Override
411                    public String getReasonPhrase()
412                    {
413                        return "your query return more than one artifact";
414                    }
415                } ).build();
416            }
417
418            // version is LATEST so we have to find the latest one from the result
419            if ( artifacts.size() > 1 && StringUtils.equals( version, LATEST_KEYWORD ) )
420            {
421                TreeMap<String, Artifact> artifactPerVersion = new TreeMap<>( VersionComparator.getInstance() );
422
423                for ( Artifact artifact : artifacts )
424                {
425                    artifactPerVersion.put( artifact.getVersion(), artifact );
426                }
427
428                return Response.temporaryRedirect(
429                    new URI( artifactPerVersion.lastEntry().getValue().getUrl() ) ).build();
430
431            }
432
433            Artifact artifact = artifacts.get( 0 );
434
435            return Response.temporaryRedirect( new URI( artifact.getUrl() ) ).build();
436        }
437        catch ( Exception e )
438        {
439            throw new ArchivaRestServiceException( e.getMessage(), e );
440        }
441    }
442
443
444    //-------------------------------------
445    // internal
446    //-------------------------------------
447    protected List<Artifact> getArtifacts( SearchResults searchResults )
448        throws ArchivaRestServiceException
449    {
450
451        if ( searchResults == null || searchResults.isEmpty() )
452        {
453            return Collections.emptyList();
454        }
455        List<Artifact> artifacts = new ArrayList<>( searchResults.getReturnedHitsCount() );
456        for ( SearchResultHit hit : searchResults.getHits() )
457        {
458            // duplicate Artifact one per available version
459            if ( hit.getVersions().size() > 0 )
460            {
461                for ( String version : hit.getVersions() )
462                {
463
464                    Artifact versionned = getModelMapper().map( hit, Artifact.class );
465
466                    if ( StringUtils.isNotBlank( version ) )
467                    {
468                        versionned.setVersion( version );
469                        versionned.setUrl( getArtifactUrl( versionned ) );
470
471                        artifacts.add( versionned );
472
473                    }
474                }
475            }
476        }
477        return artifacts;
478    }
479
480
481}