How I get file upload to work to upload ogg/mp3 file to page attachment?

How I get file upload to work to upload ogg/mp3 file to page attachment (Confluence 8.6.0 API and above) and I have Confluence 8.9.1 installed on my Virtual Private Server?

Currently I use Struts 2 and file upload from my React 18 client-side to server side goes to Tomcat’s own 400 page not to Confluence 400 page and no any logs written or to catalina.out.

image

Here is pom.xml config:

<dependency>
     <groupId>org.apache.struts</groupId>
      <artifactId>struts2-core</artifactId>
       <version>6.4.0</version>
      <scope>provided</scope>
</dependency>

Servlet configs:

<filter>
        <filter-name>struts2</filter-name>
        <filter-class>org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>struts2</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <servlet name="ai-pusher-thinking-app-servlet" key="ai-pusher-thinking-app-servlet" class="fi.i4ware.dark.AiServlet">
        <description>Servlet that serves the Pusher Thinking</description>
        <url-pattern>/ai/thinking</url-pattern>
        <multipart-config>
            <max-file-size>10485760</max-file-size> <!-- 10 MB -->
            <max-request-size>20971520</max-request-size> <!-- 20 MB -->
            <file-size-threshold>5242880</file-size-threshold> <!-- 5 MB -->
        </multipart-config>
    </servlet>

React 18 codes:

const startRecording = async () => {
    try {
      const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
      const newRecorder = RecordRTC(stream, {
        type: 'audio',
        mimeType: 'audio/mp3',
      });
      newRecorder.startRecording();
      setRecorder(newRecorder);
      setIsRecording(true);
      mediaStreamRef.current = stream;
      setupAudioLevelMeter(stream);
      sendSpeechStatus(true);
    } catch (err) {
      console.error('Error accessing microphone', err);
    }
  };

const stopRecording = () => {
    recorder.stopRecording(async () => {
      const blob = recorder.getBlob();
      audioRef.current.src = URL.createObjectURL(blob);
      setAudioBlob(blob);

      const formData = new FormData();
      formData.append('audio', blob, 'uploaded_audio.ogg');
      formData.append('gender', gender);
      formData.append('apiPageId', apiPageId);

      try {
        const response = await Axios.post(apiUrl + `/plugins/servlet/ai/generate-response?action=transcribeSpeech`, formData, {
          headers: {
            'Content-Type': 'multipart/form-data'
          }
        });
        console.log('Transcription result:', response.data.transcription);
        await handleChatGPTResponse(response.data.transcription);
      } catch (error) {
        console.error('Error uploading audio file:', error);
      }

      setIsRecording(false);
      cancelAnimationFrame(animationFrameIdRef.current);
      audioContextRef.current.close();
      mediaStreamRef.current.getTracks().forEach(track => track.stop());
    });
  };

Server-side code:

import org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper;
import org.apache.struts2.dispatcher.multipart.MultiPartRequest;
import org.apache.struts2.dispatcher.multipart.UploadedFile;

// Annotate the servlet with @MultipartConfig to handle file uploads
@MultipartConfig(
    fileSizeThreshold = 5 * 1024 * 1024, // 5 MB
    maxFileSize = 10 * 1024 * 1024,      // 10 MB
    maxRequestSize = 20 * 1024 * 1024    // 20 MB
)
public class OpenAIServiceServlet extends HttpServlet {

// some code

@Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("UTF-8");
        String action = req.getParameter("action");
        // Log the action parameter
        logger.info("Action parameter: " + action);
        String result = "";

        if (!"transcribeSpeech".equals(action)) {
            StringBuilder sb = new StringBuilder();
            try (BufferedReader reader = req.getReader()) {
                String line;
                while ((line = reader.readLine()) != null) {
                    sb.append(line);
                }
            }

            String jsonString = sb.toString();
            logger.info("Received JSON string: " + jsonString);

            // Parse JSON string
            JSONObject jsonRequest;
            try {
                jsonRequest = new JSONObject(jsonString);
            } catch (Exception e) {
                resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
                JSONObject jsonResponse = new JSONObject();
                jsonResponse.put("status", "Invalid JSON format.");
                resp.getWriter().write(jsonResponse.toString());
                return;
            }

            // Log the action parameter
            logger.info("Action parameter: " + action);

            switch (action) {
                case "generateText":
                    String prompt = jsonRequest.optString("prompt");
                    result = generateText(prompt);
                    break;
                case "synthesizeSpeech":
                    String text = jsonRequest.optString("text");
                    String voice = jsonRequest.optString("voice");
                    result = synthesizeSpeech(text, voice);
                    break;
                default:
                    resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid action parameter.");
                    return;
            }
        } else {
            // Handle transcribeSpeech action
            if (req instanceof MultiPartRequestWrapper multiPartRequest) {
                UploadedFile[] uploadedFiles = multiPartRequest.getFiles("audio");
                if (uploadedFiles == null || uploadedFiles.length == 0) {
                    resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Missing audio file.");
                    return;
                }

                UploadedFile audioFile = uploadedFiles[0]; // Assuming you want the first file
                try (InputStream is = new FileInputStream(audioFile.getAbsolutePath())) {
                    String fileName = "uploaded_audio.ogg"; // MSIE fix.
                    String originalFileName = fileName;
                    DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
                    LocalDateTime now = LocalDateTime.now();
                    String timestamp = dtf.format(now);

                    String newFileName = timestamp + "_" + originalFileName;
                    String pageId = req.getParameter("pageId");

                    result = transcribeSpeech(is, newFileName, pageId);
                } catch (IOException e) {
                    throw new ServletException("Failed to read audio file", e);
                }
            } else {
                resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Request is not a multipart request.");
            }
        }

        resp.setContentType("application/json; charset=UTF-8");
        resp.getWriter().write(result);
    }

// other functions

private String transcribeSpeech(InputStream audioData, String fileName, String pageId) throws IOException {
        Settings settings = globalSettingsManager.getGlobalSettings();
        String baseUrl = settings.getBaseUrl();

        HttpPost post = new HttpPost(baseUrl + "/v1/audio/transcriptions");
        post.setHeader("Authorization", "Bearer " + apiKey);

        HttpEntity entity = MultipartEntityBuilder.create()
            .addBinaryBody("file", audioData, ContentType.DEFAULT_BINARY, fileName)
            .addTextBody("model", "whisper-1", ContentType.create("text/plain", StandardCharsets.UTF_8))
            .build();

        post.setEntity(entity);

        String transcription;
        try (CloseableHttpResponse response = httpClient.execute(post)) {
            HttpEntity responseEntity = response.getEntity();
            String responseBody = EntityUtils.toString(responseEntity, StandardCharsets.UTF_8);
            Map<String, Object> responseMap = objectMapper.readValue(responseBody, Map.class);
            transcription = (String) responseMap.get("text");
        }

        try {
            uploadAttachment(pageId, fileName, audioData);
        } catch (IOException | ServiceException e) {
            e.printStackTrace();
        }

        return transcription;
    }

    private void uploadAttachment(String pageId, String fileName, InputStream audioData) throws IOException, ServiceException {
        Page page = pageManager.getPage(Long.parseLong(pageId));
        if (page == null) {
            throw new ServiceException("Page not found");
        }

        // Create a temporary file to store the audio data
        File tempFile = File.createTempFile("audio", null);

        // Create the AttachmentUpload object
        AttachmentUpload attachmentUpload = new AttachmentUpload(tempFile, fileName, "audio/ogg", "Uploaded audio file", true);

        // Create a ContentId object
        ContentId contentId = ContentId.of(page.getId());

        // Add the attachment to a collection
        Collection<AttachmentUpload> uploads = Collections.singletonList(attachmentUpload);

        // Save the attachment to the page
        attachmentService.addAttachments(contentId, uploads);
    }

What is the problem? Purpose is to get Text-to-Speech and Speech-to-Text by ChatGPT 4o to working.

Check if your request is a MultiPartRequestWrapper and then call getFile:

if (req instanceof MultiPartRequestWrapper multiPartRequest) {
    File audioFile = multiPartRequest.getFile("audio");
}

What do I write to pom.xml get this work?

You only need confluence as dependency, e.g.:

        <dependency>
            <groupId>com.atlassian.confluence</groupId>
            <artifactId>confluence</artifactId>
            <version>${confluence.version}</version>
            <scope>provided</scope>
        </dependency>

I still get this error:

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project dark: Compilation failure
[ERROR] /C:/git/dark/src/main/java/fi/i4ware/dark/OpenAIServiceServlet.java:[37,43] package apache.struts2.dispatcher.multipart does not exist

And after put this to pom.xml build success:

<dependency>
            <groupId>org.apache.struts</groupId>
            <artifactId>struts2-core</artifactId>
            <version>6.4.0</version>
            <scope>provided</scope>
        </dependency>

But now I get error:

HTTP Status 400 – Bad Request


Type Status Report

Message Request is not a multipart request.

Description The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing).

Actually it not never goes to Confluence 400 page. It goes to Tomcat’s own 400 page, so my Servelt with Struts2 not execute on MultiPartRequests:

I have React 18 script to upload single file:

const stopRecording = () => {
    recorder.stopRecording(async () => {
      const blob = recorder.getBlob();
      audioRef.current.src = URL.createObjectURL(blob);
      setAudioBlob(blob);

      const formData = new FormData();
      formData.append('audio', blob, 'uploaded_audio.ogg');
      formData.append('gender', gender);
      formData.append('apiPageId', apiPageId);

      try {
        const response = await Axios.post(apiUrl + `/plugins/servlet/ai/generate-response?action=transcribeSpeech`, formData, {
          headers: {
            'Content-Type': 'multipart/form-data'
          }
        });
        console.log('Transcription result:', response.data.transcription);
        await handleChatGPTResponse(response.data.transcription);
      } catch (error) {
        console.error('Error uploading audio file:', error);
      }

      setIsRecording(false);
      cancelAnimationFrame(animationFrameIdRef.current);
      audioContextRef.current.close();
      mediaStreamRef.current.getTracks().forEach(track => track.stop());
    });
  };

How about this?

if (req instanceof MultiPartRequestWrapper multiPartRequest) {
MultiPartRequestWrapper wrapper = FileUploadUtils.unwrapMultiPartRequest(req);
        UploadedFile[] attachedFiles = wrapper.getFiles("audio");
...

Okay thanks but can you tell me what I write import com.rgr.f.FileUploadUtils; to start of my Java file?

I do not find FileUploadUtils from Struts 2.

Also be known I developing my App for Confluence 8.6.+ Data Center and it do not contain old webwork and it uses Struts 2 version 2.1.+ and this is bit of different. I made also mistake to install latest version of Struts 2 so below is updated code:

//imports
import org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper;
import org.apache.struts2.dispatcher.multipart.MultiPartRequest;

//start of servlet

// Annotate the servlet with @MultipartConfig to handle file uploads
@MultipartConfig(
    location = "/tmp", // Temporary directory to store uploaded files
    fileSizeThreshold = 5 * 1024 * 1024, // 5 MB
    maxFileSize = 10 * 1024 * 1024,      // 10 MB
    maxRequestSize = 20 * 1024 * 1024    // 20 MB
)
public class OpenAIServiceServlet extends HttpServlet {

// Handle transcribeSpeech action
            if (req instanceof MultiPartRequestWrapper) {
                MultiPartRequestWrapper multiPartRequest = (MultiPartRequestWrapper) req;
                File[] uploadedFiles = multiPartRequest.getFiles("audio");
                if (uploadedFiles == null || uploadedFiles.length == 0) {
                    resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Missing audio file.");
                    return;
                }

                File audioFile = uploadedFiles[0]; // Assuming you want the first file
                try (InputStream is = new FileInputStream(audioFile.getAbsolutePath())) {
                    String fileName = "uploaded_audio.ogg"; // MSIE fix.
                    String originalFileName = fileName;
                    DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
                    LocalDateTime now = LocalDateTime.now();
                    String timestamp = dtf.format(now);

                    String newFileName = timestamp + "_" + originalFileName;
                    String pageId = req.getParameter("pageId");

                    result = transcribeSpeech(is, newFileName, pageId);
                } catch (IOException e) {
                    throw new ServletException("Failed to read audio file", e);
                }
            } else {
                resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Request is not a multipart request.");
            }

And code in atlassian-plugin.xml

<!-- Add your CORS filter here -->
    <servlet-filter name="CORS Filter" key="cors-filter" class="fi.i4ware.dark.CorsFilter">
        <url-pattern>/*</url-pattern>
    </servlet-filter>

<servlet name="Open AI Service Servlet" key="OpenAIServiceServlet" class="fi.i4ware.dark.OpenAIServiceServlet">
        <multipart-config>
            <location>/tmp</location>
            <max-file-size>20848820</max-file-size>
            <max-request-size>418018841</max-request-size>
            <file-size-threshold>1048576</file-size-threshold>
        </multipart-config>
        <url-pattern>/ai/generate-response</url-pattern>
    </servlet>

Sorry, I should have added more info. The class can be found here:

import com.atlassian.xwork.FileUploadUtils;

And you need to add following line in your POM file, in the confluence-maven-plugin <Import-Package> section:

com.opensymphony.*;resolution:="optional",

At least this setting worked for us with Confluence 8.5.10.

Regards,
Ansgar

I made these changes but lines below tells me that com.opensymphony.* or that struts2 think is not in my Confluence 6.9.1 and I build my plugin with Confluence 8.6.0.

package fi.i4ware.dark;

import com.atlassian.activeobjects.external.ActiveObjects;
import org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper;
import org.apache.struts2.dispatcher.multipart.MultiPartRequest;
import org.apache.struts2.dispatcher.multipart.UploadedFile;
import com.atlassian.xwork.FileUploadUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.annotation.MultipartConfig;
import java.io.File;
import java.io.IOException;
import java.util.List;

// Annotate the servlet with @MultipartConfig to handle file uploads
@MultipartConfig(
    location = "/tmp", // Temporary directory to store uploaded files
    fileSizeThreshold = 5 * 1024 * 1024, // 5 MB
    maxFileSize = 10 * 1024 * 1024,      // 10 MB
    maxRequestSize = 20 * 1024 * 1024    // 20 MB
)
public class CaptureUploadServlet extends HttpServlet {
    private static final Logger logger = LogManager.getLogger(CaptureUploadServlet.class);
    private static final String UPLOAD_DIRECTORY = "/tmp";
    private final ActiveObjects ao;

    public CaptureUploadServlet(ActiveObjects ao) {
        this.ao = ao;
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setCharacterEncoding("UTF-8");
        //if (request instanceof MultiPartRequestWrapper) {
            MultiPartRequestWrapper multiPartRequestWrapper = FileUploadUtils.unwrapMultiPartRequest(request);
            logger.info("Processing upload successfully"); // this line not appear to logs.
            //return;
        //} else {
            //logger.error("Error processing upload");
            return;
        //}
    }
}

And if I make it like below then error.log appear to logs:

package fi.i4ware.dark;

import com.atlassian.activeobjects.external.ActiveObjects;
import org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper;
import org.apache.struts2.dispatcher.multipart.MultiPartRequest;
import org.apache.struts2.dispatcher.multipart.UploadedFile;
import com.atlassian.xwork.FileUploadUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.annotation.MultipartConfig;
import java.io.File;
import java.io.IOException;
import java.util.List;

// Annotate the servlet with @MultipartConfig to handle file uploads
@MultipartConfig(
    location = "/tmp", // Temporary directory to store uploaded files
    fileSizeThreshold = 5 * 1024 * 1024, // 5 MB
    maxFileSize = 10 * 1024 * 1024,      // 10 MB
    maxRequestSize = 20 * 1024 * 1024    // 20 MB
)
public class CaptureUploadServlet extends HttpServlet {
    private static final Logger logger = LogManager.getLogger(CaptureUploadServlet.class);
    private static final String UPLOAD_DIRECTORY = "/tmp";
    private final ActiveObjects ao;

    public CaptureUploadServlet(ActiveObjects ao) {
        this.ao = ao;
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setCharacterEncoding("UTF-8");
        if (request instanceof MultiPartRequestWrapper) {
            MultiPartRequestWrapper multiPartRequestWrapper = FileUploadUtils.unwrapMultiPartRequest(request);
            logger.info("Processing upload successfully");
            return;
        } else {
            logger.error("Error processing upload");
            return;
        }
    }
}

I just got a lot of issues to fix after I placed:

<dependency>
            <groupId>com.pusher</groupId>
            <artifactId>pusher-http-java</artifactId>
            <version>1.3.3</version>
            <scope>compile</scope>
</dependency>

Pusher API seams to cause much problems with Confluence 8…6.0 App like below is full pom.xml what is mandatory to configure or plugin installation not success after Pusher. But yes Pusher Real-Time Boardcasting works well and ChatGTP OpenAI responses but not file upload to Speech-to-Text:

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <groupId>fi.i4ware</groupId>
    <artifactId>dark</artifactId>
    <version>1.2.2</version>

    <organization>
        <name>i4ware Software</name>
        <url>https://www.i4ware.fi/</url>
    </organization>

    <name>i4ware ChatGPT OpenAI Chat with Dark Theme</name>
    <description>This is the ChatGPT OpenAI Chat and Dark Theme for Atlassian Confluence.</description>
    <packaging>atlassian-plugin</packaging>

    <dependencies>
        <dependency>
            <groupId>com.atlassian.templaterenderer</groupId>
            <artifactId>atlassian-template-renderer-api</artifactId>
            <version>1.0.5</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.10</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.atlassian.confluence</groupId>
            <artifactId>confluence</artifactId>
            <version>${confluence.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.struts</groupId>
            <artifactId>struts2-core</artifactId>
            <version>6.4.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.atlassian.activeobjects</groupId>
            <artifactId>activeobjects-plugin</artifactId>
            <version>${activeobjects.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.atlassian.plugin</groupId>
            <artifactId>atlassian-spring-scanner-annotation</artifactId>
            <version>${atlassian.spring.scanner.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.atlassian.plugin</groupId>
            <artifactId>atlassian-spring-scanner-annotation</artifactId>
            <version>${atlassian.spring.scanner.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.inject</groupId>
            <artifactId>javax.inject</artifactId>
            <version>1</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.atlassian.upm</groupId>
            <artifactId>licensing-api</artifactId>
            <version>2.0.1</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.atlassian.upm</groupId>
            <artifactId>upm-api</artifactId>
            <version>2.0.1</version>
            <scope>provided</scope>
        </dependency>
        <!-- WIRED TEST RUNNER DEPENDENCIES -->
        <dependency>
            <groupId>com.atlassian.plugins</groupId>
            <artifactId>atlassian-plugins-osgi-testrunner</artifactId>
            <version>${plugin.testrunner.version}</version>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <artifactId>wink-client</artifactId>
                    <groupId>org.apache.wink</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>javax.ws.rs</groupId>
            <artifactId>jsr311-api</artifactId>
            <version>1.1.1</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.2.2-atlassian-1</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.atlassian.sal</groupId>
            <artifactId>sal-api</artifactId>
            <version>5.0.1</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>2.17.1</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>com.pusher</groupId>
            <artifactId>pusher-http-java</artifactId>
            <version>1.3.3</version>
            <scope>compile</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.apache.httpcomponents</groupId>
                    <artifactId>httpcore</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.apache.httpcomponents</groupId>
                    <artifactId>httpclient</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-api</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>com.atlassian.plugin</groupId>
                    <artifactId>atlassian-spring-scanner-runtime</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>com.github.luben</groupId>
                    <artifactId>zstd-jni</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>com.twitter</groupId>
            <artifactId>hpack</artifactId>
            <version>1.0.2</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>3.30.2-GA</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpcore</artifactId>
            <version>4.4.1</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5</version>
            <scope>provided</scope>
            <exclusions>
                <exclusion>
                    <artifactId>commons-logging</artifactId>
                    <groupId>commons-logging</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.10</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.google.protobuf</groupId>
            <artifactId>protobuf-java</artifactId>
            <version>3.25.3</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>com.google.protobuf.nano</groupId>
            <artifactId>protobuf-javanano</artifactId>
            <version>3.1.0</version>
            <scope>compile</scope>
            <exclusions>
                <exclusion>
                    <groupId>com.oracle.svm.core</groupId>
                    <artifactId>annotate</artifactId>
                </exclusion>
                <!-- Add more exclusions as needed -->
                <exclusion>
                    <groupId>com.atlassian.plugin</groupId>
                    <artifactId>atlassian-spring-scanner-runtime</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>com.jcraft</groupId>
            <artifactId>jzlib</artifactId>
            <version>1.1.3</version>
            <scope>compile</scope>
            <exclusions>
                <exclusion>
                    <groupId>com.oracle.svm.core</groupId>
                    <artifactId>annotate</artifactId>
                </exclusion>
                <!-- Add more exclusions as needed -->
                <exclusion>
                    <groupId>com.atlassian.plugin</groupId>
                    <artifactId>atlassian-spring-scanner-runtime</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>com.ning</groupId>
            <artifactId>compress-lzf</artifactId>
            <version>1.0.3</version> <!-- Use the version compatible with your project -->
            <scope>compile</scope>
            <exclusions>
                <exclusion>
                    <groupId>com.oracle.svm.core</groupId>
                    <artifactId>annotate</artifactId>
                </exclusion>
                <!-- Add more exclusions as needed -->
                <exclusion>
                    <groupId>com.atlassian.plugin</groupId>
                    <artifactId>atlassian-spring-scanner-runtime</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.graalvm.nativeimage</groupId>
            <artifactId>svm</artifactId>
            <version>23.1.3</version>
            <scope>compile</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.apache.httpcomponents</groupId>
                    <artifactId>httpcore</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.apache.httpcomponents</groupId>
                    <artifactId>httpclient</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-api</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.111.Final</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-tcnative-boringssl-static</artifactId>
            <version>2.0.46.Final</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>com.aayushatharva.brotli4j</groupId>
            <artifactId>brotli4j</artifactId>
            <version>1.5.0</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>com.barchart.udt</groupId>
            <artifactId>barchart-udt-bundle</artifactId>
            <version>2.3.0</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>com.fasterxml</groupId>
            <artifactId>aalto-xml</artifactId>
            <version>1.2.2</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>com.github.luben</groupId>
            <artifactId>zstd-jni</artifactId>
            <version>1.0.0</version>
            <scope>compile</scope>
        </dependency>
        <!-- Additional dependencies that might be required -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.12.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.atlassian.plugins</groupId>
            <artifactId>atlassian-plugins-osgi</artifactId>
            <version>4.0.0</version>
            <scope>provided</scope>
            <exclusions>
                <exclusion>
                    <groupId>jdk.tools</groupId>
                    <artifactId>tools</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.rxtx</groupId>
            <artifactId>rxtx</artifactId>
            <version>2.1.7</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.ow2.asm</groupId>
            <artifactId>asm</artifactId>
            <version>9.7</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.osgi</groupId>
            <artifactId>org.osgi.framework</artifactId>
            <version>1.9.0</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.3.1</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.4</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>com.atlassian.platform</groupId>
                <artifactId>platform</artifactId>
                <version>${platform.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>com.atlassian.platform</groupId>
                <artifactId>third-party</artifactId>
                <version>${platform.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>com.atlassian.plugin</groupId>
                <artifactId>atlassian-spring-scanner-external-jar</artifactId>
                <version>${atlassian.spring.scanner.version}</version>
                <scope>runtime</scope>
            </dependency>
            <dependency>
                <groupId>com.atlassian.plugin</groupId>
                <artifactId>atlassian-spring-scanner-annotation</artifactId>
                <version>${atlassian.spring.scanner.version}</version>
                <scope>provided</scope>
            </dependency>
            <dependency>
                <groupId>com.github.luben</groupId>
                <artifactId>zstd-jni</artifactId>
                <version>1.0.0</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.owasp</groupId>
                    <artifactId>dependency-check-maven</artifactId>
                    <version>8.4.3</version>
                <configuration>
                    <skipProvidedScope>true</skipProvidedScope>
                    <skipRuntimeScope>true</skipRuntimeScope>
                </configuration>
                <executions>
                    <execution>
                    <goals>
                        <goal>check</goal>
                    </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>com.atlassian.maven.plugins</groupId>
                <artifactId>confluence-maven-plugin</artifactId>
                <version>${amps.version}</version>
                <extensions>true</extensions>
                <configuration>
                    <productVersion>${confluence.version}</productVersion>
                    <productDataVersion>${confluence.data.version}</productDataVersion>
                    <enableQuickReload>true</enableQuickReload>
                    <instructions>
                    <DynamicImport-Package>
                        com.atlassian.upm.api.license;version="2.0.1",
                        com.atlassian.upm.api.license.entity;version="2.0.1",
                        com.atlassian.upm.api.util;version="2.0.1",
                        com.atlassian.upm.license.storage.plugin;version="2.0.1",
                        com.sun.mirror.*;version="0.0"
                    </DynamicImport-Package>
                    <Export-Package>
                        fi.i4ware.api,
                        fi.i4ware.dark,
                        *
                    </Export-Package>
                    <Import-Package>
                        com.atlassian.confluence.themes,
                        org.springframework.osgi.*;resolution:="optional",
                        org.eclipse.gemini.blueprint.*;resolution:="optional",
                        com.opensymphony.*;resolution:="optional",
                        *
                    </Import-Package>
                    <Spring-Context>*</Spring-Context>
                    </instructions>
                </configuration>
            </plugin>
            <plugin>
                <groupId>com.atlassian.plugin</groupId>
                <artifactId>atlassian-spring-scanner-maven-plugin</artifactId>
                <version>${atlassian.spring.scanner.version}</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>atlassian-spring-scanner</goal>
                        </goals>
                        <phase>process-classes</phase>
                    </execution>
                </executions>
                <configuration>
                    <verbose>false</verbose>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>17</source>
                    <target>17</target>
                    <compilerArgs>
                        <arg>--add-modules</arg>
                        <arg>jdk.jdi,jdk.attach</arg>
                    </compilerArgs>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>3.3.0</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <minimizeJar>true</minimizeJar>
                            <filters>
                                <filter>
                                    <artifact>org.ow2.asm:asm</artifact>
                                    <excludes>
                                        <exclude>META-INF/*.SF</exclude>
                                        <exclude>META-INF/*.DSA</exclude>
                                        <exclude>META-INF/*.RSA</exclude>
                                    </excludes>
                                </filter>
                                <filter>
                                    <artifact>org.osgi:org.osgi.framework</artifact>
                                    <excludes>
                                        <exclude>**/org/osgi/framework/wiring/**</exclude>
                                    </excludes>
                                </filter>
                                <filter>
                                    <artifact>org.apache.httpcomponents:httpcore</artifact>
                                    <excludes>
                                        <exclude>**/*</exclude>
                                    </excludes>
                                </filter>
                                <filter>
                                    <artifact>org.apache.httpcomponents:httpclient</artifact>
                                    <excludes>
                                        <exclude>**/*</exclude>
                                    </excludes>
                                </filter>
                                <filter>
                                    <artifact>org.slf4j:slf4j-api</artifact>
                                    <excludes>
                                        <exclude>**/*</exclude>
                                    </excludes>
                                </filter>
                            </filters>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.felix</groupId>
                <artifactId>maven-bundle-plugin</artifactId>
                <version>4.2.1</version>
                <extensions>true</extensions>
                <configuration>
                    <instructions>
                        <Bundle-SymbolicName>fi.i4ware.dark</Bundle-SymbolicName>
                        <Import-Package>
                            org.springframework.osgi.*;resolution:=optional,
                            org.eclipse.gemini.blueprint.*;resolution:=optional,
                            com.oracle.svm.core.annotate,
                            io.netty.internal.tcnative,
                            com.aayushatharva.brotli4j,
                            com.barchart.udt,
                            com.fasterxml.aalto,
                            com.github.luben.zstd;version="[1.0,2.0)",
                            gnu.io,
                            org.osgi.framework;version="[1.9,2.0)",
                            org.objectweb.asm.*;version="[9.7,10.0)",
                            sun.management,
                            sun.invoke.util,
                            sun.net,
                            sun.net.www,
                            sun.nio.ch,
                            sun.reflect.annotation,
                            sun.reflect.generics.factory,
                            sun.reflect.generics.reflectiveObjects,
                            sun.reflect.generics.repository,
                            sun.security.jca,
                            sun.security.provider,
                            sun.security.ssl,
                            sun.security.util,
                            sun.security.x509,
                            sun.text.spi,
                            sun.util.calendar,
                            sun.util.cldr,
                            sun.util.locale,
                            sun.util.locale.provider,
                            sun.util.resources,
                            jdk.internal,
                            jdk.internal.access,
                            jdk.internal.event,
                            jdk.internal.loader,
                            jdk.internal.logger,
                            jdk.internal.misc,
                            jdk.internal.module,
                            jdk.internal.org.objectweb.asm,
                            jdk.internal.platform,
                            jdk.internal.ref,
                            jdk.internal.reflect,
                            jdk.internal.util,
                            jdk.internal.vm,
                            jdk.internal.vm.annotation,
                            jdk.jfr.events,
                            jdk.jfr.internal,
                            jdk.jfr.internal.jfc,
                            jdk.vm.ci.aarch64,
                            jdk.vm.ci.amd64,
                            jdk.vm.ci.code,
                            jdk.vm.ci.code.site,
                            jdk.vm.ci.code.stack,
                            jdk.vm.ci.common,
                            jdk.vm.ci.hotspot,
                            jdk.vm.ci.hotspot.aarch64,
                            jdk.vm.ci.meta,
                            jdk.vm.ci.runtime,
                            jdk.vm.ci.services,
                            lzma.sdk,
                            lzma.sdk.lzma,
                            net.jpountz.lz4,
                            net.jpountz.xxhash,
                            org.apache.logging.log4j.spi,
                            org.conscrypt,
                            org.eclipse.jetty.alpn,
                            org.eclipse.jetty.npn,
                            org.jboss.marshalling,
                            reactor.blockhound,
                            reactor.blockhound.integration,
                            com.opensymphony.*
                            *
                        </Import-Package>
                        <Embed-Dependency>
                            protobuf-java;scope=compile|runtime,
                            protobuf-javanano;scope=compile|runtime,
                            jzlib;scope=compile|runtime,
                            compress-lzf;scope=compile|runtime,
                            svm;scope=compile|runtime,
                            netty-all;scope=compile|runtime,
                            netty-tcnative-boringssl-static;scope=compile|runtime,
                            brotli4j;scope=compile|runtime,
                            barchart-udt-bundle;scope=compile|runtime,
                            aalto-xml;scope=compile|runtime,
                            asm;scope=compile|runtime,
                            zstd;scope=compile|runtime
                        </Embed-Dependency>
                        <Embed-Transitive>true</Embed-Transitive>
                        <Export-Package>
                            fi.i4ware.api,
                            fi.i4ware.dark,
                            com.opensymphony.*
                            com.pusher.*,
                            com.google.protobuf.*,
                            com.google.protobuf.nano.*,
                            com.jcraft.jzlib.*,
                            com.ning.compress.*,
                            com.oracle.svm.core.annotate.*,
                            io.netty.internal.tcnative.*,
                            org.objectweb.asm.*,
                            com.sun.jdi,
                            com.sun.jdi.connect,
                            com.sun.jdi.event,
                            com.sun.jdi.request,
                            com.sun.tools.attach,
                            com.sun.management.internal,
                            sun.management,
                            sun.invoke.util,
                            sun.net,
                            sun.net.www,
                            sun.nio.ch,
                            sun.reflect.annotation,
                            sun.reflect.generics.factory,
                            sun.reflect.generics.reflectiveObjects,
                            sun.reflect.generics.repository,
                            sun.security.jca,
                            sun.security.provider,
                            sun.security.ssl,
                            sun.security.util,
                            sun.security.x509,
                            sun.text.spi,
                            sun.util.calendar,
                            sun.util.cldr,
                            sun.util.locale,
                            sun.util.locale.provider,
                            sun.util.resources,
                            jdk.internal,
                            jdk.internal.access,
                            jdk.internal.event,
                            jdk.internal.loader,
                            jdk.internal.logger,
                            jdk.internal.misc,
                            jdk.internal.module,
                            jdk.internal.org.objectweb.asm,
                            jdk.internal.platform,
                            jdk.internal.ref,
                            jdk.internal.reflect,
                            jdk.internal.util,
                            jdk.internal.vm,
                            jdk.internal.vm.annotation,
                            jdk.jfr.events,
                            jdk.jfr.internal,
                            jdk.jfr.internal.jfc,
                            jdk.vm.ci.aarch64,
                            jdk.vm.ci.amd64,
                            jdk.vm.ci.code,
                            jdk.vm.ci.code.site,
                            jdk.vm.ci.code.stack,
                            jdk.vm.ci.common,
                            jdk.vm.ci.hotspot,
                            jdk.vm.ci.hotspot.aarch64,
                            jdk.vm.ci.meta,
                            jdk.vm.ci.runtime,
                            jdk.vm.ci.services,
                            lzma.sdk,
                            lzma.sdk.lzma,
                            net.jpountz.lz4,
                            net.jpountz.xxhash,
                            org.apache.logging.log4j.spi,
                            org.conscrypt,
                            org.eclipse.jetty.alpn,
                            org.eclipse.jetty.npn,
                            org.jboss.marshalling,
                            reactor.blockhound,
                            reactor.blockhound.integration,
                            com.aayushatharva.brotli4j,
                            com.barchart.udt,
                            com.fasterxml.aalto,
                            com.github.luben.zstd,
                            *
                        </Export-Package>
                        <Private-Package>
                            com.pusher,
                            com.google.protobuf,
                            com.google.protobuf.nano,
                            com.jcraft.jzlib,
                            com.ning.compress,
                            com.oracle.svm.core.annotate,
                            io.netty.internal.tcnative,
                            org.objectweb.asm,
                            com.github.luben.zstd
                        </Private-Package>
                        <DynamicImport-Package>
                            *
                        </DynamicImport-Package>
                        <Bundle-StartLevel>20</Bundle-StartLevel>
                    </instructions>
                </configuration>
            </plugin>
        </plugins>
        <sourceDirectory>src/main/java</sourceDirectory>
        <defaultGoal>package</defaultGoal>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <filtering>true</filtering>
                <includes>
                    <include>atlassian-plugin.xml</include>
                </includes>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <filtering>false</filtering>
                <includes>
                    <include>**/*.*</include>
                </includes>
                <excludes>
                    <exclude>atlassian-plugin.xml</exclude>
                </excludes>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>META-INF/LICENSE/**</include>
                </includes>
            </resource>
        </resources>
    </build>

    <properties>
        <confluence.version>8.6.0</confluence.version>
        <confluence.data.version>5.3.4</confluence.data.version>
        <amps.version>8.6.0</amps.version>
        <activeobjects.version>3.2.7</activeobjects.version>
        <plugin.testrunner.version>2.0.2</plugin.testrunner.version>
        <atlassian.spring.scanner.version>2.1.7</atlassian.spring.scanner.version>
        <upm.license.compatibility.version>2.2.4</upm.license.compatibility.version>
        <atlassian.plugin.key>${groupId}.${artifactId}</atlassian.plugin.key>
        <platform.version>3.0.0</platform.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
    </properties>

</project>

And also after Pusher plugin need Oracle JDK 17 to get succeed to build.

Sorry, now I realized that you probably don’t need the <Import-Package> section I mentioned, I saw the xwork package and thought it was the one from opensymphony.

About the Pusher API, I can’t help you with that, I don’t know this library, and it isn’t something related to Confluence.

Thanks,

I marked now your reply for solution.

Pusher is Real-Time Board-casting Cloud Service to do Typing Indicator and Message Board-casting for all Chat users and is made for to do similar chat, etc. like Facebook. So is use i.e post message to chat then it appears to all other users web-browser without any page loads.

I meant decencies needed to get Pusher working as Confluence App probably causes conflict with Struts 2 MultiPartRequestWrapper or some thing else.

Hi,

I now found an alternative way to fix things: https://community.atlassian.com/t5/Confluence-questions/Upload-attachment-using-REST-API/qaq-p/458998#M77680

Issue is only that Atlassian Confluence API’s Strtust 2 API is outdated and not support modern XmlHTTPRequest as MultiPartRequest (I tkink only this probably be true) in React 18 front-ends.

This fix needs also extra Ajax-calls to save content to ActiveObjects Db tables.

Also I noticed that Atlassian is some reason prevented to upgrade to Struts 2 6.4 version to get thinks work wihtour REST API.