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.audit.AuditEvent;
28 import org.apache.archiva.checksum.ChecksumAlgorithm;
29 import org.apache.archiva.checksum.ChecksummedFile;
30 import org.apache.archiva.common.utils.VersionComparator;
31 import org.apache.archiva.common.utils.VersionUtil;
32 import org.apache.archiva.maven2.metadata.MavenMetadataReader;
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;
69 import java.io.FileOutputStream;
70 import java.io.FileWriter;
71 import java.io.IOException;
72 import java.nio.file.Files;
73 import java.nio.file.StandardCopyOption;
74 import java.text.DateFormat;
75 import java.text.SimpleDateFormat;
76 import java.util.ArrayList;
77 import java.util.Calendar;
78 import java.util.Collections;
79 import java.util.Date;
80 import java.util.Iterator;
81 import java.util.List;
82 import java.util.TimeZone;
83 import java.util.concurrent.CopyOnWriteArrayList;
86 * @author Olivier Lamy
88 @Service("fileUploadService#rest")
89 public class DefaultFileUploadService
90 extends AbstractRestService
91 implements FileUploadService
93 private Logger log = LoggerFactory.getLogger( getClass() );
96 private HttpServletRequest httpServletRequest;
99 private ManagedRepositoryAdmin managedRepositoryAdmin;
102 private RepositoryContentFactory repositoryFactory;
105 private ArchivaAdministration archivaAdministration;
107 private ChecksumAlgorithm[] algorithms = new ChecksumAlgorithm[]{ ChecksumAlgorithm.SHA1, ChecksumAlgorithm.MD5 };
110 @Named(value = "archivaTaskScheduler#repository")
111 private ArchivaTaskScheduler scheduler;
113 private String getStringValue( MultipartBody multipartBody, String attachmentId )
116 Attachment attachment = multipartBody.getAttachment( attachmentId );
117 return attachment == null ? "" : IOUtils.toString( attachment.getDataHandler().getInputStream() );
121 public FileMetadata post( MultipartBody multipartBody )
122 throws ArchivaRestServiceException
128 String classifier = getStringValue( multipartBody, "classifier" );
129 String packaging = getStringValue( multipartBody, "packaging" );
130 // skygo: http header form pomFile was once sending 1 for true and void for false
131 // leading to permanent false value for pomFile if using toBoolean(); use , "1", ""
132 boolean pomFile = BooleanUtils.toBoolean( getStringValue( multipartBody, "pomFile" ) );
134 Attachment file = multipartBody.getAttachment( "files[]" );
136 //Content-Disposition: form-data; name="files[]"; filename="org.apache.karaf.features.command-2.2.2.jar"
137 String fileName = file.getContentDisposition().getParameter( "filename" );
139 File tmpFile = File.createTempFile( "upload-artifact", ".tmp" );
140 tmpFile.deleteOnExit();
141 IOUtils.copy( file.getDataHandler().getInputStream(), new FileOutputStream( tmpFile ) );
142 FileMetadata fileMetadata = new FileMetadata( fileName, tmpFile.length(), "theurl" );
143 fileMetadata.setServerFileName( tmpFile.getPath() );
144 fileMetadata.setClassifier( classifier );
145 fileMetadata.setDeleteUrl( tmpFile.getName() );
146 fileMetadata.setPomFile( pomFile );
147 fileMetadata.setPackaging( packaging );
149 log.info( "uploading file: {}", fileMetadata );
151 List<FileMetadata> fileMetadatas = getSessionFilesList();
153 fileMetadatas.add( fileMetadata );
157 catch ( IOException e )
159 throw new ArchivaRestServiceException( e.getMessage(),
160 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
166 * FIXME must be per session synchronized not globally
170 protected synchronized List<FileMetadata> getSessionFilesList()
172 List<FileMetadata> fileMetadatas =
173 (List<FileMetadata>) httpServletRequest.getSession().getAttribute( FILES_SESSION_KEY );
174 if ( fileMetadatas == null )
176 fileMetadatas = new CopyOnWriteArrayList<>();
177 httpServletRequest.getSession().setAttribute( FILES_SESSION_KEY, fileMetadatas );
179 return fileMetadatas;
183 public Boolean deleteFile( String fileName )
184 throws ArchivaRestServiceException
186 File file = new File( SystemUtils.getJavaIoTmpDir(), fileName );
187 log.debug( "delete file:{},exists:{}", file.getPath(), file.exists() );
188 boolean removed = getSessionFileMetadatas().remove( new FileMetadata( fileName ) );
189 // try with full name as ui only know the file name
192 removed = getSessionFileMetadatas().remove( new FileMetadata( file.getPath() ) );
196 return file.delete();
198 return Boolean.FALSE;
202 public Boolean clearUploadedFiles()
203 throws ArchivaRestServiceException
205 List<FileMetadata> fileMetadatas = new ArrayList( getSessionFileMetadatas() );
206 for ( FileMetadata fileMetadata : fileMetadatas )
208 deleteFile( new File( fileMetadata.getServerFileName() ).getPath() );
210 getSessionFileMetadatas().clear();
215 public List<FileMetadata> getSessionFileMetadatas()
216 throws ArchivaRestServiceException
218 List<FileMetadata> fileMetadatas =
219 (List<FileMetadata>) httpServletRequest.getSession().getAttribute( FILES_SESSION_KEY );
221 return fileMetadatas == null ? Collections.<FileMetadata>emptyList() : fileMetadatas;
225 public Boolean save( String repositoryId, String groupId, String artifactId, String version, String packaging,
226 boolean generatePom )
227 throws ArchivaRestServiceException
229 repositoryId = StringUtils.trim( repositoryId );
230 groupId = StringUtils.trim( groupId );
231 artifactId = StringUtils.trim( artifactId );
232 version = StringUtils.trim( version );
233 packaging = StringUtils.trim( packaging );
235 List<FileMetadata> fileMetadatas = getSessionFilesList();
236 if ( fileMetadatas == null || fileMetadatas.isEmpty() )
238 return Boolean.FALSE;
243 ManagedRepository managedRepository = managedRepositoryAdmin.getManagedRepository( repositoryId );
245 if ( managedRepository == null )
248 throw new ArchivaRestServiceException( "Cannot find managed repository with id " + repositoryId,
249 Response.Status.BAD_REQUEST.getStatusCode(), null );
252 if ( VersionUtil.isSnapshot( version ) && !managedRepository.isSnapshots() )
255 throw new ArchivaRestServiceException(
256 "Managed repository with id " + repositoryId + " do not accept snapshots",
257 Response.Status.BAD_REQUEST.getStatusCode(), null );
260 catch ( RepositoryAdminException e )
262 throw new ArchivaRestServiceException( e.getMessage(),
263 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
266 // get from the session file with groupId/artifactId
268 Iterable<FileMetadata> filesToAdd = Iterables.filter( fileMetadatas, new Predicate<FileMetadata>()
270 public boolean apply( FileMetadata fileMetadata )
272 return fileMetadata != null && !fileMetadata.isPomFile();
275 Iterator<FileMetadata> iterator = filesToAdd.iterator();
276 boolean pomGenerated = false;
277 while ( iterator.hasNext() )
279 FileMetadata fileMetadata = iterator.next();
280 log.debug( "fileToAdd: {}", fileMetadata );
281 saveFile( repositoryId, fileMetadata, generatePom && !pomGenerated, groupId, artifactId, version,
284 deleteFile( fileMetadata.getServerFileName() );
287 filesToAdd = Iterables.filter( fileMetadatas, new Predicate<FileMetadata>()
290 public boolean apply( FileMetadata fileMetadata )
292 return fileMetadata != null && fileMetadata.isPomFile();
296 iterator = filesToAdd.iterator();
297 while ( iterator.hasNext() )
299 FileMetadata fileMetadata = iterator.next();
300 log.debug( "fileToAdd: {}", fileMetadata );
301 savePomFile( repositoryId, fileMetadata, groupId, artifactId, version, packaging );
302 deleteFile( fileMetadata.getServerFileName() );
308 protected void savePomFile( String repositoryId, FileMetadata fileMetadata, String groupId, String artifactId,
309 String version, String packaging )
310 throws ArchivaRestServiceException
315 boolean fixChecksums =
316 !( archivaAdministration.getKnownContentConsumers().contains( "create-missing-checksums" ) );
318 ManagedRepository repoConfig = managedRepositoryAdmin.getManagedRepository( repositoryId );
320 ArtifactReference artifactReference = new ArtifactReference();
321 artifactReference.setArtifactId( artifactId );
322 artifactReference.setGroupId( groupId );
323 artifactReference.setVersion( version );
324 artifactReference.setClassifier( fileMetadata.getClassifier() );
325 artifactReference.setType( packaging );
327 ManagedRepositoryContent repository = repositoryFactory.getManagedRepositoryContent( repositoryId );
329 String artifactPath = repository.toPath( artifactReference );
331 int lastIndex = artifactPath.lastIndexOf( '/' );
333 String path = artifactPath.substring( 0, lastIndex );
334 File targetPath = new File( repoConfig.getLocation(), path );
336 String pomFilename = artifactPath.substring( lastIndex + 1 );
337 if ( StringUtils.isNotEmpty( fileMetadata.getClassifier() ) )
339 pomFilename = StringUtils.remove( pomFilename, "-" + fileMetadata.getClassifier() );
341 pomFilename = FilenameUtils.removeExtension( pomFilename ) + ".pom";
343 copyFile( new File( fileMetadata.getServerFileName() ), targetPath, pomFilename, fixChecksums );
344 triggerAuditEvent( repoConfig.getId(), path + "/" + pomFilename, AuditEvent.UPLOAD_FILE );
345 queueRepositoryTask( repoConfig.getId(), new File( targetPath, pomFilename ) );
347 catch ( IOException ie )
349 throw new ArchivaRestServiceException( "Error encountered while uploading pom file: " + ie.getMessage(),
350 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), ie );
352 catch ( RepositoryException rep )
354 throw new ArchivaRestServiceException( "Repository exception: " + rep.getMessage(),
355 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), rep );
357 catch ( RepositoryAdminException e )
359 throw new ArchivaRestServiceException( "RepositoryAdmin exception: " + e.getMessage(),
360 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
364 protected void saveFile( String repositoryId, FileMetadata fileMetadata, boolean generatePom, String groupId,
365 String artifactId, String version, String packaging )
366 throws ArchivaRestServiceException
371 ManagedRepository repoConfig = managedRepositoryAdmin.getManagedRepository( repositoryId );
373 ArtifactReference artifactReference = new ArtifactReference();
374 artifactReference.setArtifactId( artifactId );
375 artifactReference.setGroupId( groupId );
376 artifactReference.setVersion( version );
377 artifactReference.setClassifier( fileMetadata.getClassifier() );
378 artifactReference.setType(
379 StringUtils.isEmpty( fileMetadata.getPackaging() ) ? packaging : fileMetadata.getPackaging() );
381 ManagedRepositoryContent repository = repositoryFactory.getManagedRepositoryContent( repositoryId );
383 String artifactPath = repository.toPath( artifactReference );
385 int lastIndex = artifactPath.lastIndexOf( '/' );
387 String path = artifactPath.substring( 0, lastIndex );
388 File targetPath = new File( repoConfig.getLocation(), path );
390 log.debug( "artifactPath: {} found targetPath: {}", artifactPath, targetPath );
392 Date lastUpdatedTimestamp = Calendar.getInstance().getTime();
393 int newBuildNumber = -1;
394 String timestamp = null;
396 File versionMetadataFile = new File( targetPath, MetadataTools.MAVEN_METADATA );
397 ArchivaRepositoryMetadata versionMetadata = getMetadata( versionMetadataFile );
399 if ( VersionUtil.isSnapshot( version ) )
401 TimeZone timezone = TimeZone.getTimeZone( "UTC" );
402 DateFormat fmt = new SimpleDateFormat( "yyyyMMdd.HHmmss" );
403 fmt.setTimeZone( timezone );
404 timestamp = fmt.format( lastUpdatedTimestamp );
405 if ( versionMetadata.getSnapshotVersion() != null )
407 newBuildNumber = versionMetadata.getSnapshotVersion().getBuildNumber() + 1;
415 if ( !targetPath.exists() )
420 String filename = artifactPath.substring( lastIndex + 1 );
421 if ( VersionUtil.isSnapshot( version ) )
423 filename = filename.replaceAll( VersionUtil.SNAPSHOT, timestamp + "-" + newBuildNumber );
426 boolean fixChecksums =
427 !( archivaAdministration.getKnownContentConsumers().contains( "create-missing-checksums" ) );
431 File targetFile = new File( targetPath, filename );
432 if ( targetFile.exists() && !VersionUtil.isSnapshot( version ) && repoConfig.isBlockRedeployments() )
434 throw new ArchivaRestServiceException(
435 "Overwriting released artifacts in repository '" + repoConfig.getId() + "' is not allowed.",
436 Response.Status.BAD_REQUEST.getStatusCode(), null );
440 copyFile( new File( fileMetadata.getServerFileName() ), targetPath, filename, fixChecksums );
441 triggerAuditEvent( repository.getId(), path + "/" + filename, AuditEvent.UPLOAD_FILE );
442 queueRepositoryTask( repository.getId(), targetFile );
445 catch ( IOException ie )
447 log.error( "IOException copying file: {}", ie.getMessage(), ie );
448 throw new ArchivaRestServiceException(
449 "Overwriting released artifacts in repository '" + repoConfig.getId() + "' is not allowed.",
450 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), ie );
455 String pomFilename = filename;
456 if ( StringUtils.isNotEmpty( fileMetadata.getClassifier() ) )
458 pomFilename = StringUtils.remove( pomFilename, "-" + fileMetadata.getClassifier() );
460 pomFilename = FilenameUtils.removeExtension( pomFilename ) + ".pom";
464 File generatedPomFile =
465 createPom( targetPath, pomFilename, fileMetadata, groupId, artifactId, version, packaging );
466 triggerAuditEvent( repoConfig.getId(), path + "/" + pomFilename, AuditEvent.UPLOAD_FILE );
469 fixChecksums( generatedPomFile );
471 queueRepositoryTask( repoConfig.getId(), generatedPomFile );
473 catch ( IOException ie )
475 throw new ArchivaRestServiceException(
476 "Error encountered while writing pom file: " + ie.getMessage(),
477 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), ie );
481 // explicitly update only if metadata-updater consumer is not enabled!
482 if ( !archivaAdministration.getKnownContentConsumers().contains( "metadata-updater" ) )
484 updateProjectMetadata( targetPath.getAbsolutePath(), lastUpdatedTimestamp, timestamp, newBuildNumber,
485 fixChecksums, fileMetadata, groupId, artifactId, version, packaging );
487 if ( VersionUtil.isSnapshot( version ) )
489 updateVersionMetadata( versionMetadata, versionMetadataFile, lastUpdatedTimestamp, timestamp,
490 newBuildNumber, fixChecksums, fileMetadata, groupId, artifactId, version,
495 catch ( RepositoryNotFoundException re )
497 throw new ArchivaRestServiceException( "Target repository cannot be found: " + re.getMessage(),
498 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), re );
500 catch ( RepositoryException rep )
502 throw new ArchivaRestServiceException( "Repository exception: " + rep.getMessage(),
503 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), rep );
505 catch ( RepositoryAdminException e )
507 throw new ArchivaRestServiceException( "RepositoryAdmin exception: " + e.getMessage(),
508 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
512 private ArchivaRepositoryMetadata getMetadata( File metadataFile )
513 throws RepositoryMetadataException
515 ArchivaRepositoryMetadata metadata = new ArchivaRepositoryMetadata();
516 if ( metadataFile.exists() )
520 metadata = MavenMetadataReader.read( metadataFile );
522 catch ( XMLException e )
524 throw new RepositoryMetadataException( e.getMessage(), e );
530 private File createPom( File targetPath, String filename, FileMetadata fileMetadata, String groupId,
531 String artifactId, String version, String packaging )
534 Model projectModel = new Model();
535 projectModel.setModelVersion( "4.0.0" );
536 projectModel.setGroupId( groupId );
537 projectModel.setArtifactId( artifactId );
538 projectModel.setVersion( version );
539 projectModel.setPackaging( packaging );
541 File pomFile = new File( targetPath, filename );
542 MavenXpp3Writer writer = new MavenXpp3Writer();
544 try (FileWriter w = new FileWriter( pomFile ))
546 writer.write( w, projectModel );
552 private void fixChecksums( File file )
554 ChecksummedFile checksum = new ChecksummedFile( file );
555 checksum.fixChecksums( algorithms );
558 private void queueRepositoryTask( String repositoryId, File localFile )
560 RepositoryTask task = new RepositoryTask();
561 task.setRepositoryId( repositoryId );
562 task.setResourceFile( localFile );
563 task.setUpdateRelatedArtifacts( true );
564 task.setScanAll( false );
568 scheduler.queueTask( task );
570 catch ( TaskQueueException e )
572 log.error( "Unable to queue repository task to execute consumers on resource file ['" + localFile.getName()
577 private void copyFile( File sourceFile, File targetPath, String targetFilename, boolean fixChecksums )
581 Files.copy( sourceFile.toPath(), new File( targetPath, targetFilename ).toPath(), StandardCopyOption.REPLACE_EXISTING,
582 StandardCopyOption.COPY_ATTRIBUTES );
586 fixChecksums( new File( targetPath, targetFilename ) );
591 * Update artifact level metadata. If it does not exist, create the metadata and fix checksums if necessary.
593 private void updateProjectMetadata( String targetPath, Date lastUpdatedTimestamp, String timestamp, int buildNumber,
594 boolean fixChecksums, FileMetadata fileMetadata, String groupId,
595 String artifactId, String version, String packaging )
596 throws RepositoryMetadataException
598 List<String> availableVersions = new ArrayList<>();
599 String latestVersion = version;
601 File projectDir = new File( targetPath ).getParentFile();
602 File projectMetadataFile = new File( projectDir, MetadataTools.MAVEN_METADATA );
604 ArchivaRepositoryMetadata projectMetadata = getMetadata( projectMetadataFile );
606 if ( projectMetadataFile.exists() )
608 availableVersions = projectMetadata.getAvailableVersions();
610 Collections.sort( availableVersions, VersionComparator.getInstance() );
612 if ( !availableVersions.contains( version ) )
614 availableVersions.add( version );
617 latestVersion = availableVersions.get( availableVersions.size() - 1 );
621 availableVersions.add( version );
623 projectMetadata.setGroupId( groupId );
624 projectMetadata.setArtifactId( artifactId );
627 if ( projectMetadata.getGroupId() == null )
629 projectMetadata.setGroupId( groupId );
632 if ( projectMetadata.getArtifactId() == null )
634 projectMetadata.setArtifactId( artifactId );
637 projectMetadata.setLatestVersion( latestVersion );
638 projectMetadata.setLastUpdatedTimestamp( lastUpdatedTimestamp );
639 projectMetadata.setAvailableVersions( availableVersions );
641 if ( !VersionUtil.isSnapshot( version ) )
643 projectMetadata.setReleasedVersion( latestVersion );
646 RepositoryMetadataWriter.write( projectMetadata, projectMetadataFile );
650 fixChecksums( projectMetadataFile );
655 * Update version level metadata for snapshot artifacts. If it does not exist, create the metadata and fix checksums
658 private void updateVersionMetadata( ArchivaRepositoryMetadata metadata, File metadataFile,
659 Date lastUpdatedTimestamp, String timestamp, int buildNumber,
660 boolean fixChecksums, FileMetadata fileMetadata, String groupId,
661 String artifactId, String version, String packaging )
662 throws RepositoryMetadataException
664 if ( !metadataFile.exists() )
666 metadata.setGroupId( groupId );
667 metadata.setArtifactId( artifactId );
668 metadata.setVersion( version );
671 if ( metadata.getSnapshotVersion() == null )
673 metadata.setSnapshotVersion( new SnapshotVersion() );
676 metadata.getSnapshotVersion().setBuildNumber( buildNumber );
677 metadata.getSnapshotVersion().setTimestamp( timestamp );
678 metadata.setLastUpdatedTimestamp( lastUpdatedTimestamp );
680 RepositoryMetadataWriter.write( metadata, metadataFile );
684 fixChecksums( metadataFile );