Create
cancel
Showing results for 
Search instead for 
Did you mean: 
Sign up Log in

Accessing jira attachment file data from a jira plugin

Carl Myers
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
June 14, 2012

I am writing a plugin which needs to access attached files and look into them, as well as create attachments.

So I have all the "managers" I need, I get an issue object, and I call issue.getAttachments() which returns com.atlassian.jira.issue.attachment.Attachment objects.

How do I get the actual file data? The Attachment object has the following getters:

getAuthor() : String

getCreated() : Timestamp

getFilename() : String (this one is the short filename, not the path, such as "image.jpg")

getFilesize() : Long

getGenericValue() : GenericValue (not deprecated, but probably should be soon)

getId() : Long

getIssue : GenericValue (deprecated)

getIssueObject : Issue

getMimetype() : String

getProperties : PropertySet

getGenericValue / getString / getLong (the GV accessors)

getTimestamp(String) : Timestamp

None of these let me get at the actual attachment data... Currently, I am doing something like this:

String attachmentBasePath = applicationProperties.getString(APKeys.JIRA_PATH_ATTACHMENTS);
String pkey = attachment.getIssueObject().getProjectObject().getKey();
String ikey = attachment.getIssueObject().getKey();

StringBuilder sb = new StringBuilder()
sb.append(attachmentBasePath);
sb.append(File.separator);
sb.append(pkey);
sb.append(File.separator);
sb.append(ikey);
sb.append(File.separator);
sp.append(attachment.getId()); // *

return sb.toString();

Furthermore, if you look at the * above, in jira 4.X you used to also have to append "_image.jpg" or whatever for the filename, this has apparently changed in jira 5.X. Clearly this isn't portable or safe and can break at any time JIRA changes its internals.

Even worse, I've determiend that APKeys.JIRA_PATH_ATTACHEMNTS stored in the DB does not get updated, so for example, if you use "create-home-zip" with the plugin SDK to save a database, then restore it from some other path later, all your tests/plugin will break because it can't find the attachments anymore. Production JIRAs probably don't change paths very often, but that makes this no less terrible. How do I do this? =(

Thanks!
-Carl

6 answers

1 accepted

Comments for this post are closed

Community moderators have prevented the ability to post new answers.

Post a new question

1 vote
Answer accepted
Carl Myers
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
June 14, 2012

I found the canonical implementation I think.

/**                                                                                                                                                                                                                                                                                
     * Returns the physical File for the given Attachment.                                                                                                                                                                                                                             
     * This method performs better as it does not need to look up the issue object.                                                                                                                                                                                                    
     *                                                                                                                                                                                                                                                                                 
     * @param issue the issue the attachment belongs to.                                                                                                                                                                                                                               
     * @param attachment the attachment.                                                                                                                                                                                                                                               
     * @return the file.                                                                                                                                                                                                                                                               
     * @throws DataAccessException on failure getting required attachment info.                                                                                                                                                                                                        
     */                                                                                                                                                                                                                                                                                
    public static File getAttachmentFile(Issue issue, Attachment attachment) throws DataAccessException                                                                                                                                                                                
    {                                                                                                                                                                                                                                                                                  
        final File attachmentDir = getAttachmentDirectory(issue);                                                                                                                                                                                                                      
        return getAttachmentFile(AttachmentAdapter.fromAttachment(attachment), attachmentDir);                                                                                                                                                                                         
    }

This is in the AttachmentUtils class. I will use this and hopefully it won't break... but I'd still really like to see the atlassian dev's comments about the JIRA_PATH_ATTACHMENTS ApplicationProperty not being updated, as looking through the code I see it still uses that.

Carl Myers
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
June 14, 2012

Another problem with this is, the AttachmentUtils method is static, meaning you can't write tests around it (if you call them outside of jira, you get an IllegalStateException sine the ComponentManager isn't initialized - this code should be moved to a non-static manager). I'd really like to have a jira dev weigh in, and I can file a jira issue if you like.

5 votes
Olivier Garand June 2, 2016

Hi everyone, I know this thread is old but here is an example implementation of InputStreamConsumer.

Hope it helps!

 

public class FileInputStreamConsumer implements InputStreamConsumer<File>{
	
	private final String filename;
	public FileInputStreamConsumer(String filename) {
		this.filename = filename;
	}
	@Override
	public File withInputStream(InputStream is) throws IOException {
		final File f = File.createTempFile(FilenameUtils.getBaseName(filename), FilenameUtils.getExtension(filename));
		StreamUtils.copy(is, new FileOutputStream(f));
		return f;
	}
}
Olivier Garand June 2, 2016

Then you can use it as you like to get files from attachement:

for (final Attachment a : attachs) {
				File f = null;
				try {
					f = am.streamAttachmentContent(a, new FileInputStreamConsumer(a.getFilename()));
				}
				catch (final IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				if (f != null && f.exists()) {
					paths.add(f);
				}
			}

 

 

Like Arnold Lite likes this
Guilherme Nogueira
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
June 21, 2016

OMG! Thank you!!

Soporte Excentia October 1, 2018

Good job Olivier. It was really helpful.

5 votes
ArtUrlWWW April 2, 2014

And, for Jira 6.2

String filePath = PathUtils.joinPaths(ComponentAccessor.getAttachmentPathManager().getDefaultAttachmentPath(), issue.getProjectObject().getKey(), issue.getKey(), attachment.getId().toString());
					File file = new File(filePath);
					if (file.exists()) {
						// attachments file exists...
					}

Francesco R
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
January 4, 2019

Since JIRA 6.3 there is also the bucket number in the middle of the complete path.
Using the FileAttachments class we could write:

bucketNumber = FileAttachments.computeIssueBucketDir(issue.getKey())
String filePath = PathUtils.joinPaths(ComponentAccessor.getAttachmentPathManager().getDefaultAttachmentPath(),
issue.getProjectObject().getKey(),
bucketNumber,
issue.getKey(),
attachment.getId().toString());

or better:

String attachmentPath = FileAttachments.getAttachmentDirectoryForIssue(new File(ComponentAccessor.getAttachmentPathManager().getDefaultAttachmentPath());
String filePath = PathUtils.joinPaths(attachmentPath,
issue.getProjectObject().getKey(),
issue.getKey()).toString(),
attachment.getId().toString());

in this last case you just need to know the project key , the issue key and the attachment id and you don't care about the bucket number

Like Andrea Pannitti likes this
2 votes
Christopher April 7, 2014

If project key has been modified by an administrator, attachement path keep the old project key and the "joinPaths(pathManager.attachmentPath, issue.projectObject.key, issue.key, attachment.id.toString())" proposed by Jamie return a path with the new project key. So it does not work.

I do it with AttachmentUtils which return the correct path and File:

File attachmentFile = AttachmentUtils.getAttachmentFile(attachment);

0 votes
Italo Qualisoni [e-Core]
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
June 21, 2017

Hello,

I've been using this way to retrieve the attatchment File object when needed. Maybe this method can help you too;

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.attachment.FileSystemAttachmentDirectoryAccessor
import com.atlassian.jira.issue.Issue

File getAttatchmentFile(Issue issue,String attatchmentId){
    return ComponentAccessor.getComponent(FileSystemAttachmentDirectoryAccessor.class).getAttachmentDirectory(issue).listFiles().find({
        File it->
         it.getName().equals(attatchmentId)
    });
}

Regards,

Italo Qualisoni

0 votes
Dmitry [Lucidchart] October 16, 2015

Trying to build a path to the attachment yourself is brittle, as JIRA can decide on a different directory structure (which seems to have happened with JIRA 7).

 

A better solution would be using AttachmentUtils.getAttachmentFile(attachment) as @christophe rousset suggested:

File attachmentFile = AttachmentUtils.getAttachmentFile(attachment);

 

However that method has been deprecated since 6.1. It still exists in 7.1, but if you just want to get the attachment content, it is suggested to use AttachmentManager.streamAttachmentContent(Attachment attachment, InputStreamConsumer<T> consumer) which is available since 6.1. Unfortunately, I haven't found a usage example and not sure how to construct  an InputStreamConsumer. Would appreciate an Atlassian dev jumping in.

Comments for this post are closed

Community moderators have prevented the ability to post new answers.

Post a new question

TAGS
AUG Leaders

Atlassian Community Events