1 package org.apache.archiva.web.api;
3 * Licensed to the Apache Software Foundation (ASF) under one
4 * or more contributor license agreements. See the NOTICE file
5 * distributed with this work for additional information
6 * regarding copyright ownership. The ASF licenses this file
7 * to you under the Apache License, Version 2.0 (the
8 * "License"); you may not use this file except in compliance
9 * with the License. You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing,
14 * software distributed under the License is distributed on an
15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 * KIND, either express or implied. See the License for the
17 * specific language governing permissions and limitations
21 import com.google.common.base.Predicate;
22 import com.google.common.collect.Iterables;
23 import org.apache.archiva.admin.model.RepositoryAdminException;
24 import org.apache.archiva.admin.model.admin.ArchivaAdministration;
25 import org.apache.archiva.admin.model.beans.ManagedRepository;
26 import org.apache.archiva.admin.model.managed.ManagedRepositoryAdmin;
27 import org.apache.archiva.checksum.ChecksumAlgorithm;
28 import org.apache.archiva.checksum.ChecksummedFile;
29 import org.apache.archiva.common.utils.VersionComparator;
30 import org.apache.archiva.common.utils.VersionUtil;
31 import org.apache.archiva.maven2.metadata.MavenMetadataReader;
32 import org.apache.archiva.metadata.model.facets.AuditEvent;
33 import org.apache.archiva.model.ArchivaRepositoryMetadata;
34 import org.apache.archiva.model.ArtifactReference;
35 import org.apache.archiva.model.SnapshotVersion;
36 import org.apache.archiva.redback.components.taskqueue.TaskQueueException;
37 import org.apache.archiva.repository.ManagedRepositoryContent;
38 import org.apache.archiva.repository.RepositoryContentFactory;
39 import org.apache.archiva.repository.RepositoryException;
40 import org.apache.archiva.repository.RepositoryNotFoundException;
41 import org.apache.archiva.repository.metadata.MetadataTools;
42 import org.apache.archiva.repository.metadata.RepositoryMetadataException;
43 import org.apache.archiva.repository.metadata.RepositoryMetadataWriter;
44 import org.apache.archiva.rest.api.services.ArchivaRestServiceException;
45 import org.apache.archiva.rest.services.AbstractRestService;
46 import org.apache.archiva.scheduler.ArchivaTaskScheduler;
47 import org.apache.archiva.scheduler.repository.model.RepositoryTask;
48 import org.apache.archiva.web.model.FileMetadata;
49 import org.apache.archiva.xml.XMLException;
50 import org.apache.commons.io.FilenameUtils;
51 import org.apache.commons.io.IOUtils;
52 import org.apache.commons.lang.BooleanUtils;
53 import org.apache.commons.lang.StringUtils;
54 import org.apache.commons.lang.SystemUtils;
55 import org.apache.cxf.jaxrs.ext.multipart.Attachment;
56 import org.apache.cxf.jaxrs.ext.multipart.MultipartBody;
57 import org.apache.maven.model.Model;
58 import org.apache.maven.model.io.xpp3.MavenXpp3Writer;
59 import org.slf4j.Logger;
60 import org.slf4j.LoggerFactory;
61 import org.springframework.stereotype.Service;
63 import javax.inject.Inject;
64 import javax.inject.Named;
65 import javax.servlet.http.HttpServletRequest;
66 import javax.ws.rs.core.Context;
67 import javax.ws.rs.core.Response;
68 import java.io.FileOutputStream;
69 import java.io.FileWriter;
70 import java.io.IOException;
71 import java.nio.file.Files;
72 import java.nio.file.Path;
73 import java.nio.file.Paths;
74 import java.nio.file.StandardCopyOption;
75 import java.text.DateFormat;
76 import java.text.SimpleDateFormat;
77 import java.util.ArrayList;
78 import java.util.Arrays;
79 import java.util.Calendar;
80 import java.util.Collections;
81 import java.util.Date;
82 import java.util.Iterator;
83 import java.util.List;
84 import java.util.TimeZone;
85 import java.util.concurrent.CopyOnWriteArrayList;
88 * @author Olivier Lamy
90 @Service("fileUploadService#rest")
91 public class DefaultFileUploadService
92 extends AbstractRestService
93 implements FileUploadService
95 private Logger log = LoggerFactory.getLogger( getClass() );
98 private HttpServletRequest httpServletRequest;
101 private ManagedRepositoryAdmin managedRepositoryAdmin;
104 private RepositoryContentFactory repositoryFactory;
107 private ArchivaAdministration archivaAdministration;
109 private List<ChecksumAlgorithm> algorithms = Arrays.asList( ChecksumAlgorithm.SHA1, ChecksumAlgorithm.MD5 );
112 @Named(value = "archivaTaskScheduler#repository")
113 private ArchivaTaskScheduler<RepositoryTask> scheduler;
115 private String getStringValue( MultipartBody multipartBody, String attachmentId )
118 Attachment attachment = multipartBody.getAttachment( attachmentId );
119 return attachment == null ? "" : IOUtils.toString( attachment.getDataHandler().getInputStream(), "UTF-8" );
123 public FileMetadata post( MultipartBody multipartBody )
124 throws ArchivaRestServiceException
130 String classifier = getStringValue( multipartBody, "classifier" );
131 String packaging = getStringValue( multipartBody, "packaging" );
132 // skygo: http header form pomFile was once sending 1 for true and void for false
133 // leading to permanent false value for pomFile if using toBoolean(); use , "1", ""
134 boolean pomFile = BooleanUtils.toBoolean( getStringValue( multipartBody, "pomFile" ) );
136 Attachment file = multipartBody.getAttachment( "files[]" );
138 //Content-Disposition: form-data; name="files[]"; filename="org.apache.karaf.features.command-2.2.2.jar"
139 String fileName = file.getContentDisposition().getParameter( "filename" );
141 Path tmpFile = Files.createTempFile( "upload-artifact", ".tmp" );
142 tmpFile.toFile().deleteOnExit();
143 IOUtils.copy( file.getDataHandler().getInputStream(), new FileOutputStream( tmpFile.toFile() ) );
144 FileMetadata fileMetadata = new FileMetadata( fileName, Files.size(tmpFile), "theurl" );
145 fileMetadata.setServerFileName( tmpFile.toString() );
146 fileMetadata.setClassifier( classifier );
147 fileMetadata.setDeleteUrl( tmpFile.getFileName().toString() );
148 fileMetadata.setPomFile( pomFile );
149 fileMetadata.setPackaging( packaging );
151 log.info( "uploading file: {}", fileMetadata );
153 List<FileMetadata> fileMetadatas = getSessionFilesList();
155 fileMetadatas.add( fileMetadata );
159 catch ( IOException e )
161 throw new ArchivaRestServiceException( e.getMessage(),
162 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
168 * FIXME must be per session synchronized not globally
172 protected synchronized List<FileMetadata> getSessionFilesList()
174 @SuppressWarnings("unchecked") List<FileMetadata> fileMetadatas = (List<FileMetadata>) httpServletRequest.getSession().getAttribute( FILES_SESSION_KEY );
175 if ( fileMetadatas == null )
177 fileMetadatas = new CopyOnWriteArrayList<>();
178 httpServletRequest.getSession().setAttribute( FILES_SESSION_KEY, fileMetadatas );
180 return fileMetadatas;
184 public Boolean deleteFile( String fileName )
185 throws ArchivaRestServiceException
187 Path file = SystemUtils.getJavaIoTmpDir().toPath().resolve( fileName );
188 log.debug( "delete file:{},exists:{}", file, Files.exists(file) );
189 boolean removed = getSessionFileMetadatas().remove( new FileMetadata( fileName ) );
190 // try with full name as ui only know the file name
193 /* unused */ getSessionFileMetadatas().remove( new FileMetadata( file.toString() ) );
197 Files.deleteIfExists( file );
199 catch ( IOException e )
201 log.error("Could not delete file {}: {}", file, e.getMessage(), e);
203 return Boolean.FALSE;
207 public Boolean clearUploadedFiles()
208 throws ArchivaRestServiceException
210 List<FileMetadata> fileMetadatas = new ArrayList<>( getSessionFileMetadatas() );
211 for ( FileMetadata fileMetadata : fileMetadatas )
213 deleteFile( Paths.get( fileMetadata.getServerFileName() ).toString() );
215 getSessionFileMetadatas().clear();
220 public List<FileMetadata> getSessionFileMetadatas()
221 throws ArchivaRestServiceException
223 @SuppressWarnings("unchecked") List<FileMetadata> fileMetadatas =
224 (List<FileMetadata>) httpServletRequest.getSession().getAttribute( FILES_SESSION_KEY );
226 return fileMetadatas == null ? Collections.<FileMetadata>emptyList() : fileMetadatas;
230 public Boolean save( String repositoryId, String groupId, String artifactId, String version, String packaging,
231 boolean generatePom )
232 throws ArchivaRestServiceException
234 repositoryId = StringUtils.trim( repositoryId );
235 groupId = StringUtils.trim( groupId );
236 artifactId = StringUtils.trim( artifactId );
237 version = StringUtils.trim( version );
238 packaging = StringUtils.trim( packaging );
240 List<FileMetadata> fileMetadatas = getSessionFilesList();
241 if ( fileMetadatas == null || fileMetadatas.isEmpty() )
243 return Boolean.FALSE;
248 ManagedRepository managedRepository = managedRepositoryAdmin.getManagedRepository( repositoryId );
250 if ( managedRepository == null )
253 throw new ArchivaRestServiceException( "Cannot find managed repository with id " + repositoryId,
254 Response.Status.BAD_REQUEST.getStatusCode(), null );
257 if ( VersionUtil.isSnapshot( version ) && !managedRepository.isSnapshots() )
260 throw new ArchivaRestServiceException(
261 "Managed repository with id " + repositoryId + " do not accept snapshots",
262 Response.Status.BAD_REQUEST.getStatusCode(), null );
265 catch ( RepositoryAdminException e )
267 throw new ArchivaRestServiceException( e.getMessage(),
268 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
271 // get from the session file with groupId/artifactId
273 Iterable<FileMetadata> filesToAdd = Iterables.filter( fileMetadatas, new Predicate<FileMetadata>()
275 public boolean apply( FileMetadata fileMetadata )
277 return fileMetadata != null && !fileMetadata.isPomFile();
280 Iterator<FileMetadata> iterator = filesToAdd.iterator();
281 boolean pomGenerated = false;
282 while ( iterator.hasNext() )
284 FileMetadata fileMetadata = iterator.next();
285 log.debug( "fileToAdd: {}", fileMetadata );
286 saveFile( repositoryId, fileMetadata, generatePom && !pomGenerated, groupId, artifactId, version,
289 deleteFile( fileMetadata.getServerFileName() );
292 filesToAdd = Iterables.filter( fileMetadatas, new Predicate<FileMetadata>()
295 public boolean apply( FileMetadata fileMetadata )
297 return fileMetadata != null && fileMetadata.isPomFile();
301 iterator = filesToAdd.iterator();
302 while ( iterator.hasNext() )
304 FileMetadata fileMetadata = iterator.next();
305 log.debug( "fileToAdd: {}", fileMetadata );
306 savePomFile( repositoryId, fileMetadata, groupId, artifactId, version, packaging );
307 deleteFile( fileMetadata.getServerFileName() );
313 protected void savePomFile( String repositoryId, FileMetadata fileMetadata, String groupId, String artifactId,
314 String version, String packaging )
315 throws ArchivaRestServiceException
320 boolean fixChecksums =
321 !( archivaAdministration.getKnownContentConsumers().contains( "create-missing-checksums" ) );
323 ManagedRepository repoConfig = managedRepositoryAdmin.getManagedRepository( repositoryId );
325 ArtifactReference artifactReference = new ArtifactReference();
326 artifactReference.setArtifactId( artifactId );
327 artifactReference.setGroupId( groupId );
328 artifactReference.setVersion( version );
329 artifactReference.setClassifier( fileMetadata.getClassifier() );
330 artifactReference.setType( packaging );
332 ManagedRepositoryContent repository = repositoryFactory.getManagedRepositoryContent( repositoryId );
334 String artifactPath = repository.toPath( artifactReference );
336 int lastIndex = artifactPath.lastIndexOf( '/' );
338 String path = artifactPath.substring( 0, lastIndex );
339 Path targetPath = Paths.get( repoConfig.getLocation(), path );
341 String pomFilename = artifactPath.substring( lastIndex + 1 );
342 if ( StringUtils.isNotEmpty( fileMetadata.getClassifier() ) )
344 pomFilename = StringUtils.remove( pomFilename, "-" + fileMetadata.getClassifier() );
346 pomFilename = FilenameUtils.removeExtension( pomFilename ) + ".pom";
348 copyFile( Paths.get( fileMetadata.getServerFileName() ), targetPath, pomFilename, fixChecksums );
349 triggerAuditEvent( repoConfig.getId(), path + "/" + pomFilename, AuditEvent.UPLOAD_FILE );
350 queueRepositoryTask( repoConfig.getId(), targetPath.resolve(pomFilename ) );
352 catch ( IOException ie )
354 throw new ArchivaRestServiceException( "Error encountered while uploading pom file: " + ie.getMessage(),
355 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), ie );
357 catch ( RepositoryException rep )
359 throw new ArchivaRestServiceException( "Repository exception: " + rep.getMessage(),
360 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), rep );
362 catch ( RepositoryAdminException e )
364 throw new ArchivaRestServiceException( "RepositoryAdmin exception: " + e.getMessage(),
365 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
369 protected void saveFile( String repositoryId, FileMetadata fileMetadata, boolean generatePom, String groupId,
370 String artifactId, String version, String packaging )
371 throws ArchivaRestServiceException
376 ManagedRepository repoConfig = managedRepositoryAdmin.getManagedRepository( repositoryId );
378 ArtifactReference artifactReference = new ArtifactReference();
379 artifactReference.setArtifactId( artifactId );
380 artifactReference.setGroupId( groupId );
381 artifactReference.setVersion( version );
382 artifactReference.setClassifier( fileMetadata.getClassifier() );
383 artifactReference.setType(
384 StringUtils.isEmpty( fileMetadata.getPackaging() ) ? packaging : fileMetadata.getPackaging() );
386 ManagedRepositoryContent repository = repositoryFactory.getManagedRepositoryContent( repositoryId );
388 String artifactPath = repository.toPath( artifactReference );
390 int lastIndex = artifactPath.lastIndexOf( '/' );
392 String path = artifactPath.substring( 0, lastIndex );
393 Path targetPath = Paths.get( repoConfig.getLocation(), path );
395 log.debug( "artifactPath: {} found targetPath: {}", artifactPath, targetPath );
397 Date lastUpdatedTimestamp = Calendar.getInstance().getTime();
398 int newBuildNumber = -1;
399 String timestamp = null;
401 Path versionMetadataFile = targetPath.resolve( MetadataTools.MAVEN_METADATA );
402 ArchivaRepositoryMetadata versionMetadata = getMetadata( versionMetadataFile );
404 if ( VersionUtil.isSnapshot( version ) )
406 TimeZone timezone = TimeZone.getTimeZone( "UTC" );
407 DateFormat fmt = new SimpleDateFormat( "yyyyMMdd.HHmmss" );
408 fmt.setTimeZone( timezone );
409 timestamp = fmt.format( lastUpdatedTimestamp );
410 if ( versionMetadata.getSnapshotVersion() != null )
412 newBuildNumber = versionMetadata.getSnapshotVersion().getBuildNumber() + 1;
420 if ( !Files.exists(targetPath) )
422 Files.createDirectories( targetPath );
425 String filename = artifactPath.substring( lastIndex + 1 );
426 if ( VersionUtil.isSnapshot( version ) )
428 filename = filename.replaceAll( VersionUtil.SNAPSHOT, timestamp + "-" + newBuildNumber );
431 boolean fixChecksums =
432 !( archivaAdministration.getKnownContentConsumers().contains( "create-missing-checksums" ) );
436 Path targetFile = targetPath.resolve( filename );
437 if ( Files.exists(targetFile) && !VersionUtil.isSnapshot( version ) && repoConfig.isBlockRedeployments() )
439 throw new ArchivaRestServiceException(
440 "Overwriting released artifacts in repository '" + repoConfig.getId() + "' is not allowed.",
441 Response.Status.BAD_REQUEST.getStatusCode(), null );
445 copyFile( Paths.get( fileMetadata.getServerFileName() ), targetPath, filename, fixChecksums );
446 triggerAuditEvent( repository.getId(), path + "/" + filename, AuditEvent.UPLOAD_FILE );
447 queueRepositoryTask( repository.getId(), targetFile );
450 catch ( IOException ie )
452 log.error( "IOException copying file: {}", ie.getMessage(), ie );
453 throw new ArchivaRestServiceException(
454 "Overwriting released artifacts in repository '" + repoConfig.getId() + "' is not allowed.",
455 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), ie );
460 String pomFilename = filename;
461 if ( StringUtils.isNotEmpty( fileMetadata.getClassifier() ) )
463 pomFilename = StringUtils.remove( pomFilename, "-" + fileMetadata.getClassifier() );
465 pomFilename = FilenameUtils.removeExtension( pomFilename ) + ".pom";
469 Path generatedPomFile =
470 createPom( targetPath, pomFilename, fileMetadata, groupId, artifactId, version, packaging );
471 triggerAuditEvent( repoConfig.getId(), path + "/" + pomFilename, AuditEvent.UPLOAD_FILE );
474 fixChecksums( generatedPomFile );
476 queueRepositoryTask( repoConfig.getId(), generatedPomFile );
478 catch ( IOException ie )
480 throw new ArchivaRestServiceException(
481 "Error encountered while writing pom file: " + ie.getMessage(),
482 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), ie );
486 // explicitly update only if metadata-updater consumer is not enabled!
487 if ( !archivaAdministration.getKnownContentConsumers().contains( "metadata-updater" ) )
489 updateProjectMetadata( targetPath.toAbsolutePath().toString(), lastUpdatedTimestamp, timestamp, newBuildNumber,
490 fixChecksums, fileMetadata, groupId, artifactId, version, packaging );
492 if ( VersionUtil.isSnapshot( version ) )
494 updateVersionMetadata( versionMetadata, versionMetadataFile, lastUpdatedTimestamp, timestamp,
495 newBuildNumber, fixChecksums, fileMetadata, groupId, artifactId, version,
500 catch ( RepositoryNotFoundException re )
502 throw new ArchivaRestServiceException( "Target repository cannot be found: " + re.getMessage(),
503 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), re );
505 catch ( RepositoryException rep )
507 throw new ArchivaRestServiceException( "Repository exception: " + rep.getMessage(),
508 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), rep );
510 catch ( RepositoryAdminException e )
512 throw new ArchivaRestServiceException( "RepositoryAdmin exception: " + e.getMessage(),
513 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
515 catch ( IOException e )
517 throw new ArchivaRestServiceException("Repository exception "+ e.getMessage(),
518 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e);
522 private ArchivaRepositoryMetadata getMetadata( Path metadataFile )
523 throws RepositoryMetadataException
525 ArchivaRepositoryMetadata metadata = new ArchivaRepositoryMetadata();
526 if ( Files.exists(metadataFile) )
530 metadata = MavenMetadataReader.read( metadataFile );
532 catch ( XMLException e )
534 throw new RepositoryMetadataException( e.getMessage(), e );
540 private Path createPom( Path targetPath, String filename, FileMetadata fileMetadata, String groupId,
541 String artifactId, String version, String packaging )
544 Model projectModel = new Model();
545 projectModel.setModelVersion( "4.0.0" );
546 projectModel.setGroupId( groupId );
547 projectModel.setArtifactId( artifactId );
548 projectModel.setVersion( version );
549 projectModel.setPackaging( packaging );
551 Path pomFile = targetPath.resolve( filename );
552 MavenXpp3Writer writer = new MavenXpp3Writer();
554 try (FileWriter w = new FileWriter( pomFile.toFile() ))
556 writer.write( w, projectModel );
562 private void fixChecksums( Path file )
564 ChecksummedFile checksum = new ChecksummedFile( file );
565 checksum.fixChecksums( algorithms );
568 private void queueRepositoryTask( String repositoryId, Path localFile )
570 RepositoryTask task = new RepositoryTask();
571 task.setRepositoryId( repositoryId );
572 task.setResourceFile( localFile );
573 task.setUpdateRelatedArtifacts( true );
574 task.setScanAll( false );
578 scheduler.queueTask( task );
580 catch ( TaskQueueException e )
582 log.error( "Unable to queue repository task to execute consumers on resource file ['{}"
583 + "'].", localFile.getFileName() );
587 private void copyFile( Path sourceFile, Path targetPath, String targetFilename, boolean fixChecksums )
591 Files.copy( sourceFile, targetPath.resolve( targetFilename ), StandardCopyOption.REPLACE_EXISTING,
592 StandardCopyOption.COPY_ATTRIBUTES );
596 fixChecksums( targetPath.resolve( targetFilename ) );
601 * Update artifact level metadata. If it does not exist, create the metadata and fix checksums if necessary.
603 private void updateProjectMetadata( String targetPath, Date lastUpdatedTimestamp, String timestamp, int buildNumber,
604 boolean fixChecksums, FileMetadata fileMetadata, String groupId,
605 String artifactId, String version, String packaging )
606 throws RepositoryMetadataException
608 List<String> availableVersions = new ArrayList<>();
609 String latestVersion = version;
611 Path projectDir = Paths.get(targetPath).getParent();
612 Path projectMetadataFile = projectDir.resolve( MetadataTools.MAVEN_METADATA );
614 ArchivaRepositoryMetadata projectMetadata = getMetadata( projectMetadataFile );
616 if ( Files.exists(projectMetadataFile) )
618 availableVersions = projectMetadata.getAvailableVersions();
620 Collections.sort( availableVersions, VersionComparator.getInstance() );
622 if ( !availableVersions.contains( version ) )
624 availableVersions.add( version );
627 latestVersion = availableVersions.get( availableVersions.size() - 1 );
631 availableVersions.add( version );
633 projectMetadata.setGroupId( groupId );
634 projectMetadata.setArtifactId( artifactId );
637 if ( projectMetadata.getGroupId() == null )
639 projectMetadata.setGroupId( groupId );
642 if ( projectMetadata.getArtifactId() == null )
644 projectMetadata.setArtifactId( artifactId );
647 projectMetadata.setLatestVersion( latestVersion );
648 projectMetadata.setLastUpdatedTimestamp( lastUpdatedTimestamp );
649 projectMetadata.setAvailableVersions( availableVersions );
651 if ( !VersionUtil.isSnapshot( version ) )
653 projectMetadata.setReleasedVersion( latestVersion );
656 RepositoryMetadataWriter.write( projectMetadata, projectMetadataFile );
660 fixChecksums( projectMetadataFile );
665 * Update version level metadata for snapshot artifacts. If it does not exist, create the metadata and fix checksums
668 private void updateVersionMetadata( ArchivaRepositoryMetadata metadata, Path metadataFile,
669 Date lastUpdatedTimestamp, String timestamp, int buildNumber,
670 boolean fixChecksums, FileMetadata fileMetadata, String groupId,
671 String artifactId, String version, String packaging )
672 throws RepositoryMetadataException
674 if ( !Files.exists(metadataFile) )
676 metadata.setGroupId( groupId );
677 metadata.setArtifactId( artifactId );
678 metadata.setVersion( version );
681 if ( metadata.getSnapshotVersion() == null )
683 metadata.setSnapshotVersion( new SnapshotVersion() );
686 metadata.getSnapshotVersion().setBuildNumber( buildNumber );
687 metadata.getSnapshotVersion().setTimestamp( timestamp );
688 metadata.setLastUpdatedTimestamp( lastUpdatedTimestamp );
690 RepositoryMetadataWriter.write( metadata, metadataFile );
694 fixChecksums( metadataFile );