This project has retired. For details please refer to its Attic page.
DefaultBrowseService xref
View Javadoc
1   package org.apache.archiva.rest.services;
2   /*
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   */
20  
21  import org.apache.archiva.admin.model.RepositoryAdminException;
22  import org.apache.archiva.admin.model.beans.ManagedRepository;
23  import org.apache.archiva.common.utils.VersionComparator;
24  import org.apache.archiva.common.utils.VersionUtil;
25  import org.apache.archiva.dependency.tree.maven2.DependencyTreeBuilder;
26  import org.apache.archiva.maven2.metadata.MavenMetadataReader;
27  import org.apache.archiva.maven2.model.Artifact;
28  import org.apache.archiva.maven2.model.TreeEntry;
29  import org.apache.archiva.metadata.generic.GenericMetadataFacet;
30  import org.apache.archiva.metadata.model.ArtifactMetadata;
31  import org.apache.archiva.metadata.model.MetadataFacet;
32  import org.apache.archiva.metadata.model.ProjectVersionMetadata;
33  import org.apache.archiva.metadata.model.ProjectVersionReference;
34  import org.apache.archiva.metadata.repository.MetadataRepository;
35  import org.apache.archiva.metadata.repository.MetadataRepositoryException;
36  import org.apache.archiva.metadata.repository.MetadataResolutionException;
37  import org.apache.archiva.metadata.repository.MetadataResolver;
38  import org.apache.archiva.metadata.repository.RepositorySession;
39  import org.apache.archiva.metadata.repository.storage.maven2.ArtifactMetadataVersionComparator;
40  import org.apache.archiva.metadata.repository.storage.maven2.MavenProjectFacet;
41  import org.apache.archiva.model.ArchivaArtifact;
42  import org.apache.archiva.model.ArchivaRepositoryMetadata;
43  import org.apache.archiva.proxy.model.RepositoryProxyConnectors;
44  import org.apache.archiva.redback.components.cache.Cache;
45  import org.apache.archiva.repository.ManagedRepositoryContent;
46  import org.apache.archiva.repository.RepositoryContentFactory;
47  import org.apache.archiva.repository.RepositoryException;
48  import org.apache.archiva.repository.RepositoryNotFoundException;
49  import org.apache.archiva.repository.metadata.MetadataTools;
50  import org.apache.archiva.rest.api.model.ArtifactContent;
51  import org.apache.archiva.rest.api.model.ArtifactContentEntry;
52  import org.apache.archiva.rest.api.model.BrowseResult;
53  import org.apache.archiva.rest.api.model.BrowseResultEntry;
54  import org.apache.archiva.rest.api.model.Entry;
55  import org.apache.archiva.rest.api.model.MetadataAddRequest;
56  import org.apache.archiva.rest.api.model.VersionsList;
57  import org.apache.archiva.rest.api.services.ArchivaRestServiceException;
58  import org.apache.archiva.rest.api.services.BrowseService;
59  import org.apache.archiva.rest.services.utils.ArtifactContentEntryComparator;
60  import org.apache.archiva.security.ArchivaSecurityException;
61  import org.apache.archiva.xml.XMLException;
62  import org.apache.commons.collections.CollectionUtils;
63  import org.apache.commons.io.FileUtils;
64  import org.apache.commons.io.IOUtils;
65  import org.apache.commons.lang.StringUtils;
66  import org.springframework.stereotype.Service;
67  
68  import javax.inject.Inject;
69  import javax.inject.Named;
70  import javax.ws.rs.core.Response;
71  import java.io.File;
72  import java.io.IOException;
73  import java.io.InputStream;
74  import java.util.ArrayList;
75  import java.util.Collection;
76  import java.util.Collections;
77  import java.util.Enumeration;
78  import java.util.HashMap;
79  import java.util.LinkedHashSet;
80  import java.util.List;
81  import java.util.Map;
82  import java.util.Set;
83  import java.util.jar.JarEntry;
84  import java.util.jar.JarFile;
85  import java.util.zip.ZipEntry;
86  
87  /**
88   * @author Olivier Lamy
89   * @since 1.4-M3
90   */
91  @Service( "browseService#rest" )
92  public class DefaultBrowseService
93      extends AbstractRestService
94      implements BrowseService
95  {
96  
97      @Inject
98      private DependencyTreeBuilder dependencyTreeBuilder;
99  
100     @Inject
101     private RepositoryContentFactory repositoryContentFactory;
102 
103     @Inject
104     @Named( value = "repositoryProxyConnectors#default" )
105     private RepositoryProxyConnectors connectors;
106 
107     @Inject
108     @Named( value = "browse#versionMetadata" )
109     private Cache<String, ProjectVersionMetadata> versionMetadataCache;
110 
111     @Override
112     public BrowseResult getRootGroups( String repositoryId )
113         throws ArchivaRestServiceException
114     {
115         List<String> selectedRepos = getSelectedRepos( repositoryId );
116 
117         Set<String> namespaces = new LinkedHashSet<String>();
118 
119         // TODO: this logic should be optional, particularly remembering we want to keep this code simple
120         //       it is located here to avoid the content repository implementation needing to do too much for what
121         //       is essentially presentation code
122         Set<String> namespacesToCollapse = new LinkedHashSet<String>();
123         RepositorySession repositorySession = repositorySessionFactory.createSession();
124         try
125         {
126             MetadataResolver metadataResolver = repositorySession.getResolver();
127 
128             for ( String repoId : selectedRepos )
129             {
130                 namespacesToCollapse.addAll( metadataResolver.resolveRootNamespaces( repositorySession, repoId ) );
131             }
132             for ( String n : namespacesToCollapse )
133             {
134                 // TODO: check performance of this
135                 namespaces.add( collapseNamespaces( repositorySession, metadataResolver, selectedRepos, n ) );
136             }
137         }
138         catch ( MetadataResolutionException e )
139         {
140             throw new ArchivaRestServiceException( e.getMessage(),
141                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
142         }
143         finally
144         {
145             repositorySession.close();
146         }
147 
148         List<BrowseResultEntry> browseGroupResultEntries = new ArrayList<>( namespaces.size() );
149         for ( String namespace : namespaces )
150         {
151             browseGroupResultEntries.add( new BrowseResultEntry( namespace, false ) );
152         }
153 
154         Collections.sort( browseGroupResultEntries );
155         return new BrowseResult( browseGroupResultEntries );
156     }
157 
158     @Override
159     public BrowseResult browseGroupId( String groupId, String repositoryId )
160         throws ArchivaRestServiceException
161     {
162         List<String> selectedRepos = getSelectedRepos( repositoryId );
163 
164         Set<String> projects = new LinkedHashSet<>();
165 
166         RepositorySession repositorySession = repositorySessionFactory.createSession();
167         Set<String> namespaces;
168         try
169         {
170             MetadataResolver metadataResolver = repositorySession.getResolver();
171 
172             Set<String> namespacesToCollapse = new LinkedHashSet<>();
173             for ( String repoId : selectedRepos )
174             {
175                 namespacesToCollapse.addAll( metadataResolver.resolveNamespaces( repositorySession, repoId, groupId ) );
176 
177                 projects.addAll( metadataResolver.resolveProjects( repositorySession, repoId, groupId ) );
178             }
179 
180             // TODO: this logic should be optional, particularly remembering we want to keep this code simple
181             // it is located here to avoid the content repository implementation needing to do too much for what
182             // is essentially presentation code
183             namespaces = new LinkedHashSet<>();
184             for ( String n : namespacesToCollapse )
185             {
186                 // TODO: check performance of this
187                 namespaces.add(
188                     collapseNamespaces( repositorySession, metadataResolver, selectedRepos, groupId + "." + n ) );
189             }
190         }
191         catch ( MetadataResolutionException e )
192         {
193             throw new ArchivaRestServiceException( e.getMessage(),
194                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
195         }
196         finally
197         {
198             repositorySession.close();
199         }
200         List<BrowseResultEntry> browseGroupResultEntries = new ArrayList<>( namespaces.size() + projects.size() );
201         for ( String namespace : namespaces )
202         {
203             browseGroupResultEntries.add( new BrowseResultEntry( namespace, false ).groupId( namespace ) );
204         }
205         for ( String project : projects )
206         {
207             browseGroupResultEntries.add(
208                 new BrowseResultEntry( groupId + '.' + project, true ).groupId( groupId ).artifactId( project ) );
209         }
210         Collections.sort( browseGroupResultEntries );
211         return new BrowseResult( browseGroupResultEntries );
212 
213     }
214 
215     @Override
216     public VersionsList getVersionsList( String groupId, String artifactId, String repositoryId )
217         throws ArchivaRestServiceException
218     {
219         List<String> selectedRepos = getSelectedRepos( repositoryId );
220 
221         try
222         {
223             Collection<String> versions = getVersions( selectedRepos, groupId, artifactId );
224             return new VersionsList( new ArrayList<>( versions ) );
225         }
226         catch ( MetadataResolutionException e )
227         {
228             throw new ArchivaRestServiceException( e.getMessage(),
229                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
230         }
231 
232     }
233 
234     private Collection<String> getVersions( List<String> selectedRepos, String groupId, String artifactId )
235         throws MetadataResolutionException
236 
237     {
238         RepositorySession repositorySession = repositorySessionFactory.createSession();
239         try
240         {
241             MetadataResolver metadataResolver = repositorySession.getResolver();
242 
243             Set<String> versions = new LinkedHashSet<String>();
244 
245             for ( String repoId : selectedRepos )
246             {
247                 Collection<String> projectVersions =
248                     metadataResolver.resolveProjectVersions( repositorySession, repoId, groupId, artifactId );
249                 versions.addAll( projectVersions );
250             }
251 
252             List<String> sortedVersions = new ArrayList<>( versions );
253 
254             Collections.sort( sortedVersions, VersionComparator.getInstance() );
255 
256             return sortedVersions;
257         }
258         finally
259         {
260             repositorySession.close();
261         }
262     }
263 
264     @Override
265     public ProjectVersionMetadata getProjectMetadata( String groupId, String artifactId, String version,
266                                                       String repositoryId )
267         throws ArchivaRestServiceException
268     {
269         List<String> selectedRepos = getSelectedRepos( repositoryId );
270 
271         RepositorySession repositorySession = null;
272         try
273         {
274             repositorySession = repositorySessionFactory.createSession();
275 
276             MetadataResolver metadataResolver = repositorySession.getResolver();
277 
278             ProjectVersionMetadata versionMetadata = null;
279             for ( String repoId : selectedRepos )
280             {
281                 if ( versionMetadata == null || versionMetadata.isIncomplete() )
282                 {
283                     try
284                     {
285                         ProjectVersionMetadata versionMetadataTmp =
286                             metadataResolver.resolveProjectVersion( repositorySession, repoId, groupId, artifactId,
287                                                                     version );
288 
289                         if ( versionMetadata == null && versionMetadataTmp != null )
290                         {
291                             versionMetadata = versionMetadataTmp;
292                         }
293 
294 
295                     }
296                     catch ( MetadataResolutionException e )
297                     {
298                         log.warn( "Skipping invalid metadata while compiling shared model for {}:{} in repo {}: {}",
299                                   groupId, artifactId, repoId, e.getMessage() );
300                     }
301                 }
302             }
303 
304             return versionMetadata;
305         }
306         finally
307         {
308             if ( repositorySession != null )
309             {
310                 repositorySession.close();
311             }
312         }
313 
314     }
315 
316     @Override
317     public ProjectVersionMetadata getProjectVersionMetadata( String groupId, String artifactId, String repositoryId )
318         throws ArchivaRestServiceException
319     {
320 
321         List<String> selectedRepos = getSelectedRepos( repositoryId );
322 
323         RepositorySession repositorySession = null;
324         try
325         {
326 
327             Collection<String> projectVersions = getVersions( selectedRepos, groupId, artifactId );
328 
329             repositorySession = repositorySessionFactory.createSession();
330 
331             MetadataResolver metadataResolver = repositorySession.getResolver();
332 
333             ProjectVersionMetadata sharedModel = new ProjectVersionMetadata();
334 
335             MavenProjectFacet mavenFacet = new MavenProjectFacet();
336             mavenFacet.setGroupId( groupId );
337             mavenFacet.setArtifactId( artifactId );
338             sharedModel.addFacet( mavenFacet );
339 
340             boolean isFirstVersion = true;
341 
342             for ( String version : projectVersions )
343             {
344                 ProjectVersionMetadata versionMetadata = null;
345                 for ( String repoId : selectedRepos )
346                 {
347                     if ( versionMetadata == null || versionMetadata.isIncomplete() )
348                     {
349                         try
350                         {
351                             ProjectVersionMetadata projectVersionMetadataResolved = null;
352                             boolean useCache = !StringUtils.endsWith( version, VersionUtil.SNAPSHOT );
353                             String cacheKey = null;
354                             boolean cacheToUpdate = false;
355                             // FIXME a bit maven centric!!!
356                             // not a snapshot so get it from cache
357                             if ( useCache )
358                             {
359                                 cacheKey = repoId + groupId + artifactId + version;
360                                 projectVersionMetadataResolved = versionMetadataCache.get( cacheKey );
361                             }
362                             if ( useCache && projectVersionMetadataResolved != null )
363                             {
364                                 versionMetadata = projectVersionMetadataResolved;
365                             }
366                             else
367                             {
368                                 projectVersionMetadataResolved =
369                                     metadataResolver.resolveProjectVersion( repositorySession, repoId, groupId,
370                                                                             artifactId, version );
371                                 versionMetadata = projectVersionMetadataResolved;
372                                 cacheToUpdate = true;
373                             }
374 
375                             if ( useCache && cacheToUpdate )
376                             {
377                                 versionMetadataCache.put( cacheKey, projectVersionMetadataResolved );
378                             }
379 
380                         }
381                         catch ( MetadataResolutionException e )
382                         {
383                             log.error( "Skipping invalid metadata while compiling shared model for " + groupId + ":"
384                                            + artifactId + " in repo " + repoId + ": " + e.getMessage() );
385                         }
386                     }
387                 }
388 
389                 if ( versionMetadata == null )
390                 {
391                     continue;
392                 }
393 
394                 if ( isFirstVersion )
395                 {
396                     sharedModel = versionMetadata;
397                     sharedModel.setId( null );
398                 }
399                 else
400                 {
401                     MavenProjectFacet versionMetadataMavenFacet =
402                         (MavenProjectFacet) versionMetadata.getFacet( MavenProjectFacet.FACET_ID );
403                     if ( versionMetadataMavenFacet != null )
404                     {
405                         if ( mavenFacet.getPackaging() != null //
406                             && !StringUtils.equalsIgnoreCase( mavenFacet.getPackaging(),
407                                                               versionMetadataMavenFacet.getPackaging() ) )
408                         {
409                             mavenFacet.setPackaging( null );
410                         }
411                     }
412 
413                     if ( StringUtils.isEmpty( sharedModel.getName() ) //
414                         && !StringUtils.isEmpty( versionMetadata.getName() ) )
415                     {
416                         sharedModel.setName( versionMetadata.getName() );
417                     }
418 
419                     if ( sharedModel.getDescription() != null //
420                         && !StringUtils.equalsIgnoreCase( sharedModel.getDescription(),
421                                                           versionMetadata.getDescription() ) )
422                     {
423                         sharedModel.setDescription( StringUtils.isNotEmpty( versionMetadata.getDescription() )
424                                                         ? versionMetadata.getDescription()
425                                                         : "" );
426                     }
427 
428                     if ( sharedModel.getIssueManagement() != null //
429                         && versionMetadata.getIssueManagement() != null //
430                         && !StringUtils.equalsIgnoreCase( sharedModel.getIssueManagement().getUrl(),
431                                                           versionMetadata.getIssueManagement().getUrl() ) )
432                     {
433                         sharedModel.setIssueManagement( versionMetadata.getIssueManagement() );
434                     }
435 
436                     if ( sharedModel.getCiManagement() != null //
437                         && versionMetadata.getCiManagement() != null //
438                         && !StringUtils.equalsIgnoreCase( sharedModel.getCiManagement().getUrl(),
439                                                           versionMetadata.getCiManagement().getUrl() ) )
440                     {
441                         sharedModel.setCiManagement( versionMetadata.getCiManagement() );
442                     }
443 
444                     if ( sharedModel.getOrganization() != null //
445                         && versionMetadata.getOrganization() != null //
446                         && !StringUtils.equalsIgnoreCase( sharedModel.getOrganization().getName(),
447                                                           versionMetadata.getOrganization().getName() ) )
448                     {
449                         sharedModel.setOrganization( versionMetadata.getOrganization() );
450                     }
451 
452                     if ( sharedModel.getUrl() != null //
453                         && !StringUtils.equalsIgnoreCase( sharedModel.getUrl(), versionMetadata.getUrl() ) )
454                     {
455                         sharedModel.setUrl( versionMetadata.getUrl() );
456                     }
457                 }
458 
459                 isFirstVersion = false;
460             }
461             return sharedModel;
462         }
463         catch ( MetadataResolutionException e )
464         {
465             throw new ArchivaRestServiceException( e.getMessage(),
466                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
467         }
468         finally
469         {
470             if ( repositorySession != null )
471             {
472                 repositorySession.close();
473             }
474         }
475     }
476 
477     @Override
478     public List<TreeEntry> getTreeEntries( String groupId, String artifactId, String version, String repositoryId )
479         throws ArchivaRestServiceException
480     {
481         List<String> selectedRepos = getSelectedRepos( repositoryId );
482 
483         try
484         {
485             return dependencyTreeBuilder.buildDependencyTree( selectedRepos, groupId, artifactId, version );
486         }
487         catch ( Exception e )
488         {
489             log.error( e.getMessage(), e );
490         }
491 
492         return Collections.emptyList();
493     }
494 
495     @Override
496     public List<ManagedRepository> getUserRepositories()
497         throws ArchivaRestServiceException
498     {
499         try
500         {
501             return userRepositories.getAccessibleRepositories( getPrincipal() );
502         }
503         catch ( ArchivaSecurityException e )
504         {
505             throw new ArchivaRestServiceException( "repositories.read.observable.error",
506                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
507         }
508     }
509 
510     @Override
511     public List<ManagedRepository> getUserManagableRepositories() throws ArchivaRestServiceException {
512         try
513         {
514             return userRepositories.getManagableRepositories( getPrincipal() );
515         }
516         catch ( ArchivaSecurityException e )
517         {
518             throw new ArchivaRestServiceException( "repositories.read.managable.error",
519                     Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
520         }
521     }
522 
523     @Override
524     public List<Artifact> getDependees( String groupId, String artifactId, String version, String repositoryId )
525         throws ArchivaRestServiceException
526     {
527         List<ProjectVersionReference> references = new ArrayList<>();
528         // TODO: what if we get duplicates across repositories?
529         RepositorySession repositorySession = repositorySessionFactory.createSession();
530         try
531         {
532             MetadataResolver metadataResolver = repositorySession.getResolver();
533             for ( String repoId : getObservableRepos() )
534             {
535                 // TODO: what about if we want to see this irrespective of version?
536                 references.addAll(
537                     metadataResolver.resolveProjectReferences( repositorySession, repoId, groupId, artifactId,
538                                                                version ) );
539             }
540         }
541         catch ( MetadataResolutionException e )
542         {
543             throw new ArchivaRestServiceException( e.getMessage(),
544                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
545         }
546         finally
547         {
548             repositorySession.close();
549         }
550 
551         List<Artifact> artifacts = new ArrayList<>( references.size() );
552 
553         for ( ProjectVersionReference projectVersionReference : references )
554         {
555             artifacts.add( new Artifact( projectVersionReference.getNamespace(), projectVersionReference.getProjectId(),
556                                          projectVersionReference.getProjectVersion() ) );
557         }
558         return artifacts;
559     }
560 
561     @Override
562     public List<Entry> getMetadatas( String groupId, String artifactId, String version, String repositoryId )
563         throws ArchivaRestServiceException
564     {
565         ProjectVersionMetadata projectVersionMetadata =
566             getProjectMetadata( groupId, artifactId, version, repositoryId );
567         if ( projectVersionMetadata == null )
568         {
569             return Collections.emptyList();
570         }
571         MetadataFacet metadataFacet = projectVersionMetadata.getFacet( GenericMetadataFacet.FACET_ID );
572 
573         if ( metadataFacet == null )
574         {
575             return Collections.emptyList();
576         }
577         Map<String, String> map = metadataFacet.toProperties();
578         List<Entry> entries = new ArrayList<>( map.size() );
579 
580         for ( Map.Entry<String, String> entry : map.entrySet() )
581         {
582             entries.add( new Entry( entry.getKey(), entry.getValue() ) );
583         }
584 
585         return entries;
586     }
587 
588     @Override
589     public Boolean addMetadata( String groupId, String artifactId, String version, String key, String value,
590                                 String repositoryId )
591         throws ArchivaRestServiceException
592     {
593         ProjectVersionMetadata projectVersionMetadata =
594             getProjectMetadata( groupId, artifactId, version, repositoryId );
595 
596         if ( projectVersionMetadata == null )
597         {
598             return Boolean.FALSE;
599         }
600 
601         Map<String, String> properties = new HashMap<>();
602 
603         MetadataFacet metadataFacet = projectVersionMetadata.getFacet( GenericMetadataFacet.FACET_ID );
604 
605         if ( metadataFacet != null && metadataFacet.toProperties() != null )
606         {
607             properties.putAll( metadataFacet.toProperties() );
608         }
609         else
610         {
611             metadataFacet = new GenericMetadataFacet();
612         }
613 
614         properties.put( key, value );
615 
616         metadataFacet.fromProperties( properties );
617 
618         projectVersionMetadata.addFacet( metadataFacet );
619 
620         RepositorySession repositorySession = repositorySessionFactory.createSession();
621 
622         try
623         {
624             MetadataRepository metadataRepository = repositorySession.getRepository();
625 
626             metadataRepository.updateProjectVersion( repositoryId, groupId, artifactId, projectVersionMetadata );
627 
628             repositorySession.save();
629         }
630         catch ( MetadataRepositoryException e )
631         {
632             log.error( e.getMessage(), e );
633             throw new ArchivaRestServiceException( e.getMessage(),
634                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
635         }
636         finally
637         {
638             repositorySession.close();
639         }
640         return Boolean.TRUE;
641     }
642 
643     @Override
644     public Boolean deleteMetadata( String groupId, String artifactId, String version, String key, String repositoryId )
645         throws ArchivaRestServiceException
646     {
647         ProjectVersionMetadata projectVersionMetadata =
648             getProjectMetadata( groupId, artifactId, version, repositoryId );
649 
650         if ( projectVersionMetadata == null )
651         {
652             return Boolean.FALSE;
653         }
654 
655         GenericMetadataFacet metadataFacet =
656             (GenericMetadataFacet) projectVersionMetadata.getFacet( GenericMetadataFacet.FACET_ID );
657 
658         if ( metadataFacet != null && metadataFacet.toProperties() != null )
659         {
660             Map<String, String> properties = metadataFacet.toProperties();
661             properties.remove( key );
662             metadataFacet.setAdditionalProperties( properties );
663         }
664         else
665         {
666             return Boolean.TRUE;
667         }
668 
669         RepositorySession repositorySession = repositorySessionFactory.createSession();
670 
671         try
672         {
673             MetadataRepository metadataRepository = repositorySession.getRepository();
674 
675             metadataRepository.updateProjectVersion( repositoryId, groupId, artifactId, projectVersionMetadata );
676 
677             repositorySession.save();
678         }
679         catch ( MetadataRepositoryException e )
680         {
681             log.error( e.getMessage(), e );
682             throw new ArchivaRestServiceException( e.getMessage(),
683                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
684         }
685         finally
686         {
687             repositorySession.close();
688         }
689         return Boolean.TRUE;
690     }
691 
692     @Override
693     public List<ArtifactContentEntry> getArtifactContentEntries( String groupId, String artifactId, String version,
694                                                                  String classifier, String type, String path,
695                                                                  String repositoryId )
696         throws ArchivaRestServiceException
697     {
698         List<String> selectedRepos = getSelectedRepos( repositoryId );
699         try
700         {
701             for ( String repoId : selectedRepos )
702             {
703 
704                 ManagedRepositoryContent managedRepositoryContent =
705                     repositoryContentFactory.getManagedRepositoryContent( repoId );
706                 ArchivaArtifact archivaArtifact = new ArchivaArtifact( groupId, artifactId, version, classifier,
707                                                                        StringUtils.isEmpty( type ) ? "jar" : type,
708                                                                        repoId );
709                 File file = managedRepositoryContent.toFile( archivaArtifact );
710                 if ( file.exists() )
711                 {
712                     return readFileEntries( file, path, repoId );
713                 }
714             }
715         }
716         catch ( IOException e )
717         {
718             log.error( e.getMessage(), e );
719             throw new ArchivaRestServiceException( e.getMessage(),
720                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
721         }
722         catch ( RepositoryNotFoundException e )
723         {
724             log.error( e.getMessage(), e );
725             throw new ArchivaRestServiceException( e.getMessage(),
726                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
727         }
728         catch ( RepositoryException e )
729         {
730             log.error( e.getMessage(), e );
731             throw new ArchivaRestServiceException( e.getMessage(),
732                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
733         }
734         return Collections.emptyList();
735     }
736 
737     @Override
738     public List<Artifact> getArtifactDownloadInfos( String groupId, String artifactId, String version,
739                                                     String repositoryId )
740         throws ArchivaRestServiceException
741     {
742         List<String> selectedRepos = getSelectedRepos( repositoryId );
743 
744         List<Artifact> artifactDownloadInfos = new ArrayList<>();
745 
746         try (RepositorySession session = repositorySessionFactory.createSession())
747         {
748             MetadataResolver metadataResolver = session.getResolver();
749             for ( String repoId : selectedRepos )
750             {
751                 List<ArtifactMetadata> artifacts = new ArrayList<>(
752                     metadataResolver.resolveArtifacts( session, repoId, groupId, artifactId, version ) );
753                 Collections.sort( artifacts, ArtifactMetadataVersionComparator.INSTANCE );
754                 if ( artifacts != null && !artifacts.isEmpty() )
755                 {
756                     return buildArtifacts( artifacts, repoId );
757                 }
758             }
759         }
760         catch ( MetadataResolutionException e )
761         {
762             log.error( e.getMessage(), e );
763             throw new ArchivaRestServiceException( e.getMessage(),
764                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
765         }
766 
767         return artifactDownloadInfos;
768     }
769 
770     @Override
771     public ArtifactContent getArtifactContentText( String groupId, String artifactId, String version, String classifier,
772                                                    String type, String path, String repositoryId )
773         throws ArchivaRestServiceException
774     {
775         List<String> selectedRepos = getSelectedRepos( repositoryId );
776         try
777         {
778             for ( String repoId : selectedRepos )
779             {
780 
781                 ManagedRepositoryContent managedRepositoryContent =
782                     repositoryContentFactory.getManagedRepositoryContent( repoId );
783                 ArchivaArtifact archivaArtifact = new ArchivaArtifact( groupId, artifactId, version, classifier,
784                                                                        StringUtils.isEmpty( type ) ? "jar" : type,
785                                                                        repoId );
786                 File file = managedRepositoryContent.toFile( archivaArtifact );
787                 if ( !file.exists() )
788                 {
789                     log.debug( "file: {} not exists for repository: {} try next repository", file, repoId );
790                     continue;
791                 }
792                 if ( StringUtils.isNotBlank( path ) )
793                 {
794                     // zip entry of the path -> path must a real file entry of the archive
795                     JarFile jarFile = new JarFile( file );
796                     ZipEntry zipEntry = jarFile.getEntry( path );
797                     try (InputStream inputStream = jarFile.getInputStream( zipEntry ))
798                     {
799                         return new ArtifactContent( IOUtils.toString( inputStream ), repoId );
800                     }
801                     finally
802                     {
803                         closeQuietly( jarFile );
804                     }
805                 }
806                 return new ArtifactContent( FileUtils.readFileToString( file ), repoId );
807             }
808         }
809         catch ( IOException e )
810         {
811             log.error( e.getMessage(), e );
812             throw new ArchivaRestServiceException( e.getMessage(),
813                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
814         }
815         catch ( RepositoryNotFoundException e )
816         {
817             log.error( e.getMessage(), e );
818             throw new ArchivaRestServiceException( e.getMessage(),
819                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
820         }
821         catch ( RepositoryException e )
822         {
823             log.error( e.getMessage(), e );
824             throw new ArchivaRestServiceException( e.getMessage(),
825                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
826         }
827         log.debug( "artifact: {}:{}:{}:{}:{} not found", groupId, artifactId, version, classifier, type );
828         // 404 ?
829         return new ArtifactContent();
830     }
831 
832     @Override
833     public Boolean artifactAvailable( String groupId, String artifactId, String version, String classifier,
834                                       String repositoryId )
835         throws ArchivaRestServiceException
836     {
837         List<String> selectedRepos = getSelectedRepos( repositoryId );
838 
839         boolean snapshot = VersionUtil.isSnapshot( version );
840 
841         try
842         {
843             for ( String repoId : selectedRepos )
844             {
845 
846                 ManagedRepository managedRepository = managedRepositoryAdmin.getManagedRepository( repoId );
847 
848                 if ( ( snapshot && !managedRepository.isSnapshots() ) || ( !snapshot
849                     && managedRepository.isSnapshots() ) )
850                 {
851                     continue;
852                 }
853                 ManagedRepositoryContent managedRepositoryContent =
854                     repositoryContentFactory.getManagedRepositoryContent( repoId );
855                 // FIXME default to jar which can be wrong for war zip etc....
856                 ArchivaArtifact archivaArtifact = new ArchivaArtifact( groupId, artifactId, version,
857                                                                        StringUtils.isEmpty( classifier )
858                                                                            ? ""
859                                                                            : classifier, "jar", repoId );
860                 File file = managedRepositoryContent.toFile( archivaArtifact );
861 
862                 if ( file != null && file.exists() )
863                 {
864                     return true;
865                 }
866 
867                 // in case of SNAPSHOT we can have timestamped version locally !
868                 if ( StringUtils.endsWith( version, VersionUtil.SNAPSHOT ) )
869                 {
870                     File metadataFile = new File( file.getParent(), MetadataTools.MAVEN_METADATA );
871                     if ( metadataFile.exists() )
872                     {
873                         try
874                         {
875                             ArchivaRepositoryMetadata archivaRepositoryMetadata =
876                                 MavenMetadataReader.read( metadataFile );
877                             int buildNumber = archivaRepositoryMetadata.getSnapshotVersion().getBuildNumber();
878                             String timeStamp = archivaRepositoryMetadata.getSnapshotVersion().getTimestamp();
879                             // rebuild file name with timestamped version and build number
880                             String timeStampFileName = new StringBuilder( artifactId ).append( '-' ) //
881                                 .append( StringUtils.remove( version, "-" + VersionUtil.SNAPSHOT ) ) //
882                                 .append( '-' ).append( timeStamp ) //
883                                 .append( '-' ).append( Integer.toString( buildNumber ) ) //
884                                 .append( ( StringUtils.isEmpty( classifier ) ? "" : "-" + classifier ) ) //
885                                 .append( ".jar" ).toString();
886 
887                             File timeStampFile = new File( file.getParent(), timeStampFileName );
888                             log.debug( "try to find timestamped snapshot version file: {}", timeStampFile.getPath() );
889                             if ( timeStampFile.exists() )
890                             {
891                                 return true;
892                             }
893                         }
894                         catch ( XMLException e )
895                         {
896                             log.warn( "skip fail to find timestamped snapshot file: {}", e.getMessage() );
897                         }
898                     }
899                 }
900 
901                 String path = managedRepositoryContent.toPath( archivaArtifact );
902 
903                 file = connectors.fetchFromProxies( managedRepositoryContent, path );
904 
905                 if ( file != null && file.exists() )
906                 {
907                     // download pom now
908                     String pomPath = StringUtils.substringBeforeLast( path, ".jar" ) + ".pom";
909                     connectors.fetchFromProxies( managedRepositoryContent, pomPath );
910                     return true;
911                 }
912             }
913         }
914         catch ( RepositoryAdminException e )
915         {
916             log.error( e.getMessage(), e );
917             throw new ArchivaRestServiceException( e.getMessage(),
918                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
919         }
920         catch ( RepositoryException e )
921         {
922             log.error( e.getMessage(), e );
923             throw new ArchivaRestServiceException( e.getMessage(),
924                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
925         }
926 
927         return false;
928     }
929 
930     @Override
931     public Boolean artifactAvailable( String groupId, String artifactId, String version, String repositoryId )
932         throws ArchivaRestServiceException
933     {
934         return artifactAvailable( groupId, artifactId, version, null, repositoryId );
935     }
936 
937     @Override
938     public List<Artifact> getArtifacts( String repositoryId )
939         throws ArchivaRestServiceException
940     {
941         RepositorySession repositorySession = repositorySessionFactory.createSession();
942         try
943         {
944             List<ArtifactMetadata> artifactMetadatas = repositorySession.getRepository().getArtifacts( repositoryId );
945             return buildArtifacts( artifactMetadatas, repositoryId );
946         }
947         catch ( MetadataRepositoryException e )
948         {
949             throw new ArchivaRestServiceException( e.getMessage(), e );
950         }
951         finally
952         {
953             repositorySession.close();
954         }
955     }
956 
957     @Override
958     public List<Artifact> getArtifactsByProjectVersionMetadata( String key, String value, String repositoryId )
959         throws ArchivaRestServiceException
960     {
961         RepositorySession repositorySession = repositorySessionFactory.createSession();
962         try
963         {
964             List<ArtifactMetadata> artifactMetadatas = repositorySession.getRepository().getArtifactsByProjectVersionMetadata( key, value, repositoryId );
965             return buildArtifacts( artifactMetadatas, repositoryId );
966         }
967         catch ( MetadataRepositoryException e )
968         {
969             throw new ArchivaRestServiceException( e.getMessage(), e );
970         }
971         finally
972         {
973             repositorySession.close();
974         }
975     }
976 
977     @Override
978     public List<Artifact> getArtifactsByMetadata( String key, String value, String repositoryId )
979         throws ArchivaRestServiceException
980     {
981         RepositorySession repositorySession = repositorySessionFactory.createSession();
982         try
983         {
984             List<ArtifactMetadata> artifactMetadatas = repositorySession.getRepository().getArtifactsByMetadata( key, value, repositoryId );
985             return buildArtifacts( artifactMetadatas, repositoryId );
986         }
987         catch ( MetadataRepositoryException e )
988         {
989             throw new ArchivaRestServiceException( e.getMessage(), e );
990         }
991         finally
992         {
993             repositorySession.close();
994         }
995     }
996 
997     @Override
998     public List<Artifact> getArtifactsByProperty( String key, String value, String repositoryId )
999         throws ArchivaRestServiceException
1000     {
1001         RepositorySession repositorySession = repositorySessionFactory.createSession();
1002         try
1003         {
1004             List<ArtifactMetadata> artifactMetadatas = repositorySession.getRepository().getArtifactsByProperty( key, value, repositoryId );
1005             return buildArtifacts( artifactMetadatas, repositoryId );
1006         }
1007         catch ( MetadataRepositoryException e )
1008         {
1009             throw new ArchivaRestServiceException( e.getMessage(), e );
1010         }
1011         finally
1012         {
1013             repositorySession.close();
1014         }
1015     }
1016 
1017     @Override
1018     public Boolean importMetadata( MetadataAddRequest metadataAddRequest, String repositoryId )
1019         throws ArchivaRestServiceException
1020     {
1021         boolean result = true;
1022         for ( Map.Entry<String, String> metadata : metadataAddRequest.getMetadatas().entrySet() )
1023         {
1024             result = addMetadata( metadataAddRequest.getGroupId(), metadataAddRequest.getArtifactId(),
1025                                   metadataAddRequest.getVersion(), metadata.getKey(), metadata.getValue(),
1026                                   repositoryId );
1027             if ( !result )
1028             {
1029                 break;
1030             }
1031         }
1032         return result;
1033     }
1034 
1035     @Override
1036     public List<Artifact> searchArtifacts( String text, String repositoryId, Boolean exact )
1037         throws ArchivaRestServiceException
1038     {
1039         RepositorySession repositorySession = repositorySessionFactory.createSession();
1040         try
1041         {
1042             List<ArtifactMetadata> artifactMetadatas =
1043                 repositorySession.getRepository().searchArtifacts( text, repositoryId, exact == null ? false : exact );
1044             return buildArtifacts( artifactMetadatas, repositoryId );
1045         }
1046         catch ( MetadataRepositoryException e )
1047         {
1048             throw new ArchivaRestServiceException( e.getMessage(), e );
1049         }
1050         finally
1051         {
1052             repositorySession.close();
1053         }
1054     }
1055 
1056     @Override
1057     public List<Artifact> searchArtifacts( String key, String text, String repositoryId, Boolean exact )
1058         throws ArchivaRestServiceException
1059     {
1060         RepositorySession repositorySession = repositorySessionFactory.createSession();
1061         try
1062         {
1063             List<ArtifactMetadata> artifactMetadatas =
1064                 repositorySession.getRepository().searchArtifacts( key, text, repositoryId, exact == null ? false : exact );
1065             return buildArtifacts( artifactMetadatas, repositoryId );
1066         }
1067         catch ( MetadataRepositoryException e )
1068         {
1069             throw new ArchivaRestServiceException( e.getMessage(), e );
1070         }
1071         finally
1072         {
1073             repositorySession.close();
1074         }
1075     }
1076 
1077     //---------------------------
1078     // internals
1079     //---------------------------
1080 
1081     private void closeQuietly( JarFile jarFile )
1082     {
1083         if ( jarFile != null )
1084         {
1085             try
1086             {
1087                 jarFile.close();
1088             }
1089             catch ( IOException e )
1090             {
1091                 log.warn( "ignore error closing jarFile {}", jarFile.getName() );
1092             }
1093         }
1094     }
1095 
1096     protected List<ArtifactContentEntry> readFileEntries(final File file, final String filterPath, final String repoId )
1097         throws IOException
1098     {
1099         String cleanedfilterPath = filterPath==null ? "" : (StringUtils.startsWith(filterPath, "/") ?
1100                 StringUtils.substringAfter(filterPath, "/") : filterPath);
1101         Map<String, ArtifactContentEntry> artifactContentEntryMap = new HashMap<>();
1102         int filterDepth = StringUtils.countMatches( cleanedfilterPath, "/" );
1103         if (!StringUtils.endsWith(cleanedfilterPath,"/") && !StringUtils.isEmpty(cleanedfilterPath)) {
1104             filterDepth++;
1105         }
1106         JarFile jarFile = new JarFile( file );
1107         try
1108         {
1109             Enumeration<JarEntry> jarEntryEnumeration = jarFile.entries();
1110             while ( jarEntryEnumeration.hasMoreElements() )
1111             {
1112                 JarEntry currentEntry = jarEntryEnumeration.nextElement();
1113                 String cleanedEntryName = StringUtils.endsWith( currentEntry.getName(), "/" ) ? //
1114                     StringUtils.substringBeforeLast( currentEntry.getName(), "/" ) : currentEntry.getName();
1115                 String entryRootPath = getRootPath( cleanedEntryName );
1116                 int depth = StringUtils.countMatches( cleanedEntryName, "/" );
1117                 if ( StringUtils.isEmpty( cleanedfilterPath ) //
1118                     && !artifactContentEntryMap.containsKey( entryRootPath ) //
1119                     && depth == filterDepth )
1120                 {
1121 
1122                     artifactContentEntryMap.put( entryRootPath,
1123                                                  new ArtifactContentEntry( entryRootPath, !currentEntry.isDirectory(),
1124                                                                            depth, repoId ) );
1125                 }
1126                 else
1127                 {
1128                     if ( StringUtils.startsWith( cleanedEntryName, cleanedfilterPath ) //
1129                         && ( depth == filterDepth || ( !currentEntry.isDirectory() && depth == filterDepth ) ) )
1130                     {
1131                         artifactContentEntryMap.put( cleanedEntryName, new ArtifactContentEntry( cleanedEntryName,
1132                                                                                                  !currentEntry.isDirectory(),
1133                                                                                                  depth, repoId ) );
1134                     }
1135                 }
1136             }
1137 
1138             if ( StringUtils.isNotEmpty( cleanedfilterPath ) )
1139             {
1140                 Map<String, ArtifactContentEntry> filteredArtifactContentEntryMap = new HashMap<>();
1141 
1142                 for ( Map.Entry<String, ArtifactContentEntry> entry : artifactContentEntryMap.entrySet() )
1143                 {
1144                     filteredArtifactContentEntryMap.put( entry.getKey(), entry.getValue() );
1145                 }
1146 
1147                 List<ArtifactContentEntry> sorted = getSmallerDepthEntries( filteredArtifactContentEntryMap );
1148                 if ( sorted == null )
1149                 {
1150                     return Collections.emptyList();
1151                 }
1152                 Collections.sort( sorted, ArtifactContentEntryComparator.INSTANCE );
1153                 return sorted;
1154             }
1155         }
1156         finally
1157         {
1158             if ( jarFile != null )
1159             {
1160                 jarFile.close();
1161             }
1162         }
1163         List<ArtifactContentEntry> sorted = new ArrayList<>( artifactContentEntryMap.values() );
1164         Collections.sort( sorted, ArtifactContentEntryComparator.INSTANCE );
1165         return sorted;
1166     }
1167 
1168     private List<ArtifactContentEntry> getSmallerDepthEntries( Map<String, ArtifactContentEntry> entries )
1169     {
1170         int smallestDepth = Integer.MAX_VALUE;
1171         Map<Integer, List<ArtifactContentEntry>> perDepthList = new HashMap<>();
1172         for ( Map.Entry<String, ArtifactContentEntry> entry : entries.entrySet() )
1173         {
1174 
1175             ArtifactContentEntry current = entry.getValue();
1176 
1177             if ( current.getDepth() < smallestDepth )
1178             {
1179                 smallestDepth = current.getDepth();
1180             }
1181 
1182             List<ArtifactContentEntry> currentList = perDepthList.get( current.getDepth() );
1183 
1184             if ( currentList == null )
1185             {
1186                 currentList = new ArrayList<>();
1187                 currentList.add( current );
1188                 perDepthList.put( current.getDepth(), currentList );
1189             }
1190             else
1191             {
1192                 currentList.add( current );
1193             }
1194 
1195         }
1196 
1197         return perDepthList.get( smallestDepth );
1198     }
1199 
1200     /**
1201      * @param path
1202      * @return org/apache -&gt; org , org -&gt; org
1203      */
1204     private String getRootPath( String path )
1205     {
1206         if ( StringUtils.contains( path, '/' ) )
1207         {
1208             return StringUtils.substringBefore( path, "/" );
1209         }
1210         return path;
1211     }
1212 
1213     private List<String> getSelectedRepos( String repositoryId )
1214         throws ArchivaRestServiceException
1215     {
1216 
1217         List<String> selectedRepos = getObservableRepos();
1218 
1219         if ( CollectionUtils.isEmpty( selectedRepos ) )
1220         {
1221             return Collections.emptyList();
1222         }
1223 
1224         if ( StringUtils.isNotEmpty( repositoryId ) )
1225         {
1226             // check user has karma on the repository
1227             if ( !selectedRepos.contains( repositoryId ) )
1228             {
1229                 throw new ArchivaRestServiceException( "browse.root.groups.repositoy.denied",
1230                                                        Response.Status.FORBIDDEN.getStatusCode(), null );
1231             }
1232             selectedRepos = Collections.singletonList( repositoryId );
1233         }
1234         return selectedRepos;
1235     }
1236 
1237 
1238     private String collapseNamespaces( RepositorySession repositorySession, MetadataResolver metadataResolver,
1239                                        Collection<String> repoIds, String n )
1240         throws MetadataResolutionException
1241     {
1242         Set<String> subNamespaces = new LinkedHashSet<String>();
1243         for ( String repoId : repoIds )
1244         {
1245             subNamespaces.addAll( metadataResolver.resolveNamespaces( repositorySession, repoId, n ) );
1246         }
1247         if ( subNamespaces.size() != 1 )
1248         {
1249             log.debug( "{} is not collapsible as it has sub-namespaces: {}", n, subNamespaces );
1250             return n;
1251         }
1252         else
1253         {
1254             for ( String repoId : repoIds )
1255             {
1256                 Collection<String> projects = metadataResolver.resolveProjects( repositorySession, repoId, n );
1257                 if ( projects != null && !projects.isEmpty() )
1258                 {
1259                     log.debug( "{} is not collapsible as it has projects", n );
1260                     return n;
1261                 }
1262             }
1263             return collapseNamespaces( repositorySession, metadataResolver, repoIds,
1264                                        n + "." + subNamespaces.iterator().next() );
1265         }
1266     }
1267 
1268     public Cache getVersionMetadataCache()
1269     {
1270         return versionMetadataCache;
1271     }
1272 
1273     public void setVersionMetadataCache( Cache versionMetadataCache )
1274     {
1275         this.versionMetadataCache = versionMetadataCache;
1276     }
1277 }