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 scheduler;
115 private String getStringValue( MultipartBody multipartBody, String attachmentId )
118 Attachment attachment = multipartBody.getAttachment( attachmentId );
119 return attachment == null ? "" : IOUtils.toString( attachment.getDataHandler().getInputStream() );
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 List<FileMetadata> fileMetadatas =
175 (List<FileMetadata>) httpServletRequest.getSession().getAttribute( FILES_SESSION_KEY );
176 if ( fileMetadatas == null )
178 fileMetadatas = new CopyOnWriteArrayList<>();
179 httpServletRequest.getSession().setAttribute( FILES_SESSION_KEY, fileMetadatas );
181 return fileMetadatas;
185 public Boolean deleteFile( String fileName )
186 throws ArchivaRestServiceException
188 Path file = SystemUtils.getJavaIoTmpDir().toPath().resolve( fileName );
189 log.debug( "delete file:{},exists:{}", file, Files.exists(file) );
190 boolean removed = getSessionFileMetadatas().remove( new FileMetadata( fileName ) );
191 // try with full name as ui only know the file name
194 /* unused */ getSessionFileMetadatas().remove( new FileMetadata( file.toString() ) );
198 Files.deleteIfExists( file );
200 catch ( IOException e )
202 log.error("Could not delete file {}: {}", file, e.getMessage(), e);
204 return Boolean.FALSE;
208 public Boolean clearUploadedFiles()
209 throws ArchivaRestServiceException
211 List<FileMetadata> fileMetadatas = new ArrayList( getSessionFileMetadatas() );
212 for ( FileMetadata fileMetadata : fileMetadatas )
214 deleteFile( Paths.get( fileMetadata.getServerFileName() ).toString() );
216 getSessionFileMetadatas().clear();
221 public List<FileMetadata> getSessionFileMetadatas()
222 throws ArchivaRestServiceException
224 List<FileMetadata> fileMetadatas =
225 (List<FileMetadata>) httpServletRequest.getSession().getAttribute( FILES_SESSION_KEY );
227 return fileMetadatas == null ? Collections.<FileMetadata>emptyList() : fileMetadatas;
231 public Boolean save( String repositoryId, String groupId, String artifactId, String version, String packaging,
232 boolean generatePom )
233 throws ArchivaRestServiceException
235 repositoryId = StringUtils.trim( repositoryId );
236 groupId = StringUtils.trim( groupId );
237 artifactId = StringUtils.trim( artifactId );
238 version = StringUtils.trim( version );
239 packaging = StringUtils.trim( packaging );
241 List<FileMetadata> fileMetadatas = getSessionFilesList();
242 if ( fileMetadatas == null || fileMetadatas.isEmpty() )
244 return Boolean.FALSE;
249 ManagedRepository managedRepository = managedRepositoryAdmin.getManagedRepository( repositoryId );
251 if ( managedRepository == null )
254 throw new ArchivaRestServiceException( "Cannot find managed repository with id " + repositoryId,
255 Response.Status.BAD_REQUEST.getStatusCode(), null );
258 if ( VersionUtil.isSnapshot( version ) && !managedRepository.isSnapshots() )
261 throw new ArchivaRestServiceException(
262 "Managed repository with id " + repositoryId + " do not accept snapshots",
263 Response.Status.BAD_REQUEST.getStatusCode(), null );
266 catch ( RepositoryAdminException e )
268 throw new ArchivaRestServiceException( e.getMessage(),
269 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
272 // get from the session file with groupId/artifactId
274 Iterable<FileMetadata> filesToAdd = Iterables.filter( fileMetadatas, new Predicate<FileMetadata>()
276 public boolean apply( FileMetadata fileMetadata )
278 return fileMetadata != null && !fileMetadata.isPomFile();
281 Iterator<FileMetadata> iterator = filesToAdd.iterator();
282 boolean pomGenerated = false;
283 while ( iterator.hasNext() )
285 FileMetadata fileMetadata = iterator.next();
286 log.debug( "fileToAdd: {}", fileMetadata );
287 saveFile( repositoryId, fileMetadata, generatePom && !pomGenerated, groupId, artifactId, version,
290 deleteFile( fileMetadata.getServerFileName() );
293 filesToAdd = Iterables.filter( fileMetadatas, new Predicate<FileMetadata>()
296 public boolean apply( FileMetadata fileMetadata )
298 return fileMetadata != null && fileMetadata.isPomFile();
302 iterator = filesToAdd.iterator();
303 while ( iterator.hasNext() )
305 FileMetadata fileMetadata = iterator.next();
306 log.debug( "fileToAdd: {}", fileMetadata );
307 savePomFile( repositoryId, fileMetadata, groupId, artifactId, version, packaging );
308 deleteFile( fileMetadata.getServerFileName() );
314 protected void savePomFile( String repositoryId, FileMetadata fileMetadata, String groupId, String artifactId,
315 String version, String packaging )
316 throws ArchivaRestServiceException
321 boolean fixChecksums =
322 !( archivaAdministration.getKnownContentConsumers().contains( "create-missing-checksums" ) );
324 ManagedRepository repoConfig = managedRepositoryAdmin.getManagedRepository( repositoryId );
326 ArtifactReference artifactReference = new ArtifactReference();
327 artifactReference.setArtifactId( artifactId );
328 artifactReference.setGroupId( groupId );
329 artifactReference.setVersion( version );
330 artifactReference.setClassifier( fileMetadata.getClassifier() );
331 artifactReference.setType( packaging );
333 ManagedRepositoryContent repository = repositoryFactory.getManagedRepositoryContent( repositoryId );
335 String artifactPath = repository.toPath( artifactReference );
337 int lastIndex = artifactPath.lastIndexOf( '/' );
339 String path = artifactPath.substring( 0, lastIndex );
340 Path targetPath = Paths.get( repoConfig.getLocation(), path );
342 String pomFilename = artifactPath.substring( lastIndex + 1 );
343 if ( StringUtils.isNotEmpty( fileMetadata.getClassifier() ) )
345 pomFilename = StringUtils.remove( pomFilename, "-" + fileMetadata.getClassifier() );
347 pomFilename = FilenameUtils.removeExtension( pomFilename ) + ".pom";
349 copyFile( Paths.get( fileMetadata.getServerFileName() ), targetPath, pomFilename, fixChecksums );
350 triggerAuditEvent( repoConfig.getId(), path + "/" + pomFilename, AuditEvent.UPLOAD_FILE );
351 queueRepositoryTask( repoConfig.getId(), targetPath.resolve(pomFilename ) );
353 catch ( IOException ie )
355 throw new ArchivaRestServiceException( "Error encountered while uploading pom file: " + ie.getMessage(),
356 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), ie );
358 catch ( RepositoryException rep )
360 throw new ArchivaRestServiceException( "Repository exception: " + rep.getMessage(),
361 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), rep );
363 catch ( RepositoryAdminException e )
365 throw new ArchivaRestServiceException( "RepositoryAdmin exception: " + e.getMessage(),
366 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
370 protected void saveFile( String repositoryId, FileMetadata fileMetadata, boolean generatePom, String groupId,
371 String artifactId, String version, String packaging )
372 throws ArchivaRestServiceException
377 ManagedRepository repoConfig = managedRepositoryAdmin.getManagedRepository( repositoryId );
379 ArtifactReference artifactReference = new ArtifactReference();
380 artifactReference.setArtifactId( artifactId );
381 artifactReference.setGroupId( groupId );
382 artifactReference.setVersion( version );
383 artifactReference.setClassifier( fileMetadata.getClassifier() );
384 artifactReference.setType(
385 StringUtils.isEmpty( fileMetadata.getPackaging() ) ? packaging : fileMetadata.getPackaging() );
387 ManagedRepositoryContent repository = repositoryFactory.getManagedRepositoryContent( repositoryId );
389 String artifactPath = repository.toPath( artifactReference );
391 int lastIndex = artifactPath.lastIndexOf( '/' );
393 String path = artifactPath.substring( 0, lastIndex );
394 Path targetPath = Paths.get( repoConfig.getLocation(), path );
396 log.debug( "artifactPath: {} found targetPath: {}", artifactPath, targetPath );
398 Date lastUpdatedTimestamp = Calendar.getInstance().getTime();
399 int newBuildNumber = -1;
400 String timestamp = null;
402 Path versionMetadataFile = targetPath.resolve( MetadataTools.MAVEN_METADATA );
403 ArchivaRepositoryMetadata versionMetadata = getMetadata( versionMetadataFile );
405 if ( VersionUtil.isSnapshot( version ) )
407 TimeZone timezone = TimeZone.getTimeZone( "UTC" );
408 DateFormat fmt = new SimpleDateFormat( "yyyyMMdd.HHmmss" );
409 fmt.setTimeZone( timezone );
410 timestamp = fmt.format( lastUpdatedTimestamp );
411 if ( versionMetadata.getSnapshotVersion() != null )
413 newBuildNumber = versionMetadata.getSnapshotVersion().getBuildNumber() + 1;
421 if ( !Files.exists(targetPath) )
423 Files.createDirectories( targetPath );
426 String filename = artifactPath.substring( lastIndex + 1 );
427 if ( VersionUtil.isSnapshot( version ) )
429 filename = filename.replaceAll( VersionUtil.SNAPSHOT, timestamp + "-" + newBuildNumber );
432 boolean fixChecksums =
433 !( archivaAdministration.getKnownContentConsumers().contains( "create-missing-checksums" ) );
437 Path targetFile = targetPath.resolve( filename );
438 if ( Files.exists(targetFile) && !VersionUtil.isSnapshot( version ) && repoConfig.isBlockRedeployments() )
440 throw new ArchivaRestServiceException(
441 "Overwriting released artifacts in repository '" + repoConfig.getId() + "' is not allowed.",
442 Response.Status.BAD_REQUEST.getStatusCode(), null );
446 copyFile( Paths.get( fileMetadata.getServerFileName() ), targetPath, filename, fixChecksums );
447 triggerAuditEvent( repository.getId(), path + "/" + filename, AuditEvent.UPLOAD_FILE );
448 queueRepositoryTask( repository.getId(), targetFile );
451 catch ( IOException ie )
453 log.error( "IOException copying file: {}", ie.getMessage(), ie );
454 throw new ArchivaRestServiceException(
455 "Overwriting released artifacts in repository '" + repoConfig.getId() + "' is not allowed.",
456 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), ie );
461 String pomFilename = filename;
462 if ( StringUtils.isNotEmpty( fileMetadata.getClassifier() ) )
464 pomFilename = StringUtils.remove( pomFilename, "-" + fileMetadata.getClassifier() );
466 pomFilename = FilenameUtils.removeExtension( pomFilename ) + ".pom";
470 Path generatedPomFile =
471 createPom( targetPath, pomFilename, fileMetadata, groupId, artifactId, version, packaging );
472 triggerAuditEvent( repoConfig.getId(), path + "/" + pomFilename, AuditEvent.UPLOAD_FILE );
475 fixChecksums( generatedPomFile );
477 queueRepositoryTask( repoConfig.getId(), generatedPomFile );
479 catch ( IOException ie )
481 throw new ArchivaRestServiceException(
482 "Error encountered while writing pom file: " + ie.getMessage(),
483 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), ie );
487 // explicitly update only if metadata-updater consumer is not enabled!
488 if ( !archivaAdministration.getKnownContentConsumers().contains( "metadata-updater" ) )
490 updateProjectMetadata( targetPath.toAbsolutePath().toString(), lastUpdatedTimestamp, timestamp, newBuildNumber,
491 fixChecksums, fileMetadata, groupId, artifactId, version, packaging );
493 if ( VersionUtil.isSnapshot( version ) )
495 updateVersionMetadata( versionMetadata, versionMetadataFile, lastUpdatedTimestamp, timestamp,
496 newBuildNumber, fixChecksums, fileMetadata, groupId, artifactId, version,
501 catch ( RepositoryNotFoundException re )
503 throw new ArchivaRestServiceException( "Target repository cannot be found: " + re.getMessage(),
504 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), re );
506 catch ( RepositoryException rep )
508 throw new ArchivaRestServiceException( "Repository exception: " + rep.getMessage(),
509 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), rep );
511 catch ( RepositoryAdminException e )
513 throw new ArchivaRestServiceException( "RepositoryAdmin exception: " + e.getMessage(),
514 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
516 catch ( IOException e )
518 throw new ArchivaRestServiceException("Repository exception "+ e.getMessage(),
519 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e);
523 private ArchivaRepositoryMetadata getMetadata( Path metadataFile )
524 throws RepositoryMetadataException
526 ArchivaRepositoryMetadata metadata = new ArchivaRepositoryMetadata();
527 if ( Files.exists(metadataFile) )
531 metadata = MavenMetadataReader.read( metadataFile );
533 catch ( XMLException e )
535 throw new RepositoryMetadataException( e.getMessage(), e );
541 private Path createPom( Path targetPath, String filename, FileMetadata fileMetadata, String groupId,
542 String artifactId, String version, String packaging )
545 Model projectModel = new Model();
546 projectModel.setModelVersion( "4.0.0" );
547 projectModel.setGroupId( groupId );
548 projectModel.setArtifactId( artifactId );
549 projectModel.setVersion( version );
550 projectModel.setPackaging( packaging );
552 Path pomFile = targetPath.resolve( filename );
553 MavenXpp3Writer writer = new MavenXpp3Writer();
555 try (FileWriter w = new FileWriter( pomFile.toFile() ))
557 writer.write( w, projectModel );
563 private void fixChecksums( Path file )
565 ChecksummedFile checksum = new ChecksummedFile( file );
566 checksum.fixChecksums( algorithms );
569 private void queueRepositoryTask( String repositoryId, Path localFile )
571 RepositoryTask task = new RepositoryTask();
572 task.setRepositoryId( repositoryId );
573 task.setResourceFile( localFile );
574 task.setUpdateRelatedArtifacts( true );
575 task.setScanAll( false );
579 scheduler.queueTask( task );
581 catch ( TaskQueueException e )
583 log.error( "Unable to queue repository task to execute consumers on resource file ['{}"
584 + "'].", localFile.getFileName() );
588 private void copyFile( Path sourceFile, Path targetPath, String targetFilename, boolean fixChecksums )
592 Files.copy( sourceFile, targetPath.resolve( targetFilename ), StandardCopyOption.REPLACE_EXISTING,
593 StandardCopyOption.COPY_ATTRIBUTES );
597 fixChecksums( targetPath.resolve( targetFilename ) );
602 * Update artifact level metadata. If it does not exist, create the metadata and fix checksums if necessary.
604 private void updateProjectMetadata( String targetPath, Date lastUpdatedTimestamp, String timestamp, int buildNumber,
605 boolean fixChecksums, FileMetadata fileMetadata, String groupId,
606 String artifactId, String version, String packaging )
607 throws RepositoryMetadataException
609 List<String> availableVersions = new ArrayList<>();
610 String latestVersion = version;
612 Path projectDir = Paths.get(targetPath).getParent();
613 Path projectMetadataFile = projectDir.resolve( MetadataTools.MAVEN_METADATA );
615 ArchivaRepositoryMetadata projectMetadata = getMetadata( projectMetadataFile );
617 if ( Files.exists(projectMetadataFile) )
619 availableVersions = projectMetadata.getAvailableVersions();
621 Collections.sort( availableVersions, VersionComparator.getInstance() );
623 if ( !availableVersions.contains( version ) )
625 availableVersions.add( version );
628 latestVersion = availableVersions.get( availableVersions.size() - 1 );
632 availableVersions.add( version );
634 projectMetadata.setGroupId( groupId );
635 projectMetadata.setArtifactId( artifactId );
638 if ( projectMetadata.getGroupId() == null )
640 projectMetadata.setGroupId( groupId );
643 if ( projectMetadata.getArtifactId() == null )
645 projectMetadata.setArtifactId( artifactId );
648 projectMetadata.setLatestVersion( latestVersion );
649 projectMetadata.setLastUpdatedTimestamp( lastUpdatedTimestamp );
650 projectMetadata.setAvailableVersions( availableVersions );
652 if ( !VersionUtil.isSnapshot( version ) )
654 projectMetadata.setReleasedVersion( latestVersion );
657 RepositoryMetadataWriter.write( projectMetadata, projectMetadataFile );
661 fixChecksums( projectMetadataFile );
666 * Update version level metadata for snapshot artifacts. If it does not exist, create the metadata and fix checksums
669 private void updateVersionMetadata( ArchivaRepositoryMetadata metadata, Path metadataFile,
670 Date lastUpdatedTimestamp, String timestamp, int buildNumber,
671 boolean fixChecksums, FileMetadata fileMetadata, String groupId,
672 String artifactId, String version, String packaging )
673 throws RepositoryMetadataException
675 if ( !Files.exists(metadataFile) )
677 metadata.setGroupId( groupId );
678 metadata.setArtifactId( artifactId );
679 metadata.setVersion( version );
682 if ( metadata.getSnapshotVersion() == null )
684 metadata.setSnapshotVersion( new SnapshotVersion() );
687 metadata.getSnapshotVersion().setBuildNumber( buildNumber );
688 metadata.getSnapshotVersion().setTimestamp( timestamp );
689 metadata.setLastUpdatedTimestamp( lastUpdatedTimestamp );
691 RepositoryMetadataWriter.write( metadata, metadataFile );
695 fixChecksums( metadataFile );