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