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 // skygo: http header form pomFile was once sending 1 for true and void for false
129 // leading to permanent false value for pomFile if using toBoolean(); use , "1", ""
130 boolean pomFile = BooleanUtils.toBoolean( getStringValue( multipartBody, "pomFile" ) );
132 Attachment file = multipartBody.getAttachment( "files[]" );
134 //Content-Disposition: form-data; name="files[]"; filename="org.apache.karaf.features.command-2.2.2.jar"
135 String fileName = file.getContentDisposition().getParameter( "filename" );
137 File tmpFile = File.createTempFile( "upload-artifact", ".tmp" );
138 tmpFile.deleteOnExit();
139 IOUtils.copy( file.getDataHandler().getInputStream(), new FileOutputStream( tmpFile ) );
140 FileMetadata fileMetadata = new FileMetadata( fileName, tmpFile.length(), "theurl" );
141 fileMetadata.setServerFileName( tmpFile.getPath() );
142 fileMetadata.setClassifier( classifier );
143 fileMetadata.setDeleteUrl( tmpFile.getName() );
144 fileMetadata.setPomFile( pomFile );
146 log.info( "uploading file: {}", fileMetadata );
148 List<FileMetadata> fileMetadatas = getSessionFilesList();
150 fileMetadatas.add( fileMetadata );
154 catch ( IOException e )
156 throw new ArchivaRestServiceException( e.getMessage(),
157 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
163 * FIXME must be per session synchronized not globally
167 protected synchronized List<FileMetadata> getSessionFilesList()
169 List<FileMetadata> fileMetadatas =
170 (List<FileMetadata>) httpServletRequest.getSession().getAttribute( FILES_SESSION_KEY );
171 if ( fileMetadatas == null )
173 fileMetadatas = new CopyOnWriteArrayList<FileMetadata>();
174 httpServletRequest.getSession().setAttribute( FILES_SESSION_KEY, fileMetadatas );
176 return fileMetadatas;
179 public Boolean deleteFile( String fileName )
180 throws ArchivaRestServiceException
182 File file = new File( SystemUtils.getJavaIoTmpDir(), fileName );
183 log.debug( "delete file:{},exists:{}", file.getPath(), file.exists() );
184 boolean removed = getSessionFileMetadatas().remove(
185 new FileMetadata( SystemUtils.getJavaIoTmpDir().getPath() + "/" + fileName ) );
188 return file.delete();
190 return Boolean.FALSE;
193 public Boolean clearUploadedFiles()
194 throws ArchivaRestServiceException
196 List<FileMetadata> fileMetadatas = new ArrayList( getSessionFileMetadatas() );
197 for ( FileMetadata fileMetadata : fileMetadatas )
199 deleteFile( new File( fileMetadata.getServerFileName() ).getName() );
204 public List<FileMetadata> getSessionFileMetadatas()
205 throws ArchivaRestServiceException
207 List<FileMetadata> fileMetadatas =
208 (List<FileMetadata>) httpServletRequest.getSession().getAttribute( FILES_SESSION_KEY );
210 return fileMetadatas == null ? Collections.<FileMetadata>emptyList() : fileMetadatas;
213 public Boolean save( String repositoryId, String groupId, String artifactId, String version,
214 String packaging, boolean generatePom )
215 throws ArchivaRestServiceException
217 repositoryId = StringUtils.trim( repositoryId );
218 groupId = StringUtils.trim( groupId );
219 artifactId = StringUtils.trim( artifactId );
220 version = StringUtils.trim( version );
221 packaging = StringUtils.trim( packaging );
223 List<FileMetadata> fileMetadatas = getSessionFilesList();
224 if ( fileMetadatas == null || fileMetadatas.isEmpty() )
226 return Boolean.FALSE;
231 ManagedRepository managedRepository = managedRepositoryAdmin.getManagedRepository( repositoryId );
233 if ( managedRepository == null )
236 throw new ArchivaRestServiceException( "Cannot find managed repository with id " + repositoryId,
237 Response.Status.BAD_REQUEST.getStatusCode(), null );
240 if ( VersionUtil.isSnapshot( version ) && !managedRepository.isSnapshots() )
243 throw new ArchivaRestServiceException(
244 "Managed repository with id " + repositoryId + " do not accept snapshots",
245 Response.Status.BAD_REQUEST.getStatusCode(), null );
248 catch ( RepositoryAdminException e )
250 throw new ArchivaRestServiceException( e.getMessage(),
251 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
254 // get from the session file with groupId/artifactId
256 Iterable<FileMetadata> filesToAdd = Iterables.filter( fileMetadatas, new Predicate<FileMetadata>()
258 public boolean apply( FileMetadata fileMetadata )
260 return fileMetadata != null && !fileMetadata.isPomFile();
263 Iterator<FileMetadata> iterator = filesToAdd.iterator();
264 boolean pomGenerated = false;
265 while ( iterator.hasNext() )
267 FileMetadata fileMetadata = iterator.next();
268 log.debug( "fileToAdd: {}", fileMetadata );
269 saveFile( repositoryId, fileMetadata, generatePom && !pomGenerated, groupId, artifactId, version,
272 deleteFile( fileMetadata.getServerFileName() );
275 filesToAdd = Iterables.filter( fileMetadatas, new Predicate<FileMetadata>()
277 public boolean apply( FileMetadata fileMetadata )
279 return fileMetadata != null && fileMetadata.isPomFile();
283 iterator = filesToAdd.iterator();
284 while ( iterator.hasNext() )
286 FileMetadata fileMetadata = iterator.next();
287 log.debug( "fileToAdd: {}", fileMetadata );
288 savePomFile( repositoryId, fileMetadata, groupId, artifactId, version, packaging );
289 deleteFile( fileMetadata.getServerFileName() );
295 protected void savePomFile( String repositoryId, FileMetadata fileMetadata, String groupId, String artifactId,
296 String version, String packaging )
297 throws ArchivaRestServiceException
302 boolean fixChecksums =
303 !( archivaAdministration.getKnownContentConsumers().contains( "create-missing-checksums" ) );
305 ManagedRepository repoConfig = managedRepositoryAdmin.getManagedRepository( repositoryId );
307 ArtifactReference artifactReference = new ArtifactReference();
308 artifactReference.setArtifactId( artifactId );
309 artifactReference.setGroupId( groupId );
310 artifactReference.setVersion( version );
311 artifactReference.setClassifier( fileMetadata.getClassifier() );
312 artifactReference.setType( packaging );
314 ManagedRepositoryContent repository = repositoryFactory.getManagedRepositoryContent( repositoryId );
316 String artifactPath = repository.toPath( artifactReference );
318 int lastIndex = artifactPath.lastIndexOf( '/' );
320 String path = artifactPath.substring( 0, lastIndex );
321 File targetPath = new File( repoConfig.getLocation(), path );
323 String pomFilename = artifactPath.substring( lastIndex + 1 );
324 if ( StringUtils.isNotEmpty( fileMetadata.getClassifier() ) )
326 pomFilename = StringUtils.remove( pomFilename, "-" + fileMetadata.getClassifier() );
328 pomFilename = FilenameUtils.removeExtension( pomFilename ) + ".pom";
330 copyFile( new File( fileMetadata.getServerFileName() ), targetPath, pomFilename, fixChecksums );
331 triggerAuditEvent( repoConfig.getId(), path + "/" + pomFilename, AuditEvent.UPLOAD_FILE );
332 queueRepositoryTask( repoConfig.getId(), new File( targetPath, pomFilename ) );
334 catch ( IOException ie )
336 throw new ArchivaRestServiceException( "Error encountered while uploading pom file: " + ie.getMessage(),
337 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), ie );
339 catch ( RepositoryException rep )
341 throw new ArchivaRestServiceException( "Repository exception: " + rep.getMessage(),
342 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), rep );
344 catch ( RepositoryAdminException e )
346 throw new ArchivaRestServiceException( "RepositoryAdmin exception: " + e.getMessage(),
347 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
351 protected void saveFile( String repositoryId, FileMetadata fileMetadata, boolean generatePom, String groupId,
352 String artifactId, String version, String packaging )
353 throws ArchivaRestServiceException
358 ManagedRepository repoConfig = managedRepositoryAdmin.getManagedRepository( repositoryId );
360 ArtifactReference artifactReference = new ArtifactReference();
361 artifactReference.setArtifactId( artifactId );
362 artifactReference.setGroupId( groupId );
363 artifactReference.setVersion( version );
364 artifactReference.setClassifier( fileMetadata.getClassifier() );
365 artifactReference.setType( packaging );
367 ManagedRepositoryContent repository = repositoryFactory.getManagedRepositoryContent( repositoryId );
369 String artifactPath = repository.toPath( artifactReference );
371 int lastIndex = artifactPath.lastIndexOf( '/' );
373 String path = artifactPath.substring( 0, lastIndex );
374 File targetPath = new File( repoConfig.getLocation(), path );
376 log.debug( "artifactPath: {} found targetPath: {}", artifactPath, targetPath );
378 Date lastUpdatedTimestamp = Calendar.getInstance().getTime();
379 int newBuildNumber = -1;
380 String timestamp = null;
382 File versionMetadataFile = new File( targetPath, MetadataTools.MAVEN_METADATA );
383 ArchivaRepositoryMetadata versionMetadata = getMetadata( versionMetadataFile );
385 if ( VersionUtil.isSnapshot( version ) )
387 TimeZone timezone = TimeZone.getTimeZone( "UTC" );
388 DateFormat fmt = new SimpleDateFormat( "yyyyMMdd.HHmmss" );
389 fmt.setTimeZone( timezone );
390 timestamp = fmt.format( lastUpdatedTimestamp );
391 if ( versionMetadata.getSnapshotVersion() != null )
393 newBuildNumber = versionMetadata.getSnapshotVersion().getBuildNumber() + 1;
401 if ( !targetPath.exists() )
406 String filename = artifactPath.substring( lastIndex + 1 );
407 if ( VersionUtil.isSnapshot( version ) )
409 filename = filename.replaceAll( VersionUtil.SNAPSHOT, timestamp + "-" + newBuildNumber );
412 boolean fixChecksums =
413 !( archivaAdministration.getKnownContentConsumers().contains( "create-missing-checksums" ) );
417 File targetFile = new File( targetPath, filename );
418 if ( targetFile.exists() && !VersionUtil.isSnapshot( version ) && repoConfig.isBlockRedeployments() )
420 throw new ArchivaRestServiceException(
421 "Overwriting released artifacts in repository '" + repoConfig.getId() + "' is not allowed.",
422 Response.Status.BAD_REQUEST.getStatusCode(), null );
426 copyFile( new File( fileMetadata.getServerFileName() ), targetPath, filename, fixChecksums );
427 triggerAuditEvent( repository.getId(), path + "/" + filename, AuditEvent.UPLOAD_FILE );
428 queueRepositoryTask( repository.getId(), targetFile );
431 catch ( IOException ie )
433 throw new ArchivaRestServiceException(
434 "Overwriting released artifacts in repository '" + repoConfig.getId() + "' is not allowed.",
435 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), ie );
440 String pomFilename = filename;
441 if ( StringUtils.isNotEmpty( fileMetadata.getClassifier() ) )
443 pomFilename = StringUtils.remove( pomFilename, "-" + fileMetadata.getClassifier() );
445 pomFilename = FilenameUtils.removeExtension( pomFilename ) + ".pom";
449 File generatedPomFile =
450 createPom( targetPath, pomFilename, fileMetadata, groupId, artifactId, version, packaging );
451 triggerAuditEvent( repoConfig.getId(), path + "/" + pomFilename, AuditEvent.UPLOAD_FILE );
454 fixChecksums( generatedPomFile );
456 queueRepositoryTask( repoConfig.getId(), generatedPomFile );
458 catch ( IOException ie )
460 throw new ArchivaRestServiceException(
461 "Error encountered while writing pom file: " + ie.getMessage(),
462 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), ie );
466 // explicitly update only if metadata-updater consumer is not enabled!
467 if ( !archivaAdministration.getKnownContentConsumers().contains( "metadata-updater" ) )
469 updateProjectMetadata( targetPath.getAbsolutePath(), lastUpdatedTimestamp, timestamp, newBuildNumber,
470 fixChecksums, fileMetadata, groupId, artifactId, version, packaging );
472 if ( VersionUtil.isSnapshot( version ) )
474 updateVersionMetadata( versionMetadata, versionMetadataFile, lastUpdatedTimestamp, timestamp,
475 newBuildNumber, fixChecksums, fileMetadata, groupId, artifactId, version,
480 catch ( RepositoryNotFoundException re )
482 throw new ArchivaRestServiceException( "Target repository cannot be found: " + re.getMessage(),
483 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), re );
485 catch ( RepositoryException rep )
487 throw new ArchivaRestServiceException( "Repository exception: " + rep.getMessage(),
488 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), rep );
490 catch ( RepositoryAdminException e )
492 throw new ArchivaRestServiceException( "RepositoryAdmin exception: " + e.getMessage(),
493 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
497 private ArchivaRepositoryMetadata getMetadata( File metadataFile )
498 throws RepositoryMetadataException
500 ArchivaRepositoryMetadata metadata = new ArchivaRepositoryMetadata();
501 if ( metadataFile.exists() )
505 metadata = MavenMetadataReader.read( metadataFile );
507 catch ( XMLException e )
509 throw new RepositoryMetadataException( e.getMessage(), e );
515 private File createPom( File targetPath, String filename, FileMetadata fileMetadata, String groupId,
516 String artifactId, String version, String packaging )
519 Model projectModel = new Model();
520 projectModel.setModelVersion( "4.0.0" );
521 projectModel.setGroupId( groupId );
522 projectModel.setArtifactId( artifactId );
523 projectModel.setVersion( version );
524 projectModel.setPackaging( packaging );
526 File pomFile = new File( targetPath, filename );
527 MavenXpp3Writer writer = new MavenXpp3Writer();
528 FileWriter w = new FileWriter( pomFile );
531 writer.write( w, projectModel );
535 IOUtils.closeQuietly( w );
541 private void fixChecksums( File file )
543 ChecksummedFile checksum = new ChecksummedFile( file );
544 checksum.fixChecksums( algorithms );
547 private void queueRepositoryTask( String repositoryId, File localFile )
549 RepositoryTask task = new RepositoryTask();
550 task.setRepositoryId( repositoryId );
551 task.setResourceFile( localFile );
552 task.setUpdateRelatedArtifacts( true );
553 task.setScanAll( false );
557 scheduler.queueTask( task );
559 catch ( TaskQueueException e )
561 log.error( "Unable to queue repository task to execute consumers on resource file ['" + localFile.getName()
566 private void copyFile( File sourceFile, File targetPath, String targetFilename, boolean fixChecksums )
569 FileOutputStream out = new FileOutputStream( new File( targetPath, targetFilename ) );
570 FileInputStream input = new FileInputStream( sourceFile );
574 IOUtils.copy( input, out );
584 fixChecksums( new File( targetPath, targetFilename ) );
589 * Update artifact level metadata. If it does not exist, create the metadata and fix checksums if necessary.
591 private void updateProjectMetadata( String targetPath, Date lastUpdatedTimestamp, String timestamp, int buildNumber,
592 boolean fixChecksums, FileMetadata fileMetadata, String groupId,
593 String artifactId, String version, String packaging )
594 throws RepositoryMetadataException
596 List<String> availableVersions = new ArrayList<String>();
597 String latestVersion = version;
599 File projectDir = new File( targetPath ).getParentFile();
600 File projectMetadataFile = new File( projectDir, MetadataTools.MAVEN_METADATA );
602 ArchivaRepositoryMetadata projectMetadata = getMetadata( projectMetadataFile );
604 if ( projectMetadataFile.exists() )
606 availableVersions = projectMetadata.getAvailableVersions();
608 Collections.sort( availableVersions, VersionComparator.getInstance() );
610 if ( !availableVersions.contains( version ) )
612 availableVersions.add( version );
615 latestVersion = availableVersions.get( availableVersions.size() - 1 );
619 availableVersions.add( version );
621 projectMetadata.setGroupId( groupId );
622 projectMetadata.setArtifactId( artifactId );
625 if ( projectMetadata.getGroupId() == null )
627 projectMetadata.setGroupId( groupId );
630 if ( projectMetadata.getArtifactId() == null )
632 projectMetadata.setArtifactId( artifactId );
635 projectMetadata.setLatestVersion( latestVersion );
636 projectMetadata.setLastUpdatedTimestamp( lastUpdatedTimestamp );
637 projectMetadata.setAvailableVersions( availableVersions );
639 if ( !VersionUtil.isSnapshot( version ) )
641 projectMetadata.setReleasedVersion( latestVersion );
644 RepositoryMetadataWriter.write( projectMetadata, projectMetadataFile );
648 fixChecksums( projectMetadataFile );
653 * Update version level metadata for snapshot artifacts. If it does not exist, create the metadata and fix checksums
656 private void updateVersionMetadata( ArchivaRepositoryMetadata metadata, File metadataFile,
657 Date lastUpdatedTimestamp, String timestamp, int buildNumber,
658 boolean fixChecksums, FileMetadata fileMetadata, String groupId,
659 String artifactId, String version, String packaging )
660 throws RepositoryMetadataException
662 if ( !metadataFile.exists() )
664 metadata.setGroupId( groupId );
665 metadata.setArtifactId( artifactId );
666 metadata.setVersion( version );
669 if ( metadata.getSnapshotVersion() == null )
671 metadata.setSnapshotVersion( new SnapshotVersion() );
674 metadata.getSnapshotVersion().setBuildNumber( buildNumber );
675 metadata.getSnapshotVersion().setTimestamp( timestamp );
676 metadata.setLastUpdatedTimestamp( lastUpdatedTimestamp );
678 RepositoryMetadataWriter.write( metadata, metadataFile );
682 fixChecksums( metadataFile );