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, final String groupId, final String artifactId, String version,
214 String packaging, final boolean generatePom )
215 throws ArchivaRestServiceException
217 List<FileMetadata> fileMetadatas = getSessionFilesList();
218 if ( fileMetadatas == null || fileMetadatas.isEmpty() )
220 return Boolean.FALSE;
225 ManagedRepository managedRepository = managedRepositoryAdmin.getManagedRepository( repositoryId );
227 if ( managedRepository == null )
230 throw new ArchivaRestServiceException( "Cannot find managed repository with id " + repositoryId,
231 Response.Status.BAD_REQUEST.getStatusCode(), null );
234 if ( VersionUtil.isSnapshot( version ) && !managedRepository.isSnapshots() )
237 throw new ArchivaRestServiceException(
238 "Managed repository with id " + repositoryId + " do not accept snapshots",
239 Response.Status.BAD_REQUEST.getStatusCode(), null );
242 catch ( RepositoryAdminException e )
244 throw new ArchivaRestServiceException( e.getMessage(),
245 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
248 // get from the session file with groupId/artifactId
250 Iterable<FileMetadata> filesToAdd = Iterables.filter( fileMetadatas, new Predicate<FileMetadata>()
252 public boolean apply( FileMetadata fileMetadata )
254 return fileMetadata != null && !fileMetadata.isPomFile();
257 Iterator<FileMetadata> iterator = filesToAdd.iterator();
258 boolean pomGenerated = false;
259 while ( iterator.hasNext() )
261 FileMetadata fileMetadata = iterator.next();
262 log.debug( "fileToAdd: {}", fileMetadata );
263 saveFile( repositoryId, fileMetadata, generatePom && !pomGenerated, groupId, artifactId, version,
266 deleteFile( fileMetadata.getServerFileName() );
269 filesToAdd = Iterables.filter( fileMetadatas, new Predicate<FileMetadata>()
271 public boolean apply( FileMetadata fileMetadata )
273 return fileMetadata != null && fileMetadata.isPomFile();
277 iterator = filesToAdd.iterator();
278 while ( iterator.hasNext() )
280 FileMetadata fileMetadata = iterator.next();
281 log.debug( "fileToAdd: {}", fileMetadata );
282 savePomFile( repositoryId, fileMetadata, groupId, artifactId, version, packaging );
283 deleteFile( fileMetadata.getServerFileName() );
289 protected void savePomFile( String repositoryId, FileMetadata fileMetadata, String groupId, String artifactId,
290 String version, String packaging )
291 throws ArchivaRestServiceException
296 boolean fixChecksums =
297 !( archivaAdministration.getKnownContentConsumers().contains( "create-missing-checksums" ) );
299 ManagedRepository repoConfig = managedRepositoryAdmin.getManagedRepository( repositoryId );
301 ArtifactReference artifactReference = new ArtifactReference();
302 artifactReference.setArtifactId( artifactId );
303 artifactReference.setGroupId( groupId );
304 artifactReference.setVersion( version );
305 artifactReference.setClassifier( fileMetadata.getClassifier() );
306 artifactReference.setType( packaging );
308 ManagedRepositoryContent repository = repositoryFactory.getManagedRepositoryContent( repositoryId );
310 String artifactPath = repository.toPath( artifactReference );
312 int lastIndex = artifactPath.lastIndexOf( '/' );
314 String path = artifactPath.substring( 0, lastIndex );
315 File targetPath = new File( repoConfig.getLocation(), path );
317 String pomFilename = artifactPath.substring( lastIndex + 1 );
318 if ( StringUtils.isNotEmpty( fileMetadata.getClassifier() ) )
320 pomFilename = StringUtils.remove( pomFilename, "-" + fileMetadata.getClassifier() );
322 pomFilename = FilenameUtils.removeExtension( pomFilename ) + ".pom";
324 copyFile( new File( fileMetadata.getServerFileName() ), targetPath, pomFilename, fixChecksums );
325 triggerAuditEvent( repoConfig.getId(), path + "/" + pomFilename, AuditEvent.UPLOAD_FILE );
326 queueRepositoryTask( repoConfig.getId(), new File( targetPath, pomFilename ) );
328 catch ( IOException ie )
330 throw new ArchivaRestServiceException( "Error encountered while uploading pom file: " + ie.getMessage(),
331 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), ie );
333 catch ( RepositoryException rep )
335 throw new ArchivaRestServiceException( "Repository exception: " + rep.getMessage(),
336 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), rep );
338 catch ( RepositoryAdminException e )
340 throw new ArchivaRestServiceException( "RepositoryAdmin exception: " + e.getMessage(),
341 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
345 protected void saveFile( String repositoryId, FileMetadata fileMetadata, boolean generatePom, String groupId,
346 String artifactId, String version, String packaging )
347 throws ArchivaRestServiceException
352 ManagedRepository repoConfig = managedRepositoryAdmin.getManagedRepository( repositoryId );
354 ArtifactReference artifactReference = new ArtifactReference();
355 artifactReference.setArtifactId( artifactId );
356 artifactReference.setGroupId( groupId );
357 artifactReference.setVersion( version );
358 artifactReference.setClassifier( fileMetadata.getClassifier() );
359 artifactReference.setType( packaging );
361 ManagedRepositoryContent repository = repositoryFactory.getManagedRepositoryContent( repositoryId );
363 String artifactPath = repository.toPath( artifactReference );
365 int lastIndex = artifactPath.lastIndexOf( '/' );
367 String path = artifactPath.substring( 0, lastIndex );
368 File targetPath = new File( repoConfig.getLocation(), path );
370 log.debug( "artifactPath: {} found targetPath: {}", artifactPath, targetPath );
372 Date lastUpdatedTimestamp = Calendar.getInstance().getTime();
373 int newBuildNumber = -1;
374 String timestamp = null;
376 File versionMetadataFile = new File( targetPath, MetadataTools.MAVEN_METADATA );
377 ArchivaRepositoryMetadata versionMetadata = getMetadata( versionMetadataFile );
379 if ( VersionUtil.isSnapshot( version ) )
381 TimeZone timezone = TimeZone.getTimeZone( "UTC" );
382 DateFormat fmt = new SimpleDateFormat( "yyyyMMdd.HHmmss" );
383 fmt.setTimeZone( timezone );
384 timestamp = fmt.format( lastUpdatedTimestamp );
385 if ( versionMetadata.getSnapshotVersion() != null )
387 newBuildNumber = versionMetadata.getSnapshotVersion().getBuildNumber() + 1;
395 if ( !targetPath.exists() )
400 String filename = artifactPath.substring( lastIndex + 1 );
401 if ( VersionUtil.isSnapshot( version ) )
403 filename = filename.replaceAll( VersionUtil.SNAPSHOT, timestamp + "-" + newBuildNumber );
406 boolean fixChecksums =
407 !( archivaAdministration.getKnownContentConsumers().contains( "create-missing-checksums" ) );
411 File targetFile = new File( targetPath, filename );
412 if ( targetFile.exists() && !VersionUtil.isSnapshot( version ) && repoConfig.isBlockRedeployments() )
414 throw new ArchivaRestServiceException(
415 "Overwriting released artifacts in repository '" + repoConfig.getId() + "' is not allowed.",
416 Response.Status.BAD_REQUEST.getStatusCode(), null );
420 copyFile( new File( fileMetadata.getServerFileName() ), targetPath, filename, fixChecksums );
421 triggerAuditEvent( repository.getId(), path + "/" + filename, AuditEvent.UPLOAD_FILE );
422 queueRepositoryTask( repository.getId(), targetFile );
425 catch ( IOException ie )
427 throw new ArchivaRestServiceException(
428 "Overwriting released artifacts in repository '" + repoConfig.getId() + "' is not allowed.",
429 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), ie );
434 String pomFilename = filename;
435 if ( StringUtils.isNotEmpty( fileMetadata.getClassifier() ) )
437 pomFilename = StringUtils.remove( pomFilename, "-" + fileMetadata.getClassifier() );
439 pomFilename = FilenameUtils.removeExtension( pomFilename ) + ".pom";
443 File generatedPomFile =
444 createPom( targetPath, pomFilename, fileMetadata, groupId, artifactId, version, packaging );
445 triggerAuditEvent( repoConfig.getId(), path + "/" + pomFilename, AuditEvent.UPLOAD_FILE );
448 fixChecksums( generatedPomFile );
450 queueRepositoryTask( repoConfig.getId(), generatedPomFile );
452 catch ( IOException ie )
454 throw new ArchivaRestServiceException(
455 "Error encountered while writing pom file: " + ie.getMessage(),
456 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), ie );
460 // explicitly update only if metadata-updater consumer is not enabled!
461 if ( !archivaAdministration.getKnownContentConsumers().contains( "metadata-updater" ) )
463 updateProjectMetadata( targetPath.getAbsolutePath(), lastUpdatedTimestamp, timestamp, newBuildNumber,
464 fixChecksums, fileMetadata, groupId, artifactId, version, packaging );
466 if ( VersionUtil.isSnapshot( version ) )
468 updateVersionMetadata( versionMetadata, versionMetadataFile, lastUpdatedTimestamp, timestamp,
469 newBuildNumber, fixChecksums, fileMetadata, groupId, artifactId, version,
474 catch ( RepositoryNotFoundException re )
476 throw new ArchivaRestServiceException( "Target repository cannot be found: " + re.getMessage(),
477 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), re );
479 catch ( RepositoryException rep )
481 throw new ArchivaRestServiceException( "Repository exception: " + rep.getMessage(),
482 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), rep );
484 catch ( RepositoryAdminException e )
486 throw new ArchivaRestServiceException( "RepositoryAdmin exception: " + e.getMessage(),
487 Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), e );
491 private ArchivaRepositoryMetadata getMetadata( File metadataFile )
492 throws RepositoryMetadataException
494 ArchivaRepositoryMetadata metadata = new ArchivaRepositoryMetadata();
495 if ( metadataFile.exists() )
499 metadata = MavenMetadataReader.read( metadataFile );
501 catch ( XMLException e )
503 throw new RepositoryMetadataException( e.getMessage(), e );
509 private File createPom( File targetPath, String filename, FileMetadata fileMetadata, String groupId,
510 String artifactId, String version, String packaging )
513 Model projectModel = new Model();
514 projectModel.setModelVersion( "4.0.0" );
515 projectModel.setGroupId( groupId );
516 projectModel.setArtifactId( artifactId );
517 projectModel.setVersion( version );
518 projectModel.setPackaging( packaging );
520 File pomFile = new File( targetPath, filename );
521 MavenXpp3Writer writer = new MavenXpp3Writer();
522 FileWriter w = new FileWriter( pomFile );
525 writer.write( w, projectModel );
529 IOUtils.closeQuietly( w );
535 private void fixChecksums( File file )
537 ChecksummedFile checksum = new ChecksummedFile( file );
538 checksum.fixChecksums( algorithms );
541 private void queueRepositoryTask( String repositoryId, File localFile )
543 RepositoryTask task = new RepositoryTask();
544 task.setRepositoryId( repositoryId );
545 task.setResourceFile( localFile );
546 task.setUpdateRelatedArtifacts( true );
547 task.setScanAll( false );
551 scheduler.queueTask( task );
553 catch ( TaskQueueException e )
555 log.error( "Unable to queue repository task to execute consumers on resource file ['" + localFile.getName()
560 private void copyFile( File sourceFile, File targetPath, String targetFilename, boolean fixChecksums )
563 FileOutputStream out = new FileOutputStream( new File( targetPath, targetFilename ) );
564 FileInputStream input = new FileInputStream( sourceFile );
568 IOUtils.copy( input, out );
578 fixChecksums( new File( targetPath, targetFilename ) );
583 * Update artifact level metadata. If it does not exist, create the metadata and fix checksums if necessary.
585 private void updateProjectMetadata( String targetPath, Date lastUpdatedTimestamp, String timestamp, int buildNumber,
586 boolean fixChecksums, FileMetadata fileMetadata, String groupId,
587 String artifactId, String version, String packaging )
588 throws RepositoryMetadataException
590 List<String> availableVersions = new ArrayList<String>();
591 String latestVersion = version;
593 File projectDir = new File( targetPath ).getParentFile();
594 File projectMetadataFile = new File( projectDir, MetadataTools.MAVEN_METADATA );
596 ArchivaRepositoryMetadata projectMetadata = getMetadata( projectMetadataFile );
598 if ( projectMetadataFile.exists() )
600 availableVersions = projectMetadata.getAvailableVersions();
602 Collections.sort( availableVersions, VersionComparator.getInstance() );
604 if ( !availableVersions.contains( version ) )
606 availableVersions.add( version );
609 latestVersion = availableVersions.get( availableVersions.size() - 1 );
613 availableVersions.add( version );
615 projectMetadata.setGroupId( groupId );
616 projectMetadata.setArtifactId( artifactId );
619 if ( projectMetadata.getGroupId() == null )
621 projectMetadata.setGroupId( groupId );
624 if ( projectMetadata.getArtifactId() == null )
626 projectMetadata.setArtifactId( artifactId );
629 projectMetadata.setLatestVersion( latestVersion );
630 projectMetadata.setLastUpdatedTimestamp( lastUpdatedTimestamp );
631 projectMetadata.setAvailableVersions( availableVersions );
633 if ( !VersionUtil.isSnapshot( version ) )
635 projectMetadata.setReleasedVersion( latestVersion );
638 RepositoryMetadataWriter.write( projectMetadata, projectMetadataFile );
642 fixChecksums( projectMetadataFile );
647 * Update version level metadata for snapshot artifacts. If it does not exist, create the metadata and fix checksums
650 private void updateVersionMetadata( ArchivaRepositoryMetadata metadata, File metadataFile,
651 Date lastUpdatedTimestamp, String timestamp, int buildNumber,
652 boolean fixChecksums, FileMetadata fileMetadata, String groupId,
653 String artifactId, String version, String packaging )
654 throws RepositoryMetadataException
656 if ( !metadataFile.exists() )
658 metadata.setGroupId( groupId );
659 metadata.setArtifactId( artifactId );
660 metadata.setVersion( version );
663 if ( metadata.getSnapshotVersion() == null )
665 metadata.setSnapshotVersion( new SnapshotVersion() );
668 metadata.getSnapshotVersion().setBuildNumber( buildNumber );
669 metadata.getSnapshotVersion().setTimestamp( timestamp );
670 metadata.setLastUpdatedTimestamp( lastUpdatedTimestamp );
672 RepositoryMetadataWriter.write( metadata, metadataFile );
676 fixChecksums( metadataFile );