]> source.dussan.org Git - archiva.git/blob
348a2dd51f96d7dc25f7c463cae08e442d9efea6
[archiva.git] /
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     public BrowseResult getRootGroups( String repositoryId )
112         throws ArchivaRestServiceException
113     {
114         List<String> selectedRepos = getSelectedRepos( repositoryId );
115
116         Set<String> namespaces = new LinkedHashSet<String>();
117
118         // TODO: this logic should be optional, particularly remembering we want to keep this code simple
119         //       it is located here to avoid the content repository implementation needing to do too much for what
120         //       is essentially presentation code
121         Set<String> namespacesToCollapse;
122         RepositorySession repositorySession = repositorySessionFactory.createSession();
123         try
124         {
125             MetadataResolver metadataResolver = repositorySession.getResolver();
126             namespacesToCollapse = new LinkedHashSet<String>();
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<BrowseResultEntry>( 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     public BrowseResult browseGroupId( String groupId, String repositoryId )
159         throws ArchivaRestServiceException
160     {
161         List<String> selectedRepos = getSelectedRepos( repositoryId );
162
163         Set<String> projects = new LinkedHashSet<String>();
164
165         RepositorySession repositorySession = repositorySessionFactory.createSession();
166         Set<String> namespaces;
167         try
168         {
169             MetadataResolver metadataResolver = repositorySession.getResolver();
170
171             Set<String> namespacesToCollapse = new LinkedHashSet<String>();
172             for ( String repoId : selectedRepos )
173             {
174                 namespacesToCollapse.addAll( metadataResolver.resolveNamespaces( repositorySession, repoId, groupId ) );
175
176                 projects.addAll( metadataResolver.resolveProjects( repositorySession, repoId, groupId ) );
177             }
178
179             // TODO: this logic should be optional, particularly remembering we want to keep this code simple
180             // it is located here to avoid the content repository implementation needing to do too much for what
181             // is essentially presentation code
182             namespaces = new LinkedHashSet<String>();
183             for ( String n : namespacesToCollapse )
184             {
185                 // TODO: check performance of this
186                 namespaces.add(
187                     collapseNamespaces( repositorySession, metadataResolver, selectedRepos, groupId + "." + n ) );
188             }
189         }
190         catch ( MetadataResolutionException e )
191         {
192             throw new ArchivaRestServiceException( e.getMessage(),
193                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
194         }
195         finally
196         {
197             repositorySession.close();
198         }
199         List<BrowseResultEntry> browseGroupResultEntries =
200             new ArrayList<BrowseResultEntry>( 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     public VersionsList getVersionsList( String groupId, String artifactId, String repositoryId )
216         throws ArchivaRestServiceException
217     {
218         List<String> selectedRepos = getSelectedRepos( repositoryId );
219
220         try
221         {
222             Collection<String> versions = getVersions( selectedRepos, groupId, artifactId );
223             return new VersionsList( new ArrayList<String>( versions ) );
224         }
225         catch ( MetadataResolutionException e )
226         {
227             throw new ArchivaRestServiceException( e.getMessage(),
228                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
229         }
230
231     }
232
233     private Collection<String> getVersions( List<String> selectedRepos, String groupId, String artifactId )
234         throws MetadataResolutionException
235
236     {
237         RepositorySession repositorySession = repositorySessionFactory.createSession();
238         try
239         {
240             MetadataResolver metadataResolver = repositorySession.getResolver();
241
242             Set<String> versions = new LinkedHashSet<String>();
243
244             for ( String repoId : selectedRepos )
245             {
246                 Collection<String> projectVersions =
247                     metadataResolver.resolveProjectVersions( repositorySession, repoId, groupId, artifactId );
248                 versions.addAll( projectVersions );
249             }
250
251             List<String> sortedVersions = new ArrayList<String>( versions );
252
253             Collections.sort( sortedVersions, VersionComparator.getInstance() );
254
255             return sortedVersions;
256         }
257         finally
258         {
259             repositorySession.close();
260         }
261     }
262
263     public ProjectVersionMetadata getProjectMetadata( String groupId, String artifactId, String version,
264                                                       String repositoryId )
265         throws ArchivaRestServiceException
266     {
267         List<String> selectedRepos = getSelectedRepos( repositoryId );
268
269         RepositorySession repositorySession = null;
270         try
271         {
272             repositorySession = repositorySessionFactory.createSession();
273
274             MetadataResolver metadataResolver = repositorySession.getResolver();
275
276             ProjectVersionMetadata versionMetadata = null;
277             for ( String repoId : selectedRepos )
278             {
279                 if ( versionMetadata == null || versionMetadata.isIncomplete() )
280                 {
281                     try
282                     {
283                         versionMetadata =
284                             metadataResolver.resolveProjectVersion( repositorySession, repoId, groupId, artifactId,
285                                                                     version );
286                     }
287                     catch ( MetadataResolutionException e )
288                     {
289                         log.warn( "Skipping invalid metadata while compiling shared model for {}:{} in repo {}: {}",
290                                   groupId, artifactId, repoId, e.getMessage() );
291                     }
292                 }
293             }
294
295             return versionMetadata;
296         }
297         finally
298         {
299             if ( repositorySession != null )
300             {
301                 repositorySession.close();
302             }
303         }
304
305     }
306
307     public ProjectVersionMetadata getProjectVersionMetadata( String groupId, String artifactId, String repositoryId )
308         throws ArchivaRestServiceException
309     {
310
311         List<String> selectedRepos = getSelectedRepos( repositoryId );
312
313         RepositorySession repositorySession = null;
314         try
315         {
316
317             Collection<String> projectVersions = getVersions( selectedRepos, groupId, artifactId );
318
319             repositorySession = repositorySessionFactory.createSession();
320
321             MetadataResolver metadataResolver = repositorySession.getResolver();
322
323             ProjectVersionMetadata sharedModel = new ProjectVersionMetadata();
324
325             MavenProjectFacet mavenFacet = new MavenProjectFacet();
326             mavenFacet.setGroupId( groupId );
327             mavenFacet.setArtifactId( artifactId );
328             sharedModel.addFacet( mavenFacet );
329
330             boolean isFirstVersion = true;
331
332             for ( String version : projectVersions )
333             {
334                 ProjectVersionMetadata versionMetadata = null;
335                 for ( String repoId : selectedRepos )
336                 {
337                     if ( versionMetadata == null || versionMetadata.isIncomplete() )
338                     {
339                         try
340                         {
341                             ProjectVersionMetadata projectVersionMetadataResolved = null;
342                             boolean useCache = !StringUtils.endsWith( version, VersionUtil.SNAPSHOT );
343                             String cacheKey = null;
344                             boolean cacheToUpdate = false;
345                             // FIXME a bit maven centric!!!
346                             // not a snapshot so get it from cache
347                             if ( useCache )
348                             {
349                                 cacheKey = repoId + groupId + artifactId + version;
350                                 projectVersionMetadataResolved = versionMetadataCache.get( cacheKey );
351                             }
352                             if ( useCache && projectVersionMetadataResolved != null )
353                             {
354                                 versionMetadata = projectVersionMetadataResolved;
355                             }
356                             else
357                             {
358                                 projectVersionMetadataResolved =
359                                     metadataResolver.resolveProjectVersion( repositorySession, repoId, groupId,
360                                                                             artifactId, version );
361                                 versionMetadata = projectVersionMetadataResolved;
362                                 cacheToUpdate = true;
363                             }
364
365                             if ( useCache && cacheToUpdate )
366                             {
367                                 versionMetadataCache.put( cacheKey, projectVersionMetadataResolved );
368                             }
369
370                         }
371                         catch ( MetadataResolutionException e )
372                         {
373                             log.error( "Skipping invalid metadata while compiling shared model for " + groupId + ":"
374                                            + artifactId + " in repo " + repoId + ": " + e.getMessage() );
375                         }
376                     }
377                 }
378
379                 if ( versionMetadata == null )
380                 {
381                     continue;
382                 }
383
384                 if ( isFirstVersion )
385                 {
386                     sharedModel = versionMetadata;
387                     sharedModel.setId( null );
388                 }
389                 else
390                 {
391                     MavenProjectFacet versionMetadataMavenFacet =
392                         (MavenProjectFacet) versionMetadata.getFacet( MavenProjectFacet.FACET_ID );
393                     if ( versionMetadataMavenFacet != null )
394                     {
395                         if ( mavenFacet.getPackaging() != null //
396                             && !StringUtils.equalsIgnoreCase( mavenFacet.getPackaging(),
397                                                               versionMetadataMavenFacet.getPackaging() ) )
398                         {
399                             mavenFacet.setPackaging( null );
400                         }
401                     }
402
403                     if ( StringUtils.isEmpty( sharedModel.getName() ) //
404                         && !StringUtils.isEmpty( versionMetadata.getName() ) )
405                     {
406                         sharedModel.setName( versionMetadata.getName() );
407                     }
408
409                     if ( sharedModel.getDescription() != null //
410                         && !StringUtils.equalsIgnoreCase( sharedModel.getDescription(),
411                                                           versionMetadata.getDescription() ) )
412                     {
413                         sharedModel.setDescription( StringUtils.isNotEmpty( versionMetadata.getDescription() )
414                                                         ? versionMetadata.getDescription()
415                                                         : "" );
416                     }
417
418                     if ( sharedModel.getIssueManagement() != null //
419                         && versionMetadata.getIssueManagement() != null //
420                         && !StringUtils.equalsIgnoreCase( sharedModel.getIssueManagement().getUrl(),
421                                                           versionMetadata.getIssueManagement().getUrl() ) )
422                     {
423                         sharedModel.setIssueManagement( versionMetadata.getIssueManagement() );
424                     }
425
426                     if ( sharedModel.getCiManagement() != null //
427                         && versionMetadata.getCiManagement() != null //
428                         && !StringUtils.equalsIgnoreCase( sharedModel.getCiManagement().getUrl(),
429                                                           versionMetadata.getCiManagement().getUrl() ) )
430                     {
431                         sharedModel.setCiManagement( versionMetadata.getCiManagement() );
432                     }
433
434                     if ( sharedModel.getOrganization() != null //
435                         && versionMetadata.getOrganization() != null //
436                         && !StringUtils.equalsIgnoreCase( sharedModel.getOrganization().getName(),
437                                                           versionMetadata.getOrganization().getName() ) )
438                     {
439                         sharedModel.setOrganization( versionMetadata.getOrganization() );
440                     }
441
442                     if ( sharedModel.getUrl() != null //
443                         && !StringUtils.equalsIgnoreCase( sharedModel.getUrl(), versionMetadata.getUrl() ) )
444                     {
445                         sharedModel.setUrl( versionMetadata.getUrl() );
446                     }
447                 }
448
449                 isFirstVersion = false;
450             }
451             return sharedModel;
452         }
453         catch ( MetadataResolutionException e )
454         {
455             throw new ArchivaRestServiceException( e.getMessage(),
456                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
457         }
458         finally
459         {
460             if ( repositorySession != null )
461             {
462                 repositorySession.close();
463             }
464         }
465     }
466
467     public List<TreeEntry> getTreeEntries( String groupId, String artifactId, String version, String repositoryId )
468         throws ArchivaRestServiceException
469     {
470         List<String> selectedRepos = getSelectedRepos( repositoryId );
471
472         try
473         {
474
475             return dependencyTreeBuilder.buildDependencyTree( selectedRepos, groupId, artifactId, version );
476
477         }
478         catch ( Exception e )
479         {
480             log.error( e.getMessage(), e );
481         }
482
483         return Collections.emptyList();
484     }
485
486     public List<ManagedRepository> getUserRepositories()
487         throws ArchivaRestServiceException
488     {
489         try
490         {
491             return userRepositories.getAccessibleRepositories( getPrincipal() );
492         }
493         catch ( ArchivaSecurityException e )
494         {
495             throw new ArchivaRestServiceException( "repositories.read.observable.error",
496                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
497         }
498     }
499
500     public List<Artifact> getDependees( String groupId, String artifactId, String version, String repositoryId )
501         throws ArchivaRestServiceException
502     {
503         List<ProjectVersionReference> references = new ArrayList<ProjectVersionReference>();
504         // TODO: what if we get duplicates across repositories?
505         RepositorySession repositorySession = repositorySessionFactory.createSession();
506         try
507         {
508             MetadataResolver metadataResolver = repositorySession.getResolver();
509             for ( String repoId : getObservableRepos() )
510             {
511                 // TODO: what about if we want to see this irrespective of version?
512                 references.addAll(
513                     metadataResolver.resolveProjectReferences( repositorySession, repoId, groupId, artifactId,
514                                                                version ) );
515             }
516         }
517         catch ( MetadataResolutionException e )
518         {
519             throw new ArchivaRestServiceException( e.getMessage(),
520                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
521         }
522         finally
523         {
524             repositorySession.close();
525         }
526
527         List<Artifact> artifacts = new ArrayList<Artifact>( references.size() );
528
529         for ( ProjectVersionReference projectVersionReference : references )
530         {
531             artifacts.add( new Artifact( projectVersionReference.getNamespace(), projectVersionReference.getProjectId(),
532                                          projectVersionReference.getProjectVersion() ) );
533         }
534         return artifacts;
535     }
536
537     public List<Entry> getMetadatas( String groupId, String artifactId, String version, String repositoryId )
538         throws ArchivaRestServiceException
539     {
540         ProjectVersionMetadata projectVersionMetadata =
541             getProjectMetadata( groupId, artifactId, version, repositoryId );
542         if ( projectVersionMetadata == null )
543         {
544             return Collections.emptyList();
545         }
546         MetadataFacet metadataFacet = projectVersionMetadata.getFacet( GenericMetadataFacet.FACET_ID );
547
548         if ( metadataFacet == null )
549         {
550             return Collections.emptyList();
551         }
552         Map<String, String> map = metadataFacet.toProperties();
553         List<Entry> entries = new ArrayList<Entry>( map.size() );
554
555         for ( Map.Entry<String, String> entry : map.entrySet() )
556         {
557             entries.add( new Entry( entry.getKey(), entry.getValue() ) );
558         }
559
560         return entries;
561     }
562
563     public Boolean addMetadata( String groupId, String artifactId, String version, String key, String value,
564                                 String repositoryId )
565         throws ArchivaRestServiceException
566     {
567         ProjectVersionMetadata projectVersionMetadata =
568             getProjectMetadata( groupId, artifactId, version, repositoryId );
569
570         if ( projectVersionMetadata == null )
571         {
572             return Boolean.FALSE;
573         }
574
575         Map<String, String> properties = new HashMap<String, String>();
576
577         MetadataFacet metadataFacet = projectVersionMetadata.getFacet( GenericMetadataFacet.FACET_ID );
578
579         if ( metadataFacet != null && metadataFacet.toProperties() != null )
580         {
581             properties.putAll( metadataFacet.toProperties() );
582         }
583         else
584         {
585             metadataFacet = new GenericMetadataFacet();
586         }
587
588         properties.put( key, value );
589
590         metadataFacet.fromProperties( properties );
591
592         projectVersionMetadata.addFacet( metadataFacet );
593
594         RepositorySession repositorySession = repositorySessionFactory.createSession();
595
596         try
597         {
598             MetadataRepository metadataRepository = repositorySession.getRepository();
599
600             metadataRepository.updateProjectVersion( repositoryId, groupId, artifactId, projectVersionMetadata );
601
602             repositorySession.save();
603         }
604         catch ( MetadataRepositoryException e )
605         {
606             log.error( e.getMessage(), e );
607             throw new ArchivaRestServiceException( e.getMessage(),
608                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
609         }
610         finally
611         {
612             repositorySession.close();
613         }
614         return Boolean.TRUE;
615     }
616
617     public Boolean deleteMetadata( String groupId, String artifactId, String version, String key, String repositoryId )
618         throws ArchivaRestServiceException
619     {
620         ProjectVersionMetadata projectVersionMetadata =
621             getProjectMetadata( groupId, artifactId, version, repositoryId );
622
623         if ( projectVersionMetadata == null )
624         {
625             return Boolean.FALSE;
626         }
627
628         GenericMetadataFacet metadataFacet =
629             (GenericMetadataFacet) projectVersionMetadata.getFacet( GenericMetadataFacet.FACET_ID );
630
631         if ( metadataFacet != null && metadataFacet.toProperties() != null )
632         {
633             Map<String, String> properties = metadataFacet.toProperties();
634             properties.remove( key );
635             metadataFacet.setAdditionalProperties( properties );
636         }
637         else
638         {
639             return Boolean.TRUE;
640         }
641
642         RepositorySession repositorySession = repositorySessionFactory.createSession();
643
644         try
645         {
646             MetadataRepository metadataRepository = repositorySession.getRepository();
647
648             metadataRepository.updateProjectVersion( repositoryId, groupId, artifactId, projectVersionMetadata );
649
650             repositorySession.save();
651         }
652         catch ( MetadataRepositoryException e )
653         {
654             log.error( e.getMessage(), e );
655             throw new ArchivaRestServiceException( e.getMessage(),
656                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
657         }
658         finally
659         {
660             repositorySession.close();
661         }
662         return Boolean.TRUE;
663     }
664
665     public List<ArtifactContentEntry> getArtifactContentEntries( String groupId, String artifactId, String version,
666                                                                  String classifier, String type, String path,
667                                                                  String repositoryId )
668         throws ArchivaRestServiceException
669     {
670         List<String> selectedRepos = getSelectedRepos( repositoryId );
671         try
672         {
673             for ( String repoId : selectedRepos )
674             {
675
676                 ManagedRepositoryContent managedRepositoryContent =
677                     repositoryContentFactory.getManagedRepositoryContent( repoId );
678                 ArchivaArtifact archivaArtifact = new ArchivaArtifact( groupId, artifactId, version, classifier,
679                                                                        StringUtils.isEmpty( type ) ? "jar" : type,
680                                                                        repoId );
681                 File file = managedRepositoryContent.toFile( archivaArtifact );
682                 if ( file.exists() )
683                 {
684                     return readFileEntries( file, path, repoId );
685                 }
686             }
687         }
688         catch ( IOException e )
689         {
690             log.error( e.getMessage(), e );
691             throw new ArchivaRestServiceException( e.getMessage(),
692                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
693         }
694         catch ( RepositoryNotFoundException e )
695         {
696             log.error( e.getMessage(), e );
697             throw new ArchivaRestServiceException( e.getMessage(),
698                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
699         }
700         catch ( RepositoryException e )
701         {
702             log.error( e.getMessage(), e );
703             throw new ArchivaRestServiceException( e.getMessage(),
704                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
705         }
706         return Collections.emptyList();
707     }
708
709     public List<Artifact> getArtifactDownloadInfos( String groupId, String artifactId, String version,
710                                                     String repositoryId )
711         throws ArchivaRestServiceException
712     {
713         List<String> selectedRepos = getSelectedRepos( repositoryId );
714
715         List<Artifact> artifactDownloadInfos = new ArrayList<Artifact>();
716
717         RepositorySession session = repositorySessionFactory.createSession();
718
719         MetadataResolver metadataResolver = session.getResolver();
720
721         try
722         {
723             for ( String repoId : selectedRepos )
724             {
725                 List<ArtifactMetadata> artifacts = new ArrayList<ArtifactMetadata>(
726                     metadataResolver.resolveArtifacts( session, repoId, groupId, artifactId, version ) );
727                 Collections.sort( artifacts, ArtifactMetadataVersionComparator.INSTANCE );
728                 if ( artifacts != null && !artifacts.isEmpty() )
729                 {
730                     return buildArtifacts( artifacts, repoId );
731                 }
732             }
733         }
734         catch ( MetadataResolutionException e )
735         {
736             log.error( e.getMessage(), e );
737             throw new ArchivaRestServiceException( e.getMessage(),
738                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
739         }
740         finally
741         {
742             if ( session != null )
743             {
744                 session.closeQuietly();
745             }
746         }
747
748         return artifactDownloadInfos;
749     }
750
751     public ArtifactContent getArtifactContentText( String groupId, String artifactId, String version, String classifier,
752                                                    String type, String path, String repositoryId )
753         throws ArchivaRestServiceException
754     {
755         List<String> selectedRepos = getSelectedRepos( repositoryId );
756         try
757         {
758             for ( String repoId : selectedRepos )
759             {
760
761                 ManagedRepositoryContent managedRepositoryContent =
762                     repositoryContentFactory.getManagedRepositoryContent( repoId );
763                 ArchivaArtifact archivaArtifact = new ArchivaArtifact( groupId, artifactId, version, classifier,
764                                                                        StringUtils.isEmpty( type ) ? "jar" : type,
765                                                                        repoId );
766                 File file = managedRepositoryContent.toFile( archivaArtifact );
767                 if ( !file.exists() )
768                 {
769                     log.debug( "file: {} not exists for repository: {} try next repository", file, repoId );
770                     continue;
771                 }
772                 if ( StringUtils.isNotBlank( path ) )
773                 {
774                     // zip entry of the path -> path must a real file entry of the archive
775                     JarFile jarFile = new JarFile( file );
776                     ZipEntry zipEntry = jarFile.getEntry( path );
777                     InputStream inputStream = jarFile.getInputStream( zipEntry );
778                     try
779                     {
780                         return new ArtifactContent( IOUtils.toString( inputStream ), repoId );
781                     }
782                     finally
783                     {
784                         closeQuietly( jarFile );
785                         IOUtils.closeQuietly( inputStream );
786                     }
787                 }
788                 return new ArtifactContent( FileUtils.readFileToString( file ), repoId );
789             }
790         }
791         catch ( IOException e )
792         {
793             log.error( e.getMessage(), e );
794             throw new ArchivaRestServiceException( e.getMessage(),
795                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
796         }
797         catch ( RepositoryNotFoundException e )
798         {
799             log.error( e.getMessage(), e );
800             throw new ArchivaRestServiceException( e.getMessage(),
801                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
802         }
803         catch ( RepositoryException e )
804         {
805             log.error( e.getMessage(), e );
806             throw new ArchivaRestServiceException( e.getMessage(),
807                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
808         }
809         log.debug( "artifact: {}:{}:{}:{}:{} not found", groupId, artifactId, version, classifier, type );
810         // 404 ?
811         return new ArtifactContent();
812     }
813
814     public Boolean artifactAvailable( String groupId, String artifactId, String version, String classifier,
815                                       String repositoryId )
816         throws ArchivaRestServiceException
817     {
818         List<String> selectedRepos = getSelectedRepos( repositoryId );
819
820         boolean snapshot = VersionUtil.isSnapshot( version );
821
822         try
823         {
824             for ( String repoId : selectedRepos )
825             {
826
827                 ManagedRepository managedRepository = managedRepositoryAdmin.getManagedRepository( repoId );
828
829                 if ( ( snapshot && !managedRepository.isSnapshots() ) || ( !snapshot
830                     && managedRepository.isSnapshots() ) )
831                 {
832                     continue;
833                 }
834                 ManagedRepositoryContent managedRepositoryContent =
835                     repositoryContentFactory.getManagedRepositoryContent( repoId );
836                 // FIXME default to jar which can be wrong for war zip etc....
837                 ArchivaArtifact archivaArtifact = new ArchivaArtifact( groupId, artifactId, version,
838                                                                        StringUtils.isEmpty( classifier )
839                                                                            ? ""
840                                                                            : classifier, "jar", repoId
841                 );
842                 File file = managedRepositoryContent.toFile( archivaArtifact );
843
844                 if ( file != null && file.exists() )
845                 {
846                     return true;
847                 }
848
849                 // in case of SNAPSHOT we can have timestamped version locally !
850                 if ( StringUtils.endsWith( version, VersionUtil.SNAPSHOT ) )
851                 {
852                     File metadataFile = new File( file.getParent(), MetadataTools.MAVEN_METADATA );
853                     if ( metadataFile.exists() )
854                     {
855                         try
856                         {
857                             ArchivaRepositoryMetadata archivaRepositoryMetadata =
858                                 MavenMetadataReader.read( metadataFile );
859                             int buildNumber = archivaRepositoryMetadata.getSnapshotVersion().getBuildNumber();
860                             String timeStamp = archivaRepositoryMetadata.getSnapshotVersion().getTimestamp();
861                             // rebuild file name with timestamped version and build number
862                             String timeStampFileName = new StringBuilder( artifactId ).append( '-' ) //
863                                 .append( StringUtils.remove( version, "-" + VersionUtil.SNAPSHOT ) ) //
864                                 .append( '-' ).append( timeStamp ) //
865                                 .append( '-' ).append( Integer.toString( buildNumber ) ) //
866                                 .append( ( StringUtils.isEmpty( classifier ) ? "" : "-" + classifier ) ) //
867                                 .append( ".jar" ).toString();
868
869                             File timeStampFile = new File( file.getParent(), timeStampFileName );
870                             log.debug( "try to find timestamped snapshot version file: {}", timeStampFile.getPath() );
871                             if ( timeStampFile.exists() )
872                             {
873                                 return true;
874                             }
875                         }
876                         catch ( XMLException e )
877                         {
878                             log.warn( "skip fail to find timestamped snapshot file: {}", e.getMessage() );
879                         }
880                     }
881                 }
882
883                 String path = managedRepositoryContent.toPath( archivaArtifact );
884
885                 file = connectors.fetchFromProxies( managedRepositoryContent, path );
886
887                 if ( file != null && file.exists() )
888                 {
889                     // download pom now
890                     String pomPath = StringUtils.substringBeforeLast( path, ".jar" ) + ".pom";
891                     connectors.fetchFromProxies( managedRepositoryContent, pomPath );
892                     return true;
893                 }
894             }
895         }
896         catch ( RepositoryAdminException e )
897         {
898             log.error( e.getMessage(), e );
899             throw new ArchivaRestServiceException( e.getMessage(),
900                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
901         }
902         catch ( RepositoryException e )
903         {
904             log.error( e.getMessage(), e );
905             throw new ArchivaRestServiceException( e.getMessage(),
906                                                    Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
907         }
908
909         return false;
910     }
911
912     public Boolean artifactAvailable( String groupId, String artifactId, String version, String repositoryId )
913         throws ArchivaRestServiceException
914     {
915         return artifactAvailable( groupId, artifactId, version, null, repositoryId );
916     }
917
918     public List<Artifact> getArtifacts( String repositoryId )
919         throws ArchivaRestServiceException
920     {
921         RepositorySession repositorySession = repositorySessionFactory.createSession();
922         try
923         {
924             List<ArtifactMetadata> artifactMetadatas = repositorySession.getRepository().getArtifacts( repositoryId );
925             return buildArtifacts( artifactMetadatas, repositoryId );
926         }
927         catch ( MetadataRepositoryException e )
928         {
929             throw new ArchivaRestServiceException( e.getMessage(), e );
930         }
931         finally
932         {
933             repositorySession.close();
934         }
935     }
936
937     public Boolean importMetadata( MetadataAddRequest metadataAddRequest, String repositoryId )
938         throws ArchivaRestServiceException
939     {
940         boolean result = true;
941         for ( Map.Entry<String, String> metadata : metadataAddRequest.getMetadatas().entrySet() )
942         {
943             result = addMetadata( metadataAddRequest.getGroupId(), metadataAddRequest.getArtifactId(),
944                                   metadataAddRequest.getVersion(), metadata.getKey(), metadata.getValue(),
945                                   repositoryId );
946             if ( !result )
947             {
948                 break;
949             }
950         }
951         return result;
952     }
953
954     //---------------------------
955     // internals
956     //---------------------------
957
958     private void closeQuietly( JarFile jarFile )
959     {
960         if ( jarFile != null )
961         {
962             try
963             {
964                 jarFile.close();
965             }
966             catch ( IOException e )
967             {
968                 log.warn( "ignore error closing jarFile {}", jarFile.getName() );
969             }
970         }
971     }
972
973     protected List<ArtifactContentEntry> readFileEntries( File file, String filterPath, String repoId )
974         throws IOException
975     {
976         Map<String, ArtifactContentEntry> artifactContentEntryMap = new HashMap<String, ArtifactContentEntry>();
977         int filterDepth = StringUtils.countMatches( filterPath, "/" );
978         /*if ( filterDepth == 0 )
979         {
980             filterDepth = 1;
981         }*/
982         JarFile jarFile = new JarFile( file );
983         try
984         {
985             Enumeration<JarEntry> jarEntryEnumeration = jarFile.entries();
986             while ( jarEntryEnumeration.hasMoreElements() )
987             {
988                 JarEntry currentEntry = jarEntryEnumeration.nextElement();
989                 String cleanedEntryName = StringUtils.endsWith( currentEntry.getName(), "/" ) ? //
990                     StringUtils.substringBeforeLast( currentEntry.getName(), "/" ) : currentEntry.getName();
991                 String entryRootPath = getRootPath( cleanedEntryName );
992                 int depth = StringUtils.countMatches( cleanedEntryName, "/" );
993                 if ( StringUtils.isEmpty( filterPath ) //
994                     && !artifactContentEntryMap.containsKey( entryRootPath ) //
995                     && depth == filterDepth )
996                 {
997
998                     artifactContentEntryMap.put( entryRootPath,
999                                                  new ArtifactContentEntry( entryRootPath, !currentEntry.isDirectory(),
1000                                                                            depth, repoId )
1001                     );
1002                 }
1003                 else
1004                 {
1005                     if ( StringUtils.startsWith( cleanedEntryName, filterPath ) //
1006                         && ( depth == filterDepth || ( !currentEntry.isDirectory() && depth == filterDepth ) ) )
1007                     {
1008                         artifactContentEntryMap.put( cleanedEntryName, new ArtifactContentEntry( cleanedEntryName,
1009                                                                                                  !currentEntry.isDirectory(),
1010                                                                                                  depth, repoId ) );
1011                     }
1012                 }
1013             }
1014
1015             if ( StringUtils.isNotEmpty( filterPath ) )
1016             {
1017                 Map<String, ArtifactContentEntry> filteredArtifactContentEntryMap =
1018                     new HashMap<String, ArtifactContentEntry>();
1019
1020                 for ( Map.Entry<String, ArtifactContentEntry> entry : artifactContentEntryMap.entrySet() )
1021                 {
1022                     filteredArtifactContentEntryMap.put( entry.getKey(), entry.getValue() );
1023                 }
1024
1025                 List<ArtifactContentEntry> sorted = getSmallerDepthEntries( filteredArtifactContentEntryMap );
1026                 if ( sorted == null )
1027                 {
1028                     return Collections.emptyList();
1029                 }
1030                 Collections.sort( sorted, ArtifactContentEntryComparator.INSTANCE );
1031                 return sorted;
1032             }
1033         }
1034         finally
1035         {
1036             if ( jarFile != null )
1037             {
1038                 jarFile.close();
1039             }
1040         }
1041         List<ArtifactContentEntry> sorted = new ArrayList<ArtifactContentEntry>( artifactContentEntryMap.values() );
1042         Collections.sort( sorted, ArtifactContentEntryComparator.INSTANCE );
1043         return sorted;
1044     }
1045
1046     private List<ArtifactContentEntry> getSmallerDepthEntries( Map<String, ArtifactContentEntry> entries )
1047     {
1048         int smallestDepth = Integer.MAX_VALUE;
1049         Map<Integer, List<ArtifactContentEntry>> perDepthList = new HashMap<Integer, List<ArtifactContentEntry>>();
1050         for ( Map.Entry<String, ArtifactContentEntry> entry : entries.entrySet() )
1051         {
1052
1053             ArtifactContentEntry current = entry.getValue();
1054
1055             if ( current.getDepth() < smallestDepth )
1056             {
1057                 smallestDepth = current.getDepth();
1058             }
1059
1060             List<ArtifactContentEntry> currentList = perDepthList.get( current.getDepth() );
1061
1062             if ( currentList == null )
1063             {
1064                 currentList = new ArrayList<ArtifactContentEntry>();
1065                 currentList.add( current );
1066                 perDepthList.put( current.getDepth(), currentList );
1067             }
1068             else
1069             {
1070                 currentList.add( current );
1071             }
1072
1073         }
1074
1075         return perDepthList.get( smallestDepth );
1076     }
1077
1078     /**
1079      * @param path
1080      * @return org/apache -&gt; org , org -&gt; org
1081      */
1082     private String getRootPath( String path )
1083     {
1084         if ( StringUtils.contains( path, '/' ) )
1085         {
1086             return StringUtils.substringBefore( path, "/" );
1087         }
1088         return path;
1089     }
1090
1091     private List<String> getSelectedRepos( String repositoryId )
1092         throws ArchivaRestServiceException
1093     {
1094
1095         List<String> selectedRepos = getObservableRepos();
1096
1097         if ( CollectionUtils.isEmpty( selectedRepos ) )
1098         {
1099             return Collections.emptyList();
1100         }
1101
1102         if ( StringUtils.isNotEmpty( repositoryId ) )
1103         {
1104             // check user has karma on the repository
1105             if ( !selectedRepos.contains( repositoryId ) )
1106             {
1107                 throw new ArchivaRestServiceException( "browse.root.groups.repositoy.denied",
1108                                                        Response.Status.FORBIDDEN.getStatusCode(), null );
1109             }
1110             selectedRepos = Collections.singletonList( repositoryId );
1111         }
1112         return selectedRepos;
1113     }
1114
1115
1116     private String collapseNamespaces( RepositorySession repositorySession, MetadataResolver metadataResolver,
1117                                        Collection<String> repoIds, String n )
1118         throws MetadataResolutionException
1119     {
1120         Set<String> subNamespaces = new LinkedHashSet<String>();
1121         for ( String repoId : repoIds )
1122         {
1123             subNamespaces.addAll( metadataResolver.resolveNamespaces( repositorySession, repoId, n ) );
1124         }
1125         if ( subNamespaces.size() != 1 )
1126         {
1127             log.debug( "{} is not collapsible as it has sub-namespaces: {}", n, subNamespaces );
1128             return n;
1129         }
1130         else
1131         {
1132             for ( String repoId : repoIds )
1133             {
1134                 Collection<String> projects = metadataResolver.resolveProjects( repositorySession, repoId, n );
1135                 if ( projects != null && !projects.isEmpty() )
1136                 {
1137                     log.debug( "{} is not collapsible as it has projects", n );
1138                     return n;
1139                 }
1140             }
1141             return collapseNamespaces( repositorySession, metadataResolver, repoIds,
1142                                        n + "." + subNamespaces.iterator().next() );
1143         }
1144     }
1145
1146     public Cache getVersionMetadataCache()
1147     {
1148         return versionMetadataCache;
1149     }
1150
1151     public void setVersionMetadataCache( Cache versionMetadataCache )
1152     {
1153         this.versionMetadataCache = versionMetadataCache;
1154     }
1155 }