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;
64 import javax.inject.Inject;
65 import javax.inject.Named;
66 import javax.servlet.http.HttpServletRequest;
67 import javax.ws.rs.core.Context;
68 import javax.ws.rs.core.Response;
70 import java.io.FileInputStream;
71 import java.io.FileOutputStream;
72 import java.io.FileWriter;
73 import java.io.IOException;
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() );
120 public FileMetadata post( MultipartBody multipartBody )
121 throws ArchivaRestServiceException
127 String classifier = getStringValue( multipartBody, "classifier" );
128 String packaging = getStringValue( multipartBody, "packaging" );
129 // skygo: http header form pomFile was once sending 1 for true and void for false
130 // leading to permanent false value for pomFile if using toBoolean(); use , "1", ""
131 boolean pomFile = BooleanUtils.toBoolean( getStringValue( multipartBody, "pomFile" ) );
133 Attachment file = multipartBody.getAttachment( "files[]" );
135 //Content-Disposition: form-data; name="files[]"; filename="org.apache.karaf.features.command-2.2.2.jar"
136 String fileName = file.getContentDisposition().getParameter( "filename" );
138 File tmpFile = File.createTempFile( "upload-artifact", ".tmp" );
139 tmpFile.deleteOnExit();
140 IOUtils.copy( file.getDataHandler().getInputStream(), new FileOutputStream( tmpFile ) );
141 FileMetadata fileMetadata = new FileMetadata( fileName, tmpFile.length(), "theurl" );
142 fileMetadata.setServerFileName( tmpFile.getPath() );
143 fileMetadata.setClassifier( classifier );
144 fileMetadata.setDeleteUrl( tmpFile.getName() );
145 fileMetadata.setPomFile( pomFile );
146 fileMetadata.setPackaging( packaging );
148 log.info( "uploading file: {}", fileMetadata );
150 List<FileMetadata> fileMetadatas = getSessionFilesList();
152 fileMetadatas.add( fileMetadata );
156 catch ( IOException e )
158 throw new ArchivaRestServiceException( e.getMessage(),
159 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
165 * FIXME must be per session synchronized not globally
169 protected synchronized List<FileMetadata> getSessionFilesList()
171 List<FileMetadata> fileMetadatas =
172 (List<FileMetadata>) httpServletRequest.getSession().getAttribute( FILES_SESSION_KEY );
173 if ( fileMetadatas == null )
175 fileMetadatas = new CopyOnWriteArrayList<FileMetadata>();
176 httpServletRequest.getSession().setAttribute( FILES_SESSION_KEY, fileMetadatas );
178 return fileMetadatas;
181 public Boolean deleteFile( String fileName )
182 throws ArchivaRestServiceException
184 File file = new File( SystemUtils.getJavaIoTmpDir(), fileName );
185 log.debug( "delete file:{},exists:{}", file.getPath(), file.exists() );
186 boolean removed = getSessionFileMetadatas().remove( new FileMetadata( fileName ) );
189 return file.delete();
191 return Boolean.FALSE;
194 public Boolean clearUploadedFiles()
195 throws ArchivaRestServiceException
197 List<FileMetadata> fileMetadatas = new ArrayList( getSessionFileMetadatas() );
198 for ( FileMetadata fileMetadata : fileMetadatas )
200 deleteFile( new File( fileMetadata.getServerFileName() ).getPath() );
202 getSessionFileMetadatas().clear();
206 public List<FileMetadata> getSessionFileMetadatas()
207 throws ArchivaRestServiceException
209 List<FileMetadata> fileMetadatas =
210 (List<FileMetadata>) httpServletRequest.getSession().getAttribute( FILES_SESSION_KEY );
212 return fileMetadatas == null ? Collections.<FileMetadata>emptyList() : fileMetadatas;
215 public Boolean save( String repositoryId, String groupId, String artifactId, String version, String packaging,
216 boolean generatePom )
217 throws ArchivaRestServiceException
219 repositoryId = StringUtils.trim( repositoryId );
220 groupId = StringUtils.trim( groupId );
221 artifactId = StringUtils.trim( artifactId );
222 version = StringUtils.trim( version );
223 packaging = StringUtils.trim( packaging );
225 List<FileMetadata> fileMetadatas = getSessionFilesList();
226 if ( fileMetadatas == null || fileMetadatas.isEmpty() )
228 return Boolean.FALSE;
233 ManagedRepository managedRepository = managedRepositoryAdmin.getManagedRepository( repositoryId );
235 if ( managedRepository == null )
238 throw new ArchivaRestServiceException( "Cannot find managed repository with id " + repositoryId,
239 Response.Status.BAD_REQUEST.getStatusCode(), null );
242 if ( VersionUtil.isSnapshot( version ) && !managedRepository.isSnapshots() )
245 throw new ArchivaRestServiceException(
246 "Managed repository with id " + repositoryId + " do not accept snapshots",
247 Response.Status.BAD_REQUEST.getStatusCode(), null );
250 catch ( RepositoryAdminException e )
252 throw new ArchivaRestServiceException( e.getMessage(),
253 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
256 // get from the session file with groupId/artifactId
258 Iterable<FileMetadata> filesToAdd = Iterables.filter( fileMetadatas, new Predicate<FileMetadata>()
260 public boolean apply( FileMetadata fileMetadata )
262 return fileMetadata != null && !fileMetadata.isPomFile();
265 Iterator<FileMetadata> iterator = filesToAdd.iterator();
266 boolean pomGenerated = false;
267 while ( iterator.hasNext() )
269 FileMetadata fileMetadata = iterator.next();
270 log.debug( "fileToAdd: {}", fileMetadata );
271 saveFile( repositoryId, fileMetadata, generatePom && !pomGenerated, groupId, artifactId, version,
274 deleteFile( fileMetadata.getServerFileName() );
277 filesToAdd = Iterables.filter( fileMetadatas, new Predicate<FileMetadata>()
279 public boolean apply( FileMetadata fileMetadata )
281 return fileMetadata != null && fileMetadata.isPomFile();
285 iterator = filesToAdd.iterator();
286 while ( iterator.hasNext() )
288 FileMetadata fileMetadata = iterator.next();
289 log.debug( "fileToAdd: {}", fileMetadata );
290 savePomFile( repositoryId, fileMetadata, groupId, artifactId, version, packaging );
291 deleteFile( fileMetadata.getServerFileName() );
297 protected void savePomFile( String repositoryId, FileMetadata fileMetadata, String groupId, String artifactId,
298 String version, String packaging )
299 throws ArchivaRestServiceException
304 boolean fixChecksums =
305 !( archivaAdministration.getKnownContentConsumers().contains( "create-missing-checksums" ) );
307 ManagedRepository repoConfig = managedRepositoryAdmin.getManagedRepository( repositoryId );
309 ArtifactReference artifactReference = new ArtifactReference();
310 artifactReference.setArtifactId( artifactId );
311 artifactReference.setGroupId( groupId );
312 artifactReference.setVersion( version );
313 artifactReference.setClassifier( fileMetadata.getClassifier() );
314 artifactReference.setType( packaging );
316 ManagedRepositoryContent repository = repositoryFactory.getManagedRepositoryContent( repositoryId );
318 String artifactPath = repository.toPath( artifactReference );
320 int lastIndex = artifactPath.lastIndexOf( '/' );
322 String path = artifactPath.substring( 0, lastIndex );
323 File targetPath = new File( repoConfig.getLocation(), path );
325 String pomFilename = artifactPath.substring( lastIndex + 1 );
326 if ( StringUtils.isNotEmpty( fileMetadata.getClassifier() ) )
328 pomFilename = StringUtils.remove( pomFilename, "-" + fileMetadata.getClassifier() );
330 pomFilename = FilenameUtils.removeExtension( pomFilename ) + ".pom";
332 copyFile( new File( fileMetadata.getServerFileName() ), targetPath, pomFilename, fixChecksums );
333 triggerAuditEvent( repoConfig.getId(), path + "/" + pomFilename, AuditEvent.UPLOAD_FILE );
334 queueRepositoryTask( repoConfig.getId(), new File( targetPath, pomFilename ) );
336 catch ( IOException ie )
338 throw new ArchivaRestServiceException( "Error encountered while uploading pom file: " + ie.getMessage(),
339 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), ie );
341 catch ( RepositoryException rep )
343 throw new ArchivaRestServiceException( "Repository exception: " + rep.getMessage(),
344 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), rep );
346 catch ( RepositoryAdminException e )
348 throw new ArchivaRestServiceException( "RepositoryAdmin exception: " + e.getMessage(),
349 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
353 protected void saveFile( String repositoryId, FileMetadata fileMetadata, boolean generatePom, String groupId,
354 String artifactId, String version, String packaging )
355 throws ArchivaRestServiceException
360 ManagedRepository repoConfig = managedRepositoryAdmin.getManagedRepository( repositoryId );
362 ArtifactReference artifactReference = new ArtifactReference();
363 artifactReference.setArtifactId( artifactId );
364 artifactReference.setGroupId( groupId );
365 artifactReference.setVersion( version );
366 artifactReference.setClassifier( fileMetadata.getClassifier() );
367 artifactReference.setType(
368 StringUtils.isEmpty( fileMetadata.getPackaging() ) ? packaging : fileMetadata.getPackaging() );
370 ManagedRepositoryContent repository = repositoryFactory.getManagedRepositoryContent( repositoryId );
372 String artifactPath = repository.toPath( artifactReference );
374 int lastIndex = artifactPath.lastIndexOf( '/' );
376 String path = artifactPath.substring( 0, lastIndex );
377 File targetPath = new File( repoConfig.getLocation(), path );
379 log.debug( "artifactPath: {} found targetPath: {}", artifactPath, targetPath );
381 Date lastUpdatedTimestamp = Calendar.getInstance().getTime();
382 int newBuildNumber = -1;
383 String timestamp = null;
385 File versionMetadataFile = new File( targetPath, MetadataTools.MAVEN_METADATA );
386 ArchivaRepositoryMetadata versionMetadata = getMetadata( versionMetadataFile );
388 if ( VersionUtil.isSnapshot( version ) )
390 TimeZone timezone = TimeZone.getTimeZone( "UTC" );
391 DateFormat fmt = new SimpleDateFormat( "yyyyMMdd.HHmmss" );
392 fmt.setTimeZone( timezone );
393 timestamp = fmt.format( lastUpdatedTimestamp );
394 if ( versionMetadata.getSnapshotVersion() != null )
396 newBuildNumber = versionMetadata.getSnapshotVersion().getBuildNumber() + 1;
404 if ( !targetPath.exists() )
409 String filename = artifactPath.substring( lastIndex + 1 );
410 if ( VersionUtil.isSnapshot( version ) )
412 filename = filename.replaceAll( VersionUtil.SNAPSHOT, timestamp + "-" + newBuildNumber );
415 boolean fixChecksums =
416 !( archivaAdministration.getKnownContentConsumers().contains( "create-missing-checksums" ) );
420 File targetFile = new File( targetPath, filename );
421 if ( targetFile.exists() && !VersionUtil.isSnapshot( version ) && repoConfig.isBlockRedeployments() )
423 throw new ArchivaRestServiceException(
424 "Overwriting released artifacts in repository '" + repoConfig.getId() + "' is not allowed.",
425 Response.Status.BAD_REQUEST.getStatusCode(), null );
429 copyFile( new File( fileMetadata.getServerFileName() ), targetPath, filename, fixChecksums );
430 triggerAuditEvent( repository.getId(), path + "/" + filename, AuditEvent.UPLOAD_FILE );
431 queueRepositoryTask( repository.getId(), targetFile );
434 catch ( IOException ie )
436 log.error( "IOException copying file: {}", ie.getMessage(), ie );
437 throw new ArchivaRestServiceException(
438 "Overwriting released artifacts in repository '" + repoConfig.getId() + "' is not allowed.",
439 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), ie );
444 String pomFilename = filename;
445 if ( StringUtils.isNotEmpty( fileMetadata.getClassifier() ) )
447 pomFilename = StringUtils.remove( pomFilename, "-" + fileMetadata.getClassifier() );
449 pomFilename = FilenameUtils.removeExtension( pomFilename ) + ".pom";
453 File generatedPomFile =
454 createPom( targetPath, pomFilename, fileMetadata, groupId, artifactId, version, packaging );
455 triggerAuditEvent( repoConfig.getId(), path + "/" + pomFilename, AuditEvent.UPLOAD_FILE );
458 fixChecksums( generatedPomFile );
460 queueRepositoryTask( repoConfig.getId(), generatedPomFile );
462 catch ( IOException ie )
464 throw new ArchivaRestServiceException(
465 "Error encountered while writing pom file: " + ie.getMessage(),
466 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), ie );
470 // explicitly update only if metadata-updater consumer is not enabled!
471 if ( !archivaAdministration.getKnownContentConsumers().contains( "metadata-updater" ) )
473 updateProjectMetadata( targetPath.getAbsolutePath(), lastUpdatedTimestamp, timestamp, newBuildNumber,
474 fixChecksums, fileMetadata, groupId, artifactId, version, packaging );
476 if ( VersionUtil.isSnapshot( version ) )
478 updateVersionMetadata( versionMetadata, versionMetadataFile, lastUpdatedTimestamp, timestamp,
479 newBuildNumber, fixChecksums, fileMetadata, groupId, artifactId, version,
484 catch ( RepositoryNotFoundException re )
486 throw new ArchivaRestServiceException( "Target repository cannot be found: " + re.getMessage(),
487 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), re );
489 catch ( RepositoryException rep )
491 throw new ArchivaRestServiceException( "Repository exception: " + rep.getMessage(),
492 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), rep );
494 catch ( RepositoryAdminException e )
496 throw new ArchivaRestServiceException( "RepositoryAdmin exception: " + e.getMessage(),
497 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
501 private ArchivaRepositoryMetadata getMetadata( File metadataFile )
502 throws RepositoryMetadataException
504 ArchivaRepositoryMetadata metadata = new ArchivaRepositoryMetadata();
505 if ( metadataFile.exists() )
509 metadata = MavenMetadataReader.read( metadataFile );
511 catch ( XMLException e )
513 throw new RepositoryMetadataException( e.getMessage(), e );
519 private File createPom( File targetPath, String filename, FileMetadata fileMetadata, String groupId,
520 String artifactId, String version, String packaging )
523 Model projectModel = new Model();
524 projectModel.setModelVersion( "4.0.0" );
525 projectModel.setGroupId( groupId );
526 projectModel.setArtifactId( artifactId );
527 projectModel.setVersion( version );
528 projectModel.setPackaging( packaging );
530 File pomFile = new File( targetPath, filename );
531 MavenXpp3Writer writer = new MavenXpp3Writer();
532 FileWriter w = new FileWriter( pomFile );
535 writer.write( w, projectModel );
539 IOUtils.closeQuietly( w );
545 private void fixChecksums( File file )
547 ChecksummedFile checksum = new ChecksummedFile( file );
548 checksum.fixChecksums( algorithms );
551 private void queueRepositoryTask( String repositoryId, File localFile )
553 RepositoryTask task = new RepositoryTask();
554 task.setRepositoryId( repositoryId );
555 task.setResourceFile( localFile );
556 task.setUpdateRelatedArtifacts( true );
557 task.setScanAll( false );
561 scheduler.queueTask( task );
563 catch ( TaskQueueException e )
565 log.error( "Unable to queue repository task to execute consumers on resource file ['" + localFile.getName()
570 private void copyFile( File sourceFile, File targetPath, String targetFilename, boolean fixChecksums )
573 FileOutputStream out = new FileOutputStream( new File( targetPath, targetFilename ) );
574 FileInputStream input = new FileInputStream( sourceFile );
578 IOUtils.copy( input, out );
588 fixChecksums( new File( targetPath, targetFilename ) );
593 * Update artifact level metadata. If it does not exist, create the metadata and fix checksums if necessary.
595 private void updateProjectMetadata( String targetPath, Date lastUpdatedTimestamp, String timestamp, int buildNumber,
596 boolean fixChecksums, FileMetadata fileMetadata, String groupId,
597 String artifactId, String version, String packaging )
598 throws RepositoryMetadataException
600 List<String> availableVersions = new ArrayList<String>();
601 String latestVersion = version;
603 File projectDir = new File( targetPath ).getParentFile();
604 File projectMetadataFile = new File( projectDir, MetadataTools.MAVEN_METADATA );
606 ArchivaRepositoryMetadata projectMetadata = getMetadata( projectMetadataFile );
608 if ( projectMetadataFile.exists() )
610 availableVersions = projectMetadata.getAvailableVersions();
612 Collections.sort( availableVersions, VersionComparator.getInstance() );
614 if ( !availableVersions.contains( version ) )
616 availableVersions.add( version );
619 latestVersion = availableVersions.get( availableVersions.size() - 1 );
623 availableVersions.add( version );
625 projectMetadata.setGroupId( groupId );
626 projectMetadata.setArtifactId( artifactId );
629 if ( projectMetadata.getGroupId() == null )
631 projectMetadata.setGroupId( groupId );
634 if ( projectMetadata.getArtifactId() == null )
636 projectMetadata.setArtifactId( artifactId );
639 projectMetadata.setLatestVersion( latestVersion );
640 projectMetadata.setLastUpdatedTimestamp( lastUpdatedTimestamp );
641 projectMetadata.setAvailableVersions( availableVersions );
643 if ( !VersionUtil.isSnapshot( version ) )
645 projectMetadata.setReleasedVersion( latestVersion );
648 RepositoryMetadataWriter.write( projectMetadata, projectMetadataFile );
652 fixChecksums( projectMetadataFile );
657 * Update version level metadata for snapshot artifacts. If it does not exist, create the metadata and fix checksums
660 private void updateVersionMetadata( ArchivaRepositoryMetadata metadata, File metadataFile,
661 Date lastUpdatedTimestamp, String timestamp, int buildNumber,
662 boolean fixChecksums, FileMetadata fileMetadata, String groupId,
663 String artifactId, String version, String packaging )
664 throws RepositoryMetadataException
666 if ( !metadataFile.exists() )
668 metadata.setGroupId( groupId );
669 metadata.setArtifactId( artifactId );
670 metadata.setVersion( version );
673 if ( metadata.getSnapshotVersion() == null )
675 metadata.setSnapshotVersion( new SnapshotVersion() );
678 metadata.getSnapshotVersion().setBuildNumber( buildNumber );
679 metadata.getSnapshotVersion().setTimestamp( timestamp );
680 metadata.setLastUpdatedTimestamp( lastUpdatedTimestamp );
682 RepositoryMetadataWriter.write( metadata, metadataFile );
686 fixChecksums( metadataFile );