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