Milesoft LinkedIn Extension (linkedin-ext)

1. Overview & Purpose

The LinkedIn Extension (linkedin-ext) is an enterprise-grade facade library designed for the Milesoft ecosystem. It simplifies and standardizes interaction with the LinkedIn REST API, allowing downstream applications to publish rich content, post text updates, and perform multi-step media uploads.

By wrapping low-level LinkedIn Rest.li endpoints, linkedin-ext ensures that compliance, header standards, and security are maintained consistently across all applications and microservices.

Key Capabilities & Core Benefits:

  • Automatic Organization URN Scoping: Encapsulates the target organization (company ID) and automatically scopes API interactions (e.g., converting "66763891" to "urn:li:organization:66763891").
  • Multi-Source Image Processing: Seamlessly uploads images regardless of how they are represented. Supports local files (java.nio.file.Path), memory-backed assets (java.awt.image.BufferedImage), or remote web-accessible URLs (e.g., Cloudinary-hosted resources).
  • Standardized Rest.li Header Injection: Injects mandatory, modern headers into all requests to ensure strict version pinning and compatibility:
    • X-Restli-Protocol-Version: 2.0.0
    • LinkedIn-Version: 202511
  • Output ID Resolution: Automatically extracts the response header x-restli-id (the newly generated Post/Share URN) on successful publication, returning a clean, traceable reference identifier.
  • Robust Exception Model: Ensures strict validation of API request payloads utilizing static preconditions prior to dispatching network calls.

2. Installation & Dependency Configuration

The linkedin-ext extension package is hosted inside our secure Google Cloud Artifact Registry.

To pull this package into your downstream JVM microservice or application, configure your build settings as follows:

2.1 Gradle Setup (build.gradle)

Register our secure Artifact Registry URL and include the dependency:

plugins {
    id "com.google.cloud.artifactregistry.gradle-plugin" version "2.2.5"
    id "java"
}

repositories {
    mavenLocal()
    mavenCentral()
    maven {
        url "artifactregistry://us-west2-maven.pkg.dev/milesoft-repo/repo-maven"
    }
}

dependencies {
    // Required Milesoft base dependencies
    implementation "io.milesoft:commons:2.0.1"
    implementation "io.milesoft:clients:2.0.1"
    
    // Milesoft LinkedIn Extension
    implementation "io.milesoft:linkedin-ext:2.0.1"
}

3. Core Entities & Domain Models

The library enforces type-safety and minimizes developer error via cohesive domain representations.

3.1 Image

The Image class represents a rich media upload payload. It features a Lombok-backed builder and handles all standard image sources:

import java.awt.image.BufferedImage;
import java.nio.file.Path;
import lombok.Builder;
import lombok.Getter;
import lombok.NonNull;

@Getter
@Builder(builderClassName = "Builder")
public class Image {

    @NonNull
    private final String title;

    @NonNull
    private final String fileName;

    private final Path file;

    private final BufferedImage data;

    private final String url;
}

3.2 LinkedIn REST Protocol Constants

Standardized Rest.li protocol definitions are defined under io.milesoft.linkedin.constants.LinkedInConstants:

Constant Key Value Description
VISIBILITY_PUBLIC "PUBLIC" Sets visibility context to public feed
DISTRIBUTION_MAIN_FEED "MAIN_FEED" Standard feed delivery channel
LIFECYCLE_STATE_PUBLISHED "PUBLISHED" Direct publishing state
PREFIX_ORGANIZATION "urn:li:organization:" Core author organization prefix
PREFIX_IMAGE "urn:li:image:" Core uploaded image prefix
HEADER_PROTOCOL_KEY "X-Restli-Protocol-Version" Rest.li Protocol Key
HEADER_PROTOCOL_VALUE "2.0.0" Protocol version standard
HEADER_VERSION_KEY "LinkedIn-Version" LinkedIn Version Pin Key
HEADER_VERSION_VALUE "202511" API Version Target
HEADER_POST_ID "x-restli-id" Post identifier returned in response

4. Spring Boot Integration & Bean Configuration

Integrating the LinkedIn Extension into a Spring Boot service is clean and declarative.

4.1 Define the Config Class

Instantiate the service class as a bean, specifying the company organization ID retrieved from application properties:

import io.milesoft.linkedin.services.LinkedInService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SocialConfig {

    @Bean
    public LinkedInService linkedInService(@Value("${linkedIn.companyId}") String companyId) {
        return new LinkedInService(companyId);
    }
}

4.2 Property Definition (application.yml)

Configure the respective target variables under your profile configs:

linkedIn:
  companyId: "12345678"

5. API & Usage Guide

Downstream clients can interact with LinkedInService via simple, high-level methods.

5.1 Share a Text-Only Post

Publish a lightweight text-only message to the company's main feed:

import io.milesoft.linkedin.services.LinkedInService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class PublishingService {

    @Autowired
    private LinkedInService linkedInService;

    public void publishUpdate(String content, String oauthToken) {
        // Publishes the post and returns the unique LinkedIn URN (e.g. "urn:li:share:7123456")
        String postUrn = linkedInService.shareText(content, oauthToken);
        System.out.println("Text update published: " + postUrn);
    }
}

5.2 Share a Post with a Media Asset (Image)

Upload and publish a rich image post to the company's main feed:

import io.milesoft.linkedin.domain.Image;
import io.milesoft.linkedin.services.LinkedInService;
import java.nio.file.Paths;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class SocialMarketingService {

    @Autowired
    private LinkedInService linkedInService;

    public void publishCampaign(String message, String imageUrl, String oauthToken) {
        // Build image model targeting a remote URL (downloaded & processed automatically)
        Image image = Image.builder()
            .title("Launch Campaign 2026")
            .fileName("campaign_launch.png")
            .url(imageUrl)
            .build();

        // Performs upload and posts to the main feed under organizational authorship
        String postUrn = linkedInService.shareImage(image, message, oauthToken);
        System.out.println("Media post successfully published! URN: " + postUrn);
    }
}

6. Under-the-Hood Mechanics & Network Flow

For complex image sharing operations, the extension encapsulates a multi-step sequence to satisfy LinkedIn's strict secure content delivery guidelines:

[Downstream Client]                    [LinkedInService]                   [LinkedIn REST API]
       |                                       |                                    |
       |----- shareImage(image, msg, tok) ---->|                                    |
       |                                       |----- 1. Initialize Upload -------->|
       |                                       |<---- returns binary PUT URL -------|
       |                                       |                                    |
       |                                       |----- 2. PUT binary payload ------->| (Amazon S3 direct storage)
       |                                       |<---- 201 Created ------------------|
       |                                       |                                    |
       |                                       |----- 3. POST Share Payload ------->| (Contains media URN + post msg)
       |                                       |<---- returns Header: x-restli-id --|
       |<---- Returns unique Post URN ---------|                                    |

Flow Breakdown:

  1. Initialize Upload: Dispatches a POST request to https://api.linkedin.com/rest/images?action=initializeUpload specifying the owner organization. It returns a target AWS S3 pre-signed upload URL along with a registered image asset URN (urn:li:image:<imageId>).
  2. Binary Push: Streams the raw binary image payload using a PUT request with custom Multi-part / File handlers directly to the pre-signed storage URL.
  3. Post Publication: Dispatches a final POST to https://api.linkedin.com/rest/posts specifying the commentary text, public feed visibility, and ties the newly uploaded media URN to the post content.
  4. Header Interception: The response parses the response header list, retrieves the x-restli-id value containing the unique post identifier, and returns it to the client.

7. Testing & Verification

For local development and verification patterns, integration tests are written under src/test/java utilizing custom git-ignored configuration property files.

7.1 Integration Test Example

Place private test credentials under /src/test/resources/application.private.properties (never commit this file) and execute tests:

import io.milesoft.commons.utils.PropertyUtils;
import io.milesoft.linkedin.domain.Image;
import java.nio.file.Path;
import java.util.Properties;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;

public class TestLinkedInService {

    private LinkedInService linkedInService;
    private String accessToken;

    @Before
    public void before() {
        // Loads local, developer-only properties
        final Properties properties = PropertyUtils.read("/application.private.properties");
        final String companyId = properties.getProperty("linkedIn.companyId");
        this.accessToken = properties.getProperty("linkedIn.accessToken");

        this.linkedInService = new LinkedInService(companyId);
    }

    @Test
    @Ignore("Integration test - run locally with private credentials only")
    public void testShareText() {
        String message = "Direct integration test post from Milesoft SDK";
        String urnId = linkedInService.shareText(message, accessToken);
        System.out.println("Verified text share: " + urnId);
    }
}