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 ) );
187 // try with full name as ui only know the file name
190 removed = getSessionFileMetadatas().remove( new FileMetadata( file.getPath() ) );
194 return file.delete();
196 return Boolean.FALSE;
199 public Boolean clearUploadedFiles()
200 throws ArchivaRestServiceException
202 List<FileMetadata> fileMetadatas = new ArrayList( getSessionFileMetadatas() );
203 for ( FileMetadata fileMetadata : fileMetadatas )
205 deleteFile( new File( fileMetadata.getServerFileName() ).getPath() );
207 getSessionFileMetadatas().clear();
211 public List<FileMetadata> getSessionFileMetadatas()
212 throws ArchivaRestServiceException
214 List<FileMetadata> fileMetadatas =
215 (List<FileMetadata>) httpServletRequest.getSession().getAttribute( FILES_SESSION_KEY );
217 return fileMetadatas == null ? Collections.<FileMetadata>emptyList() : fileMetadatas;
220 public Boolean save( String repositoryId, String groupId, String artifactId, String version, String packaging,
221 boolean generatePom )
222 throws ArchivaRestServiceException
224 repositoryId = StringUtils.trim( repositoryId );
225 groupId = StringUtils.trim( groupId );
226 artifactId = StringUtils.trim( artifactId );
227 version = StringUtils.trim( version );
228 packaging = StringUtils.trim( packaging );
230 List<FileMetadata> fileMetadatas = getSessionFilesList();
231 if ( fileMetadatas == null || fileMetadatas.isEmpty() )
233 return Boolean.FALSE;
238 ManagedRepository managedRepository = managedRepositoryAdmin.getManagedRepository( repositoryId );
240 if ( managedRepository == null )
243 throw new ArchivaRestServiceException( "Cannot find managed repository with id " + repositoryId,
244 Response.Status.BAD_REQUEST.getStatusCode(), null );
247 if ( VersionUtil.isSnapshot( version ) && !managedRepository.isSnapshots() )
250 throw new ArchivaRestServiceException(
251 "Managed repository with id " + repositoryId + " do not accept snapshots",
252 Response.Status.BAD_REQUEST.getStatusCode(), null );
255 catch ( RepositoryAdminException e )
257 throw new ArchivaRestServiceException( e.getMessage(),
258 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
261 // get from the session file with groupId/artifactId
263 Iterable<FileMetadata> filesToAdd = Iterables.filter( fileMetadatas, new Predicate<FileMetadata>()
265 public boolean apply( FileMetadata fileMetadata )
267 return fileMetadata != null && !fileMetadata.isPomFile();
270 Iterator<FileMetadata> iterator = filesToAdd.iterator();
271 boolean pomGenerated = false;
272 while ( iterator.hasNext() )
274 FileMetadata fileMetadata = iterator.next();
275 log.debug( "fileToAdd: {}", fileMetadata );
276 saveFile( repositoryId, fileMetadata, generatePom && !pomGenerated, groupId, artifactId, version,
279 deleteFile( fileMetadata.getServerFileName() );
282 filesToAdd = Iterables.filter( fileMetadatas, new Predicate<FileMetadata>()
284 public boolean apply( FileMetadata fileMetadata )
286 return fileMetadata != null && fileMetadata.isPomFile();
290 iterator = filesToAdd.iterator();
291 while ( iterator.hasNext() )
293 FileMetadata fileMetadata = iterator.next();
294 log.debug( "fileToAdd: {}", fileMetadata );
295 savePomFile( repositoryId, fileMetadata, groupId, artifactId, version, packaging );
296 deleteFile( fileMetadata.getServerFileName() );
302 protected void savePomFile( String repositoryId, FileMetadata fileMetadata, String groupId, String artifactId,
303 String version, String packaging )
304 throws ArchivaRestServiceException
309 boolean fixChecksums =
310 !( archivaAdministration.getKnownContentConsumers().contains( "create-missing-checksums" ) );
312 ManagedRepository repoConfig = managedRepositoryAdmin.getManagedRepository( repositoryId );
314 ArtifactReference artifactReference = new ArtifactReference();
315 artifactReference.setArtifactId( artifactId );
316 artifactReference.setGroupId( groupId );
317 artifactReference.setVersion( version );
318 artifactReference.setClassifier( fileMetadata.getClassifier() );
319 artifactReference.setType( packaging );
321 ManagedRepositoryContent repository = repositoryFactory.getManagedRepositoryContent( repositoryId );
323 String artifactPath = repository.toPath( artifactReference );
325 int lastIndex = artifactPath.lastIndexOf( '/' );
327 String path = artifactPath.substring( 0, lastIndex );
328 File targetPath = new File( repoConfig.getLocation(), path );
330 String pomFilename = artifactPath.substring( lastIndex + 1 );
331 if ( StringUtils.isNotEmpty( fileMetadata.getClassifier() ) )
333 pomFilename = StringUtils.remove( pomFilename, "-" + fileMetadata.getClassifier() );
335 pomFilename = FilenameUtils.removeExtension( pomFilename ) + ".pom";
337 copyFile( new File( fileMetadata.getServerFileName() ), targetPath, pomFilename, fixChecksums );
338 triggerAuditEvent( repoConfig.getId(), path + "/" + pomFilename, AuditEvent.UPLOAD_FILE );
339 queueRepositoryTask( repoConfig.getId(), new File( targetPath, pomFilename ) );
341 catch ( IOException ie )
343 throw new ArchivaRestServiceException( "Error encountered while uploading pom file: " + ie.getMessage(),
344 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), ie );
346 catch ( RepositoryException rep )
348 throw new ArchivaRestServiceException( "Repository exception: " + rep.getMessage(),
349 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), rep );
351 catch ( RepositoryAdminException e )
353 throw new ArchivaRestServiceException( "RepositoryAdmin exception: " + e.getMessage(),
354 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
358 protected void saveFile( String repositoryId, FileMetadata fileMetadata, boolean generatePom, String groupId,
359 String artifactId, String version, String packaging )
360 throws ArchivaRestServiceException
365 ManagedRepository repoConfig = managedRepositoryAdmin.getManagedRepository( repositoryId );
367 ArtifactReference artifactReference = new ArtifactReference();
368 artifactReference.setArtifactId( artifactId );
369 artifactReference.setGroupId( groupId );
370 artifactReference.setVersion( version );
371 artifactReference.setClassifier( fileMetadata.getClassifier() );
372 artifactReference.setType(
373 StringUtils.isEmpty( fileMetadata.getPackaging() ) ? packaging : fileMetadata.getPackaging() );
375 ManagedRepositoryContent repository = repositoryFactory.getManagedRepositoryContent( repositoryId );
377 String artifactPath = repository.toPath( artifactReference );
379 int lastIndex = artifactPath.lastIndexOf( '/' );
381 String path = artifactPath.substring( 0, lastIndex );
382 File targetPath = new File( repoConfig.getLocation(), path );
384 log.debug( "artifactPath: {} found targetPath: {}", artifactPath, targetPath );
386 Date lastUpdatedTimestamp = Calendar.getInstance().getTime();
387 int newBuildNumber = -1;
388 String timestamp = null;
390 File versionMetadataFile = new File( targetPath, MetadataTools.MAVEN_METADATA );
391 ArchivaRepositoryMetadata versionMetadata = getMetadata( versionMetadataFile );
393 if ( VersionUtil.isSnapshot( version ) )
395 TimeZone timezone = TimeZone.getTimeZone( "UTC" );
396 DateFormat fmt = new SimpleDateFormat( "yyyyMMdd.HHmmss" );
397 fmt.setTimeZone( timezone );
398 timestamp = fmt.format( lastUpdatedTimestamp );
399 if ( versionMetadata.getSnapshotVersion() != null )
401 newBuildNumber = versionMetadata.getSnapshotVersion().getBuildNumber() + 1;
409 if ( !targetPath.exists() )
414 String filename = artifactPath.substring( lastIndex + 1 );
415 if ( VersionUtil.isSnapshot( version ) )
417 filename = filename.replaceAll( VersionUtil.SNAPSHOT, timestamp + "-" + newBuildNumber );
420 boolean fixChecksums =
421 !( archivaAdministration.getKnownContentConsumers().contains( "create-missing-checksums" ) );
425 File targetFile = new File( targetPath, filename );
426 if ( targetFile.exists() && !VersionUtil.isSnapshot( version ) && repoConfig.isBlockRedeployments() )
428 throw new ArchivaRestServiceException(
429 "Overwriting released artifacts in repository '" + repoConfig.getId() + "' is not allowed.",
430 Response.Status.BAD_REQUEST.getStatusCode(), null );
434 copyFile( new File( fileMetadata.getServerFileName() ), targetPath, filename, fixChecksums );
435 triggerAuditEvent( repository.getId(), path + "/" + filename, AuditEvent.UPLOAD_FILE );
436 queueRepositoryTask( repository.getId(), targetFile );
439 catch ( IOException ie )
441 log.error( "IOException copying file: {}", ie.getMessage(), ie );
442 throw new ArchivaRestServiceException(
443 "Overwriting released artifacts in repository '" + repoConfig.getId() + "' is not allowed.",
444 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), ie );
449 String pomFilename = filename;
450 if ( StringUtils.isNotEmpty( fileMetadata.getClassifier() ) )
452 pomFilename = StringUtils.remove( pomFilename, "-" + fileMetadata.getClassifier() );
454 pomFilename = FilenameUtils.removeExtension( pomFilename ) + ".pom";
458 File generatedPomFile =
459 createPom( targetPath, pomFilename, fileMetadata, groupId, artifactId, version, packaging );
460 triggerAuditEvent( repoConfig.getId(), path + "/" + pomFilename, AuditEvent.UPLOAD_FILE );
463 fixChecksums( generatedPomFile );
465 queueRepositoryTask( repoConfig.getId(), generatedPomFile );
467 catch ( IOException ie )
469 throw new ArchivaRestServiceException(
470 "Error encountered while writing pom file: " + ie.getMessage(),
471 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), ie );
475 // explicitly update only if metadata-updater consumer is not enabled!
476 if ( !archivaAdministration.getKnownContentConsumers().contains( "metadata-updater" ) )
478 updateProjectMetadata( targetPath.getAbsolutePath(), lastUpdatedTimestamp, timestamp, newBuildNumber,
479 fixChecksums, fileMetadata, groupId, artifactId, version, packaging );
481 if ( VersionUtil.isSnapshot( version ) )
483 updateVersionMetadata( versionMetadata, versionMetadataFile, lastUpdatedTimestamp, timestamp,
484 newBuildNumber, fixChecksums, fileMetadata, groupId, artifactId, version,
489 catch ( RepositoryNotFoundException re )
491 throw new ArchivaRestServiceException( "Target repository cannot be found: " + re.getMessage(),
492 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), re );
494 catch ( RepositoryException rep )
496 throw new ArchivaRestServiceException( "Repository exception: " + rep.getMessage(),
497 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), rep );
499 catch ( RepositoryAdminException e )
501 throw new ArchivaRestServiceException( "RepositoryAdmin exception: " + e.getMessage(),
502 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
506 private ArchivaRepositoryMetadata getMetadata( File metadataFile )
507 throws RepositoryMetadataException
509 ArchivaRepositoryMetadata metadata = new ArchivaRepositoryMetadata();
510 if ( metadataFile.exists() )
514 metadata = MavenMetadataReader.read( metadataFile );
516 catch ( XMLException e )
518 throw new RepositoryMetadataException( e.getMessage(), e );
524 private File createPom( File targetPath, String filename, FileMetadata fileMetadata, String groupId,
525 String artifactId, String version, String packaging )
528 Model projectModel = new Model();
529 projectModel.setModelVersion( "4.0.0" );
530 projectModel.setGroupId( groupId );
531 projectModel.setArtifactId( artifactId );
532 projectModel.setVersion( version );
533 projectModel.setPackaging( packaging );
535 File pomFile = new File( targetPath, filename );
536 MavenXpp3Writer writer = new MavenXpp3Writer();
537 FileWriter w = new FileWriter( pomFile );
540 writer.write( w, projectModel );
544 IOUtils.closeQuietly( w );
550 private void fixChecksums( File file )
552 ChecksummedFile checksum = new ChecksummedFile( file );
553 checksum.fixChecksums( algorithms );
556 private void queueRepositoryTask( String repositoryId, File localFile )
558 RepositoryTask task = new RepositoryTask();
559 task.setRepositoryId( repositoryId );
560 task.setResourceFile( localFile );
561 task.setUpdateRelatedArtifacts( true );
562 task.setScanAll( false );
566 scheduler.queueTask( task );
568 catch ( TaskQueueException e )
570 log.error( "Unable to queue repository task to execute consumers on resource file ['" + localFile.getName()
575 private void copyFile( File sourceFile, File targetPath, String targetFilename, boolean fixChecksums )
578 FileOutputStream out = new FileOutputStream( new File( targetPath, targetFilename ) );
579 FileInputStream input = new FileInputStream( sourceFile );
583 IOUtils.copy( input, out );
593 fixChecksums( new File( targetPath, targetFilename ) );
598 * Update artifact level metadata. If it does not exist, create the metadata and fix checksums if necessary.
600 private void updateProjectMetadata( String targetPath, Date lastUpdatedTimestamp, String timestamp, int buildNumber,
601 boolean fixChecksums, FileMetadata fileMetadata, String groupId,
602 String artifactId, String version, String packaging )
603 throws RepositoryMetadataException
605 List<String> availableVersions = new ArrayList<String>();
606 String latestVersion = version;
608 File projectDir = new File( targetPath ).getParentFile();
609 File projectMetadataFile = new File( projectDir, MetadataTools.MAVEN_METADATA );
611 ArchivaRepositoryMetadata projectMetadata = getMetadata( projectMetadataFile );
613 if ( projectMetadataFile.exists() )
615 availableVersions = projectMetadata.getAvailableVersions();
617 Collections.sort( availableVersions, VersionComparator.getInstance() );
619 if ( !availableVersions.contains( version ) )
621 availableVersions.add( version );
624 latestVersion = availableVersions.get( availableVersions.size() - 1 );
628 availableVersions.add( version );
630 projectMetadata.setGroupId( groupId );
631 projectMetadata.setArtifactId( artifactId );
634 if ( projectMetadata.getGroupId() == null )
636 projectMetadata.setGroupId( groupId );
639 if ( projectMetadata.getArtifactId() == null )
641 projectMetadata.setArtifactId( artifactId );
644 projectMetadata.setLatestVersion( latestVersion );
645 projectMetadata.setLastUpdatedTimestamp( lastUpdatedTimestamp );
646 projectMetadata.setAvailableVersions( availableVersions );
648 if ( !VersionUtil.isSnapshot( version ) )
650 projectMetadata.setReleasedVersion( latestVersion );
653 RepositoryMetadataWriter.write( projectMetadata, projectMetadataFile );
657 fixChecksums( projectMetadataFile );
662 * Update version level metadata for snapshot artifacts. If it does not exist, create the metadata and fix checksums
665 private void updateVersionMetadata( ArchivaRepositoryMetadata metadata, File metadataFile,
666 Date lastUpdatedTimestamp, String timestamp, int buildNumber,
667 boolean fixChecksums, FileMetadata fileMetadata, String groupId,
668 String artifactId, String version, String packaging )
669 throws RepositoryMetadataException
671 if ( !metadataFile.exists() )
673 metadata.setGroupId( groupId );
674 metadata.setArtifactId( artifactId );
675 metadata.setVersion( version );
678 if ( metadata.getSnapshotVersion() == null )
680 metadata.setSnapshotVersion( new SnapshotVersion() );
683 metadata.getSnapshotVersion().setBuildNumber( buildNumber );
684 metadata.getSnapshotVersion().setTimestamp( timestamp );
685 metadata.setLastUpdatedTimestamp( lastUpdatedTimestamp );
687 RepositoryMetadataWriter.write( metadata, metadataFile );
691 fixChecksums( metadataFile );