001    package org.apache.archiva.rest.services;
002    /*
003     * Licensed to the Apache Software Foundation (ASF) under one
004     * or more contributor license agreements.  See the NOTICE file
005     * distributed with this work for additional information
006     * regarding copyright ownership.  The ASF licenses this file
007     * to you under the Apache License, Version 2.0 (the
008     * "License"); you may not use this file except in compliance
009     * with the License.  You may obtain a copy of the License at
010     *
011     *   http://www.apache.org/licenses/LICENSE-2.0
012     *
013     * Unless required by applicable law or agreed to in writing,
014     * software distributed under the License is distributed on an
015     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
016     * KIND, either express or implied.  See the License for the
017     * specific language governing permissions and limitations
018     * under the License.
019     */
020    
021    import org.apache.archiva.admin.model.RepositoryAdminException;
022    import org.apache.archiva.admin.model.beans.ManagedRepository;
023    import org.apache.archiva.common.utils.VersionComparator;
024    import org.apache.archiva.common.utils.VersionUtil;
025    import org.apache.archiva.dependency.tree.maven2.DependencyTreeBuilder;
026    import org.apache.archiva.maven2.metadata.MavenMetadataReader;
027    import org.apache.archiva.maven2.model.Artifact;
028    import org.apache.archiva.maven2.model.TreeEntry;
029    import org.apache.archiva.metadata.generic.GenericMetadataFacet;
030    import org.apache.archiva.metadata.model.ArtifactMetadata;
031    import org.apache.archiva.metadata.model.MetadataFacet;
032    import org.apache.archiva.metadata.model.ProjectVersionMetadata;
033    import org.apache.archiva.metadata.model.ProjectVersionReference;
034    import org.apache.archiva.metadata.repository.MetadataRepository;
035    import org.apache.archiva.metadata.repository.MetadataRepositoryException;
036    import org.apache.archiva.metadata.repository.MetadataResolutionException;
037    import org.apache.archiva.metadata.repository.MetadataResolver;
038    import org.apache.archiva.metadata.repository.RepositorySession;
039    import org.apache.archiva.metadata.repository.storage.maven2.ArtifactMetadataVersionComparator;
040    import org.apache.archiva.metadata.repository.storage.maven2.MavenProjectFacet;
041    import org.apache.archiva.model.ArchivaArtifact;
042    import org.apache.archiva.model.ArchivaRepositoryMetadata;
043    import org.apache.archiva.proxy.model.RepositoryProxyConnectors;
044    import org.apache.archiva.repository.ManagedRepositoryContent;
045    import org.apache.archiva.repository.RepositoryContentFactory;
046    import org.apache.archiva.repository.RepositoryException;
047    import org.apache.archiva.repository.RepositoryNotFoundException;
048    import org.apache.archiva.repository.metadata.MetadataTools;
049    import org.apache.archiva.rest.api.model.ArtifactContent;
050    import org.apache.archiva.rest.api.model.ArtifactContentEntry;
051    import org.apache.archiva.rest.api.model.BrowseResult;
052    import org.apache.archiva.rest.api.model.BrowseResultEntry;
053    import org.apache.archiva.rest.api.model.Entry;
054    import org.apache.archiva.rest.api.model.MetadataAddRequest;
055    import org.apache.archiva.rest.api.model.VersionsList;
056    import org.apache.archiva.rest.api.services.ArchivaRestServiceException;
057    import org.apache.archiva.rest.api.services.BrowseService;
058    import org.apache.archiva.rest.services.utils.ArtifactContentEntryComparator;
059    import org.apache.archiva.security.ArchivaSecurityException;
060    import org.apache.archiva.xml.XMLException;
061    import org.apache.commons.collections.CollectionUtils;
062    import org.apache.commons.io.FileUtils;
063    import org.apache.commons.io.IOUtils;
064    import org.apache.commons.lang.StringUtils;
065    import org.springframework.stereotype.Service;
066    
067    import javax.inject.Inject;
068    import javax.inject.Named;
069    import javax.ws.rs.core.Response;
070    import java.io.File;
071    import java.io.IOException;
072    import java.io.InputStream;
073    import java.util.ArrayList;
074    import java.util.Collection;
075    import java.util.Collections;
076    import java.util.Enumeration;
077    import java.util.HashMap;
078    import java.util.LinkedHashSet;
079    import java.util.List;
080    import java.util.Map;
081    import java.util.Set;
082    import java.util.jar.JarEntry;
083    import java.util.jar.JarFile;
084    import java.util.zip.ZipEntry;
085    
086    /**
087     * @author Olivier Lamy
088     * @since 1.4-M3
089     */
090    @Service( "browseService#rest" )
091    public class DefaultBrowseService
092        extends AbstractRestService
093        implements BrowseService
094    {
095    
096        @Inject
097        private DependencyTreeBuilder dependencyTreeBuilder;
098    
099        @Inject
100        private RepositoryContentFactory repositoryContentFactory;
101    
102        @Inject
103        @Named( value = "repositoryProxyConnectors#default" )
104        private RepositoryProxyConnectors connectors;
105    
106        public BrowseResult getRootGroups( String repositoryId )
107            throws ArchivaRestServiceException
108        {
109            List<String> selectedRepos = getSelectedRepos( repositoryId );
110    
111            Set<String> namespaces = new LinkedHashSet<String>();
112    
113            // TODO: this logic should be optional, particularly remembering we want to keep this code simple
114            //       it is located here to avoid the content repository implementation needing to do too much for what
115            //       is essentially presentation code
116            Set<String> namespacesToCollapse;
117            RepositorySession repositorySession = repositorySessionFactory.createSession();
118            try
119            {
120                MetadataResolver metadataResolver = repositorySession.getResolver();
121                namespacesToCollapse = new LinkedHashSet<String>();
122    
123                for ( String repoId : selectedRepos )
124                {
125                    namespacesToCollapse.addAll( metadataResolver.resolveRootNamespaces( repositorySession, repoId ) );
126                }
127                for ( String n : namespacesToCollapse )
128                {
129                    // TODO: check performance of this
130                    namespaces.add( collapseNamespaces( repositorySession, metadataResolver, selectedRepos, n ) );
131                }
132            }
133            catch ( MetadataResolutionException e )
134            {
135                throw new ArchivaRestServiceException( e.getMessage(),
136                                                       Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
137            }
138            finally
139            {
140                repositorySession.close();
141            }
142    
143            List<BrowseResultEntry> browseGroupResultEntries = new ArrayList<BrowseResultEntry>( namespaces.size() );
144            for ( String namespace : namespaces )
145            {
146                browseGroupResultEntries.add( new BrowseResultEntry( namespace, false ) );
147            }
148    
149            Collections.sort( browseGroupResultEntries );
150            return new BrowseResult( browseGroupResultEntries );
151        }
152    
153        public BrowseResult browseGroupId( String groupId, String repositoryId )
154            throws ArchivaRestServiceException
155        {
156            List<String> selectedRepos = getSelectedRepos( repositoryId );
157    
158            Set<String> projects = new LinkedHashSet<String>();
159    
160            RepositorySession repositorySession = repositorySessionFactory.createSession();
161            Set<String> namespaces;
162            try
163            {
164                MetadataResolver metadataResolver = repositorySession.getResolver();
165    
166                Set<String> namespacesToCollapse = new LinkedHashSet<String>();
167                for ( String repoId : selectedRepos )
168                {
169                    namespacesToCollapse.addAll( metadataResolver.resolveNamespaces( repositorySession, repoId, groupId ) );
170    
171                    projects.addAll( metadataResolver.resolveProjects( repositorySession, repoId, groupId ) );
172                }
173    
174                // TODO: this logic should be optional, particularly remembering we want to keep this code simple
175                // it is located here to avoid the content repository implementation needing to do too much for what
176                // is essentially presentation code
177                namespaces = new LinkedHashSet<String>();
178                for ( String n : namespacesToCollapse )
179                {
180                    // TODO: check performance of this
181                    namespaces.add(
182                        collapseNamespaces( repositorySession, metadataResolver, selectedRepos, groupId + "." + n ) );
183                }
184            }
185            catch ( MetadataResolutionException e )
186            {
187                throw new ArchivaRestServiceException( e.getMessage(),
188                                                       Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
189            }
190            finally
191            {
192                repositorySession.close();
193            }
194            List<BrowseResultEntry> browseGroupResultEntries =
195                new ArrayList<BrowseResultEntry>( namespaces.size() + projects.size() );
196            for ( String namespace : namespaces )
197            {
198                browseGroupResultEntries.add( new BrowseResultEntry( namespace, false ) );
199            }
200            for ( String project : projects )
201            {
202                browseGroupResultEntries.add( new BrowseResultEntry( groupId + '.' + project, true ) );
203            }
204            Collections.sort( browseGroupResultEntries );
205            return new BrowseResult( browseGroupResultEntries );
206    
207        }
208    
209        public VersionsList getVersionsList( String groupId, String artifactId, String repositoryId )
210            throws ArchivaRestServiceException
211        {
212            List<String> selectedRepos = getSelectedRepos( repositoryId );
213    
214            try
215            {
216                Collection<String> versions = getVersions( selectedRepos, groupId, artifactId );
217                return new VersionsList( new ArrayList<String>( versions ) );
218            }
219            catch ( MetadataResolutionException e )
220            {
221                throw new ArchivaRestServiceException( e.getMessage(),
222                                                       Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
223            }
224    
225        }
226    
227        private Collection<String> getVersions( List<String> selectedRepos, String groupId, String artifactId )
228            throws MetadataResolutionException
229    
230        {
231            RepositorySession repositorySession = repositorySessionFactory.createSession();
232            try
233            {
234                MetadataResolver metadataResolver = repositorySession.getResolver();
235    
236                Set<String> versions = new LinkedHashSet<String>();
237    
238                for ( String repoId : selectedRepos )
239                {
240                    Collection<String> projectVersions =
241                        metadataResolver.resolveProjectVersions( repositorySession, repoId, groupId, artifactId );
242                    versions.addAll( projectVersions );
243                }
244    
245                List<String> sortedVersions = new ArrayList<String>( versions );
246    
247                Collections.sort( sortedVersions, VersionComparator.getInstance() );
248    
249                return sortedVersions;
250            }
251            finally
252            {
253                repositorySession.close();
254            }
255        }
256    
257        public ProjectVersionMetadata getProjectMetadata( String groupId, String artifactId, String version,
258                                                          String repositoryId )
259            throws ArchivaRestServiceException
260        {
261            List<String> selectedRepos = getSelectedRepos( repositoryId );
262    
263            RepositorySession repositorySession = null;
264            try
265            {
266                repositorySession = repositorySessionFactory.createSession();
267    
268                MetadataResolver metadataResolver = repositorySession.getResolver();
269    
270                ProjectVersionMetadata versionMetadata = null;
271                for ( String repoId : selectedRepos )
272                {
273                    if ( versionMetadata == null || versionMetadata.isIncomplete() )
274                    {
275                        try
276                        {
277                            versionMetadata =
278                                metadataResolver.resolveProjectVersion( repositorySession, repoId, groupId, artifactId,
279                                                                        version );
280                        }
281                        catch ( MetadataResolutionException e )
282                        {
283                            log.warn(
284                                "Skipping invalid metadata while compiling shared model for " + groupId + ":" + artifactId
285                                    + " in repo " + repoId + ": " + e.getMessage() );
286                        }
287                    }
288                }
289    
290                return versionMetadata;
291            }
292            finally
293            {
294                if ( repositorySession != null )
295                {
296                    repositorySession.close();
297                }
298            }
299    
300        }
301    
302        public ProjectVersionMetadata getProjectVersionMetadata( String groupId, String artifactId, String repositoryId )
303            throws ArchivaRestServiceException
304        {
305    
306            List<String> selectedRepos = getSelectedRepos( repositoryId );
307    
308            RepositorySession repositorySession = null;
309            try
310            {
311    
312                Collection<String> projectVersions = getVersions( selectedRepos, groupId, artifactId );
313    
314                repositorySession = repositorySessionFactory.createSession();
315    
316                MetadataResolver metadataResolver = repositorySession.getResolver();
317    
318                ProjectVersionMetadata sharedModel = new ProjectVersionMetadata();
319    
320                MavenProjectFacet mavenFacet = new MavenProjectFacet();
321                mavenFacet.setGroupId( groupId );
322                mavenFacet.setArtifactId( artifactId );
323                sharedModel.addFacet( mavenFacet );
324    
325                boolean isFirstVersion = true;
326    
327                for ( String version : projectVersions )
328                {
329                    ProjectVersionMetadata versionMetadata = null;
330                    for ( String repoId : selectedRepos )
331                    {
332                        if ( versionMetadata == null || versionMetadata.isIncomplete() )
333                        {
334                            try
335                            {
336                                versionMetadata =
337                                    metadataResolver.resolveProjectVersion( repositorySession, repoId, groupId, artifactId,
338                                                                            version );
339                            }
340                            catch ( MetadataResolutionException e )
341                            {
342                                log.error( "Skipping invalid metadata while compiling shared model for " + groupId + ":"
343                                               + artifactId + " in repo " + repoId + ": " + e.getMessage() );
344                            }
345                        }
346                    }
347    
348                    if ( versionMetadata == null )
349                    {
350                        continue;
351                    }
352    
353                    if ( isFirstVersion )
354                    {
355                        sharedModel = versionMetadata;
356                        sharedModel.setId( null );
357                    }
358                    else
359                    {
360                        MavenProjectFacet versionMetadataMavenFacet =
361                            (MavenProjectFacet) versionMetadata.getFacet( MavenProjectFacet.FACET_ID );
362                        if ( versionMetadataMavenFacet != null )
363                        {
364                            if ( mavenFacet.getPackaging() != null && !StringUtils.equalsIgnoreCase(
365                                mavenFacet.getPackaging(), versionMetadataMavenFacet.getPackaging() ) )
366                            {
367                                mavenFacet.setPackaging( null );
368                            }
369                        }
370    
371                        if ( StringUtils.isEmpty( sharedModel.getName() ) && !StringUtils.isEmpty(
372                            versionMetadata.getName() ) )
373                        {
374                            sharedModel.setName( versionMetadata.getName() );
375                        }
376    
377                        if ( sharedModel.getDescription() != null && !StringUtils.equalsIgnoreCase(
378                            sharedModel.getDescription(), versionMetadata.getDescription() ) )
379                        {
380                            sharedModel.setDescription( StringUtils.isNotEmpty( versionMetadata.getDescription() )
381                                                            ? versionMetadata.getDescription()
382                                                            : "" );
383                        }
384    
385                        if ( sharedModel.getIssueManagement() != null && versionMetadata.getIssueManagement() != null
386                            && !StringUtils.equalsIgnoreCase( sharedModel.getIssueManagement().getUrl(),
387                                                              versionMetadata.getIssueManagement().getUrl() ) )
388                        {
389                            sharedModel.setIssueManagement( versionMetadata.getIssueManagement() );
390                        }
391    
392                        if ( sharedModel.getCiManagement() != null && versionMetadata.getCiManagement() != null
393                            && !StringUtils.equalsIgnoreCase( sharedModel.getCiManagement().getUrl(),
394                                                              versionMetadata.getCiManagement().getUrl() ) )
395                        {
396                            sharedModel.setCiManagement( versionMetadata.getCiManagement() );
397                        }
398    
399                        if ( sharedModel.getOrganization() != null && versionMetadata.getOrganization() != null
400                            && !StringUtils.equalsIgnoreCase( sharedModel.getOrganization().getName(),
401                                                              versionMetadata.getOrganization().getName() ) )
402                        {
403                            sharedModel.setOrganization( versionMetadata.getOrganization() );
404                        }
405    
406                        if ( sharedModel.getUrl() != null && !StringUtils.equalsIgnoreCase( sharedModel.getUrl(),
407                                                                                            versionMetadata.getUrl() ) )
408                        {
409                            sharedModel.setUrl( versionMetadata.getUrl() );
410                        }
411                    }
412    
413                    isFirstVersion = false;
414                }
415                return sharedModel;
416            }
417            catch ( MetadataResolutionException e )
418            {
419                throw new ArchivaRestServiceException( e.getMessage(),
420                                                       Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
421            }
422            finally
423            {
424                if ( repositorySession != null )
425                {
426                    repositorySession.close();
427                }
428            }
429        }
430    
431        public List<TreeEntry> getTreeEntries( String groupId, String artifactId, String version, String repositoryId )
432            throws ArchivaRestServiceException
433        {
434            List<String> selectedRepos = getSelectedRepos( repositoryId );
435    
436            try
437            {
438    
439                return dependencyTreeBuilder.buildDependencyTree( selectedRepos, groupId, artifactId, version );
440    
441            }
442            catch ( Exception e )
443            {
444                log.error( e.getMessage(), e );
445            }
446    
447            return Collections.emptyList();
448        }
449    
450        public List<ManagedRepository> getUserRepositories()
451            throws ArchivaRestServiceException
452        {
453            try
454            {
455                return userRepositories.getAccessibleRepositories( getPrincipal() );
456            }
457            catch ( ArchivaSecurityException e )
458            {
459                throw new ArchivaRestServiceException( "repositories.read.observable.error",
460                                                       Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
461            }
462        }
463    
464        public List<Artifact> getDependees( String groupId, String artifactId, String version, String repositoryId )
465            throws ArchivaRestServiceException
466        {
467            List<ProjectVersionReference> references = new ArrayList<ProjectVersionReference>();
468            // TODO: what if we get duplicates across repositories?
469            RepositorySession repositorySession = repositorySessionFactory.createSession();
470            try
471            {
472                MetadataResolver metadataResolver = repositorySession.getResolver();
473                for ( String repoId : getObservableRepos() )
474                {
475                    // TODO: what about if we want to see this irrespective of version?
476                    references.addAll(
477                        metadataResolver.resolveProjectReferences( repositorySession, repoId, groupId, artifactId,
478                                                                   version ) );
479                }
480            }
481            catch ( MetadataResolutionException e )
482            {
483                throw new ArchivaRestServiceException( e.getMessage(),
484                                                       Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
485            }
486            finally
487            {
488                repositorySession.close();
489            }
490    
491            List<Artifact> artifacts = new ArrayList<Artifact>( references.size() );
492    
493            for ( ProjectVersionReference projectVersionReference : references )
494            {
495                artifacts.add( new Artifact( projectVersionReference.getNamespace(), projectVersionReference.getProjectId(),
496                                             projectVersionReference.getProjectVersion() ) );
497            }
498            return artifacts;
499        }
500    
501        public List<Entry> getMetadatas( String groupId, String artifactId, String version, String repositoryId )
502            throws ArchivaRestServiceException
503        {
504            ProjectVersionMetadata projectVersionMetadata =
505                getProjectMetadata( groupId, artifactId, version, repositoryId );
506            if ( projectVersionMetadata == null )
507            {
508                return Collections.emptyList();
509            }
510            MetadataFacet metadataFacet = projectVersionMetadata.getFacet( GenericMetadataFacet.FACET_ID );
511    
512            if ( metadataFacet == null )
513            {
514                return Collections.emptyList();
515            }
516            Map<String, String> map = metadataFacet.toProperties();
517            List<Entry> entries = new ArrayList<Entry>( map.size() );
518    
519            for ( Map.Entry<String, String> entry : map.entrySet() )
520            {
521                entries.add( new Entry( entry.getKey(), entry.getValue() ) );
522            }
523    
524            return entries;
525        }
526    
527        public Boolean addMetadata( String groupId, String artifactId, String version, String key, String value,
528                                    String repositoryId )
529            throws ArchivaRestServiceException
530        {
531            ProjectVersionMetadata projectVersionMetadata =
532                getProjectMetadata( groupId, artifactId, version, repositoryId );
533    
534            if ( projectVersionMetadata == null )
535            {
536                return Boolean.FALSE;
537            }
538    
539            Map<String, String> properties = new HashMap<String, String>();
540    
541            MetadataFacet metadataFacet = projectVersionMetadata.getFacet( GenericMetadataFacet.FACET_ID );
542    
543            if ( metadataFacet != null && metadataFacet.toProperties() != null )
544            {
545                properties.putAll( metadataFacet.toProperties() );
546            }
547            else
548            {
549                metadataFacet = new GenericMetadataFacet();
550            }
551    
552            properties.put( key, value );
553    
554            metadataFacet.fromProperties( properties );
555    
556            projectVersionMetadata.addFacet( metadataFacet );
557    
558            RepositorySession repositorySession = repositorySessionFactory.createSession();
559    
560            try
561            {
562                MetadataRepository metadataRepository = repositorySession.getRepository();
563    
564                metadataRepository.updateProjectVersion( repositoryId, groupId, artifactId, projectVersionMetadata );
565    
566                repositorySession.save();
567            }
568            catch ( MetadataRepositoryException e )
569            {
570                log.error( e.getMessage(), e );
571                throw new ArchivaRestServiceException( e.getMessage(),
572                                                       Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
573            }
574            finally
575            {
576                repositorySession.close();
577            }
578            return Boolean.TRUE;
579        }
580    
581        public Boolean deleteMetadata( String groupId, String artifactId, String version, String key, String repositoryId )
582            throws ArchivaRestServiceException
583        {
584            ProjectVersionMetadata projectVersionMetadata =
585                getProjectMetadata( groupId, artifactId, version, repositoryId );
586    
587            if ( projectVersionMetadata == null )
588            {
589                return Boolean.FALSE;
590            }
591    
592            GenericMetadataFacet metadataFacet =
593                (GenericMetadataFacet) projectVersionMetadata.getFacet( GenericMetadataFacet.FACET_ID );
594    
595            if ( metadataFacet != null && metadataFacet.toProperties() != null )
596            {
597                Map<String, String> properties = metadataFacet.toProperties();
598                properties.remove( key );
599                metadataFacet.setAdditionalProperties( properties );
600            }
601            else
602            {
603                return Boolean.TRUE;
604            }
605    
606            RepositorySession repositorySession = repositorySessionFactory.createSession();
607    
608            try
609            {
610                MetadataRepository metadataRepository = repositorySession.getRepository();
611    
612                metadataRepository.updateProjectVersion( repositoryId, groupId, artifactId, projectVersionMetadata );
613    
614                repositorySession.save();
615            }
616            catch ( MetadataRepositoryException e )
617            {
618                log.error( e.getMessage(), e );
619                throw new ArchivaRestServiceException( e.getMessage(),
620                                                       Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
621            }
622            finally
623            {
624                repositorySession.close();
625            }
626            return Boolean.TRUE;
627        }
628    
629        public List<ArtifactContentEntry> getArtifactContentEntries( String groupId, String artifactId, String version,
630                                                                     String classifier, String type, String path,
631                                                                     String repositoryId )
632            throws ArchivaRestServiceException
633        {
634            List<String> selectedRepos = getSelectedRepos( repositoryId );
635            try
636            {
637                for ( String repoId : selectedRepos )
638                {
639    
640                    ManagedRepositoryContent managedRepositoryContent =
641                        repositoryContentFactory.getManagedRepositoryContent( repoId );
642                    ArchivaArtifact archivaArtifact = new ArchivaArtifact( groupId, artifactId, version, classifier,
643                                                                           StringUtils.isEmpty( type ) ? "jar" : type,
644                                                                           repoId );
645                    File file = managedRepositoryContent.toFile( archivaArtifact );
646                    if ( file.exists() )
647                    {
648                        return readFileEntries( file, path, repoId );
649                    }
650                }
651            }
652            catch ( IOException e )
653            {
654                log.error( e.getMessage(), e );
655                throw new ArchivaRestServiceException( e.getMessage(),
656                                                       Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
657            }
658            catch ( RepositoryNotFoundException e )
659            {
660                log.error( e.getMessage(), e );
661                throw new ArchivaRestServiceException( e.getMessage(),
662                                                       Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
663            }
664            catch ( RepositoryException e )
665            {
666                log.error( e.getMessage(), e );
667                throw new ArchivaRestServiceException( e.getMessage(),
668                                                       Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
669            }
670            return Collections.emptyList();
671        }
672    
673        public List<Artifact> getArtifactDownloadInfos( String groupId, String artifactId, String version,
674                                                        String repositoryId )
675            throws ArchivaRestServiceException
676        {
677            List<String> selectedRepos = getSelectedRepos( repositoryId );
678    
679            List<Artifact> artifactDownloadInfos = new ArrayList<Artifact>();
680    
681            RepositorySession session = repositorySessionFactory.createSession();
682    
683            MetadataResolver metadataResolver = session.getResolver();
684    
685            try
686            {
687                for ( String repoId : selectedRepos )
688                {
689                    List<ArtifactMetadata> artifacts = new ArrayList<ArtifactMetadata>(
690                        metadataResolver.resolveArtifacts( session, repoId, groupId, artifactId, version ) );
691                    Collections.sort( artifacts, ArtifactMetadataVersionComparator.INSTANCE );
692                    if ( artifacts != null && !artifacts.isEmpty() )
693                    {
694                        return buildArtifacts( artifacts, repoId );
695                    }
696                }
697            }
698            catch ( MetadataResolutionException e )
699            {
700                log.error( e.getMessage(), e );
701                throw new ArchivaRestServiceException( e.getMessage(),
702                                                       Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
703            }
704            finally
705            {
706                if ( session != null )
707                {
708                    session.closeQuietly();
709                }
710            }
711    
712            return artifactDownloadInfos;
713        }
714    
715        public ArtifactContent getArtifactContentText( String groupId, String artifactId, String version, String classifier,
716                                                       String type, String path, String repositoryId )
717            throws ArchivaRestServiceException
718        {
719            List<String> selectedRepos = getSelectedRepos( repositoryId );
720            try
721            {
722                for ( String repoId : selectedRepos )
723                {
724    
725                    ManagedRepositoryContent managedRepositoryContent =
726                        repositoryContentFactory.getManagedRepositoryContent( repoId );
727                    ArchivaArtifact archivaArtifact = new ArchivaArtifact( groupId, artifactId, version, classifier,
728                                                                           StringUtils.isEmpty( type ) ? "jar" : type,
729                                                                           repoId );
730                    File file = managedRepositoryContent.toFile( archivaArtifact );
731                    if ( !file.exists() )
732                    {
733                        log.debug( "file: {} not exists for repository: {} try next repository", file, repoId );
734                        continue;
735                    }
736                    if ( StringUtils.isNotBlank( path ) )
737                    {
738                        // zip entry of the path -> path must a real file entry of the archive
739                        JarFile jarFile = new JarFile( file );
740                        ZipEntry zipEntry = jarFile.getEntry( path );
741                        InputStream inputStream = jarFile.getInputStream( zipEntry );
742                        try
743                        {
744                            return new ArtifactContent( IOUtils.toString( inputStream ), repoId );
745                        }
746                        finally
747                        {
748                            closeQuietly( jarFile );
749                            IOUtils.closeQuietly( inputStream );
750                        }
751                    }
752                    return new ArtifactContent( FileUtils.readFileToString( file ), repoId );
753                }
754            }
755            catch ( IOException e )
756            {
757                log.error( e.getMessage(), e );
758                throw new ArchivaRestServiceException( e.getMessage(),
759                                                       Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
760            }
761            catch ( RepositoryNotFoundException e )
762            {
763                log.error( e.getMessage(), e );
764                throw new ArchivaRestServiceException( e.getMessage(),
765                                                       Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
766            }
767            catch ( RepositoryException e )
768            {
769                log.error( e.getMessage(), e );
770                throw new ArchivaRestServiceException( e.getMessage(),
771                                                       Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
772            }
773            log.debug( "artifact: {}:{}:{}:{}:{} not found", groupId, artifactId, version, classifier, type );
774            // 404 ?
775            return new ArtifactContent();
776        }
777    
778        public Boolean artifactAvailable( String groupId, String artifactId, String version, String classifier,
779                                          String repositoryId )
780            throws ArchivaRestServiceException
781        {
782            List<String> selectedRepos = getSelectedRepos( repositoryId );
783    
784            boolean snapshot = VersionUtil.isSnapshot( version );
785    
786            try
787            {
788                for ( String repoId : selectedRepos )
789                {
790    
791                    ManagedRepository managedRepository = managedRepositoryAdmin.getManagedRepository( repoId );
792    
793                    if ( ( snapshot && !managedRepository.isSnapshots() ) || ( !snapshot
794                        && managedRepository.isSnapshots() ) )
795                    {
796                        continue;
797                    }
798                    ManagedRepositoryContent managedRepositoryContent =
799                        repositoryContentFactory.getManagedRepositoryContent( repoId );
800                    // FIXME default to jar which can be wrong for war zip etc....
801                    ArchivaArtifact archivaArtifact = new ArchivaArtifact( groupId, artifactId, version,
802                                                                           StringUtils.isEmpty( classifier )
803                                                                               ? ""
804                                                                               : classifier, "jar", repoId );
805                    File file = managedRepositoryContent.toFile( archivaArtifact );
806    
807                    if ( file != null && file.exists() )
808                    {
809                        return true;
810                    }
811    
812                    // in case of SNAPSHOT we can have timestamped version locally !
813                    if ( StringUtils.endsWith( version, VersionUtil.SNAPSHOT ) )
814                    {
815                        File metadataFile = new File( file.getParent(), MetadataTools.MAVEN_METADATA );
816                        if ( metadataFile.exists() )
817                        {
818                            try
819                            {
820                                ArchivaRepositoryMetadata archivaRepositoryMetadata =
821                                    MavenMetadataReader.read( metadataFile );
822                                int buildNumber = archivaRepositoryMetadata.getSnapshotVersion().getBuildNumber();
823                                String timeStamp = archivaRepositoryMetadata.getSnapshotVersion().getTimestamp();
824                                // rebuild file name with timestamped version and build number
825                                String timeStampFileName = new StringBuilder( artifactId ).append( '-' ).append(
826                                    StringUtils.remove( version, "-" + VersionUtil.SNAPSHOT ) ).append( '-' ).append(
827                                    timeStamp ).append( '-' ).append( Integer.toString( buildNumber ) ).append(
828                                    ( StringUtils.isEmpty( classifier ) ? "" : "-" + classifier ) ).append(
829                                    ".jar" ).toString();
830                                /*File timeStampFile = new File( file.getParent(),
831                                                               artifactId + "-" + StringUtils.remove( version, "-"
832                                                                   + VersionUtil.SNAPSHOT ) + "-" + timeStamp + "-"
833                                                                   + Integer.toString( buildNumber )
834                                                                   + ( StringUtils.isEmpty( classifier )
835                                                                   ? ""
836                                                                   : "-" + classifier ) + ".jar" );*/
837                                File timeStampFile = new File( file.getParent(), timeStampFileName );
838                                log.debug( "try to find timestamped snapshot version file: {}", timeStampFile.getPath() );
839                                if ( timeStampFile.exists() )
840                                {
841                                    return true;
842                                }
843                            }
844                            catch ( XMLException e )
845                            {
846                                log.warn( "skip fail to find timestamped snapshot file: {}", e.getMessage() );
847                            }
848                        }
849                    }
850    
851                    String path = managedRepositoryContent.toPath( archivaArtifact );
852    
853                    file = connectors.fetchFromProxies( managedRepositoryContent, path );
854    
855                    if ( file != null && file.exists() )
856                    {
857                        // download pom now
858                        String pomPath = StringUtils.substringBeforeLast( path, ".jar" ) + ".pom";
859                        connectors.fetchFromProxies( managedRepositoryContent, pomPath );
860                        return true;
861                    }
862                }
863            }
864            catch ( RepositoryAdminException e )
865            {
866                log.error( e.getMessage(), e );
867                throw new ArchivaRestServiceException( e.getMessage(),
868                                                       Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
869            }
870            catch ( RepositoryException e )
871            {
872                log.error( e.getMessage(), e );
873                throw new ArchivaRestServiceException( e.getMessage(),
874                                                       Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
875            }
876    
877            return false;
878        }
879    
880        public Boolean artifactAvailable( String groupId, String artifactId, String version, String repositoryId )
881            throws ArchivaRestServiceException
882        {
883            return artifactAvailable( groupId, artifactId, version, null, repositoryId );
884        }
885    
886        public List<Artifact> getArtifacts( String repositoryId )
887            throws ArchivaRestServiceException
888        {
889            RepositorySession repositorySession = repositorySessionFactory.createSession();
890            try
891            {
892                List<ArtifactMetadata> artifactMetadatas = repositorySession.getRepository().getArtifacts( repositoryId );
893                return buildArtifacts( artifactMetadatas, repositoryId );
894            }
895            catch ( MetadataRepositoryException e )
896            {
897                throw new ArchivaRestServiceException( e.getMessage(), e );
898            }
899            finally
900            {
901                repositorySession.close();
902            }
903        }
904    
905        public Boolean importMetadata( MetadataAddRequest metadataAddRequest, String repositoryId )
906            throws ArchivaRestServiceException
907        {
908            boolean result = true;
909            for ( Map.Entry<String, String> metadata : metadataAddRequest.getMetadatas().entrySet() )
910            {
911                result = addMetadata( metadataAddRequest.getGroupId(), metadataAddRequest.getArtifactId(),
912                                      metadataAddRequest.getVersion(), metadata.getKey(), metadata.getValue(),
913                                      repositoryId );
914                if ( !result )
915                {
916                    break;
917                }
918            }
919            return result;
920        }
921    
922        //---------------------------
923        // internals
924        //---------------------------
925    
926        private void closeQuietly( JarFile jarFile )
927        {
928            if ( jarFile != null )
929            {
930                try
931                {
932                    jarFile.close();
933                }
934                catch ( IOException e )
935                {
936                    log.warn( "ignore error closing jarFile {}", jarFile.getName() );
937                }
938            }
939        }
940    
941        protected List<ArtifactContentEntry> readFileEntries( File file, String filterPath, String repoId )
942            throws IOException
943        {
944            Map<String, ArtifactContentEntry> artifactContentEntryMap = new HashMap<String, ArtifactContentEntry>();
945            int filterDepth = StringUtils.countMatches( filterPath, "/" );
946            /*if ( filterDepth == 0 )
947            {
948                filterDepth = 1;
949            }*/
950            JarFile jarFile = new JarFile( file );
951            try
952            {
953                Enumeration<JarEntry> jarEntryEnumeration = jarFile.entries();
954                while ( jarEntryEnumeration.hasMoreElements() )
955                {
956                    JarEntry currentEntry = jarEntryEnumeration.nextElement();
957                    String cleanedEntryName =
958                        StringUtils.endsWith( currentEntry.getName(), "/" ) ? StringUtils.substringBeforeLast(
959                            currentEntry.getName(), "/" ) : currentEntry.getName();
960                    String entryRootPath = getRootPath( cleanedEntryName );
961                    int depth = StringUtils.countMatches( cleanedEntryName, "/" );
962                    if ( StringUtils.isEmpty( filterPath ) && !artifactContentEntryMap.containsKey( entryRootPath )
963                        && depth == filterDepth )
964                    {
965    
966                        artifactContentEntryMap.put( entryRootPath,
967                                                     new ArtifactContentEntry( entryRootPath, !currentEntry.isDirectory(),
968                                                                               depth, repoId ) );
969                    }
970                    else
971                    {
972                        if ( StringUtils.startsWith( cleanedEntryName, filterPath ) && ( depth == filterDepth || (
973                            !currentEntry.isDirectory() && depth == filterDepth ) ) )
974                        {
975                            artifactContentEntryMap.put( cleanedEntryName, new ArtifactContentEntry( cleanedEntryName,
976                                                                                                     !currentEntry.isDirectory(),
977                                                                                                     depth, repoId ) );
978                        }
979                    }
980                }
981    
982                if ( StringUtils.isNotEmpty( filterPath ) )
983                {
984                    Map<String, ArtifactContentEntry> filteredArtifactContentEntryMap =
985                        new HashMap<String, ArtifactContentEntry>();
986    
987                    for ( Map.Entry<String, ArtifactContentEntry> entry : artifactContentEntryMap.entrySet() )
988                    {
989                        filteredArtifactContentEntryMap.put( entry.getKey(), entry.getValue() );
990                    }
991    
992                    List<ArtifactContentEntry> sorted = getSmallerDepthEntries( filteredArtifactContentEntryMap );
993                    if ( sorted == null )
994                    {
995                        return Collections.emptyList();
996                    }
997                    Collections.sort( sorted, ArtifactContentEntryComparator.INSTANCE );
998                    return sorted;
999                }
1000            }
1001            finally
1002            {
1003                if ( jarFile != null )
1004                {
1005                    jarFile.close();
1006                }
1007            }
1008            List<ArtifactContentEntry> sorted = new ArrayList<ArtifactContentEntry>( artifactContentEntryMap.values() );
1009            Collections.sort( sorted, ArtifactContentEntryComparator.INSTANCE );
1010            return sorted;
1011        }
1012    
1013        private List<ArtifactContentEntry> getSmallerDepthEntries( Map<String, ArtifactContentEntry> entries )
1014        {
1015            int smallestDepth = Integer.MAX_VALUE;
1016            Map<Integer, List<ArtifactContentEntry>> perDepthList = new HashMap<Integer, List<ArtifactContentEntry>>();
1017            for ( Map.Entry<String, ArtifactContentEntry> entry : entries.entrySet() )
1018            {
1019    
1020                ArtifactContentEntry current = entry.getValue();
1021    
1022                if ( current.getDepth() < smallestDepth )
1023                {
1024                    smallestDepth = current.getDepth();
1025                }
1026    
1027                List<ArtifactContentEntry> currentList = perDepthList.get( current.getDepth() );
1028    
1029                if ( currentList == null )
1030                {
1031                    currentList = new ArrayList<ArtifactContentEntry>();
1032                    currentList.add( current );
1033                    perDepthList.put( current.getDepth(), currentList );
1034                }
1035                else
1036                {
1037                    currentList.add( current );
1038                }
1039    
1040            }
1041    
1042            return perDepthList.get( smallestDepth );
1043        }
1044    
1045        /**
1046         * @param path
1047         * @return org/apache -> org , org -> org
1048         */
1049        private String getRootPath( String path )
1050        {
1051            if ( StringUtils.contains( path, '/' ) )
1052            {
1053                return StringUtils.substringBefore( path, "/" );
1054            }
1055            return path;
1056        }
1057    
1058        private List<String> getSelectedRepos( String repositoryId )
1059            throws ArchivaRestServiceException
1060        {
1061    
1062            List<String> selectedRepos = getObservableRepos();
1063    
1064            if ( CollectionUtils.isEmpty( selectedRepos ) )
1065            {
1066                return Collections.emptyList();
1067            }
1068    
1069            if ( StringUtils.isNotEmpty( repositoryId ) )
1070            {
1071                // check user has karma on the repository
1072                if ( !selectedRepos.contains( repositoryId ) )
1073                {
1074                    throw new ArchivaRestServiceException( "browse.root.groups.repositoy.denied",
1075                                                           Response.Status.FORBIDDEN.getStatusCode(), null );
1076                }
1077                selectedRepos = Collections.singletonList( repositoryId );
1078            }
1079            return selectedRepos;
1080        }
1081    
1082    
1083        private String collapseNamespaces( RepositorySession repositorySession, MetadataResolver metadataResolver,
1084                                           Collection<String> repoIds, String n )
1085            throws MetadataResolutionException
1086        {
1087            Set<String> subNamespaces = new LinkedHashSet<String>();
1088            for ( String repoId : repoIds )
1089            {
1090                subNamespaces.addAll( metadataResolver.resolveNamespaces( repositorySession, repoId, n ) );
1091            }
1092            if ( subNamespaces.size() != 1 )
1093            {
1094                log.debug( "{} is not collapsible as it has sub-namespaces: {}", n, subNamespaces );
1095                return n;
1096            }
1097            else
1098            {
1099                for ( String repoId : repoIds )
1100                {
1101                    Collection<String> projects = metadataResolver.resolveProjects( repositorySession, repoId, n );
1102                    if ( projects != null && !projects.isEmpty() )
1103                    {
1104                        log.debug( "{} is not collapsible as it has projects", n );
1105                        return n;
1106                    }
1107                }
1108                return collapseNamespaces( repositorySession, metadataResolver, repoIds,
1109                                           n + "." + subNamespaces.iterator().next() );
1110            }
1111        }
1112    }