자바

apache tika 라이브러리를 이용하여 일반 문서 및 암호화된 문서의 정보 알아보기

알쓸개잡 2024. 3. 8. 19:41

Apache tika 라이브러리는 문서 파일에 대한 Mime Type을 탐지하고 파일의 내용과 여러 항목의 메타데이터를 추출할 수 있는 기능을 제공하는 라이브러리다. 단순히 파일의 확장자가 아닌 실제 파일의 내용을 기반으로 한 Mime Type을 알아야 해당 Mime Type에 맞는 적절한 파일 컨트롤을 할 수 있다. 이번 포스팅에서는 Tika 라이브러리를 사용하여 파일에 대한 Mime Type과 여러 가지 메타데이터를 추출하는 방법과 함께 MS Office, PDF 문서에 비밀번호가 설정된 경우에도 Mime Type을 확인해 볼 수 있는 방법을 소개하고자 한다.

 

apache tika 라이브러리는 3.0.0-BETA 베타 버전과 함께 릴리즈된 2.9.1 버전까지 나와있다.

각 버전에 대한 릴리즈 노트는 https://tika.apache.org/ 페이지에서 우측의 각 버전 항목을 선택하면 확인할 수 있다.

 

빌드툴은 maven, tika 버전은 2.9.1 버전 기준으로 작성하겠다.

Dependency

tika 라이브러리는 두개의 핵심 모듈이 있다. 바로 tika-core, tika-parser 모듈이다.

<properties>
    <tika.version>2.9.1</tika.version>
</properties>

...

<dependencies>
	...
    <dependency>
        <groupId>org.apache.tika</groupId>
        <artifactId>tika-core</artifactId>
        <version>${tika.version}</version>
    </dependency>
    <dependency>
        <groupId>org.apache.tika</groupId>
        <artifactId>tika-parsers-standard-package</artifactId>
        <version>${tika.version}</version>
    </dependency>
    ...
</dependencies>

 

 

tika-parsers-standard-package artifact는 tika에서 공식적으로 지원가능한 parser 모듈들을 설치한다.

tika 1.28.5 버전의 경우에는 tika parser 디펜던시의 artifact 는 tika-parsers로 지정한다.

 

두 개의 artifact 가 설치되면 지원되는 parser를 확인해 볼 수 있다.

TIKA 에서 지원하는 파서 모듈
TIKA에서 지원하는 파서 모듈

지원되는 파서 모듈들을 살펴보면 microsoft office 문서, pdf 문서 등 다양한 문서들에 대한 파서를 지원함을 확인할 수 있다.

 

 

TIKA 라이브러리 사용 코드

일반 파일

다음은 tika 라이브러리를 사용하여 파일에 mime type 정보와 함께 meta 정보를 출력하는 코드이다.

public void analyzeDocument(Path path) {
    System.out.println("==========================================");
    System.out.println("path: " + path);
    AutoDetectParser parser = new AutoDetectParser();
    BodyContentHandler handler = new BodyContentHandler(-1);
    Metadata metadata = new Metadata();
    ParseContext context = new ParseContext();

    try(InputStream inputStream = TikaInputStream.get(path)) {
        parser.parse( inputStream, handler, metadata, context );
        printMetaInfo( metadata );
        String mimeType = metadata.get( Metadata.CONTENT_TYPE );
        System.out.println("Mime type: " + mimeType);
    }
    catch ( EncryptedDocumentException e ) {
        System.out.println("encrypted, " + e.getMessage());
        String mimeType = metadata.get( Metadata.CONTENT_TYPE );
        System.out.println("mime type: " + mimeType);
    }
    catch ( IOException e ) {
        System.out.println("io exception, " + e.getMessage());
    }
    catch ( SAXException e ) {
        System.out.println("sax exception, " + e.getMessage());
    }
    catch ( TikaException e ) {
        System.out.println("tika exception, " + e.getMessage());
    }
    System.out.println("==========================================");
}

private void printMetaInfo(Metadata metadata) {
    String[] metaDataNames = metadata.names();
    for (String name : metaDataNames) {
        System.out.println(name + " : " + metadata.get(name));
    }
}

Path 타입의 parameter는 대상 파일의 경로를 참조하고 있는 Path 인스턴스다.

메서드에 전달된 파일에 암호가 걸린 경우에는 EncryptedDocumentException이 발생하여 해당 문서가 암호화된 것을 알 수 있다.

Tika 라이브러리 프로세스에 관여하는 클래스에 대한 설명은 다음과 같다.

  • AutoDetectParser 클래스는 주어진 파일의 유형을 자동으로 감지하고 파싱하는 역할을 한다.
  • BodyContentHandler 클래스는 파싱된 문서의 본문 내용을 처리하고 수집하는 역할을 한다. BodyContentHandler를 이용하여 본문의 텍스트를 추출할 수 있다. BodyContentHandler는 파서가 추출한 본문 내용을 캡처하고 저장하는 핸들러로 사용된다.
  • MetaData 클래스는 문서와 관련된 메타데이터(ex. 제목, 저자, 생성일, 수정일, mime type 등)를 저장하고 관리하는 역할을 한다. TIKA에서 추출한 문서의 여러 메타데이터를 MetaData 클래스에 저장한다.
  • ParseContext 클래스는 파서가 문서를 처리할 때 필요한 추가 구성이나 리소스를 파서에 전달하는 역할을 한다. ParseContext 클래스를 통해서 파싱 동작을 사용자의 요구에 맞게 조정하고, 파싱 프로세스 중에 필요한 다양한 종류의 데이터와 설정을 파서에게 제공하는 컨테이너 역할을 한다. 다음은 ParseContext의 사용 예시다.

이미지 파일을 analyzeDocument 메서드에 전달했을 때 출력되는 메타데이터 정보 예시는 다음과 같다.

Transparency Alpha : nonpremultipled
X-TIKA:Parsed-By-Full-Set : org.apache.tika.parser.DefaultParser
tiff:ImageLength : 50
Compression CompressionTypeName : deflate
Data BitsPerSample : 8 8 8 8
Data PlanarConfiguration : PixelInterleaved
Dimension VerticalPixelSize : 0.35285816
IHDR : width=250, height=50, bitDepth=8, colorType=RGBAlpha, compressionMethod=deflate, filterMethod=adaptive, interlaceMethod=none
Chroma ColorSpaceType : RGB
tiff:BitsPerSample : 8 8 8 8
Content-Type : image/png
height : 50
imagereader:NumImages : 1
pHYs : pixelsPerUnitXAxis=2834, pixelsPerUnitYAxis=2834, unitSpecifier=meter
Dimension PixelAspectRatio : 1.0
Compression NumProgressiveScans : 1
X-TIKA:Parsed-By : org.apache.tika.parser.DefaultParser
Dimension HorizontalPixelSize : 0.35285816
Chroma BlackIsZero : true
Compression Lossless : true
width : 250
Dimension ImageOrientation : Normal
tiff:ImageWidth : 250
Chroma NumChannels : 4
Data SampleFormat : UnsignedIntegral
Mime type: image/png

 

 

암호가 걸린 문서

문서에 암호가 걸린 경우 문서를 파싱하게 되면 EncryptedDocumentException이 발생하여 해당 문서에 암호가 걸린 것을 감지할 수 있다. 더불어 암호가 제공된 경우에는 해당 암호를 파서에게 전달하여 문서에 대한 정보를 얻어 올 수 있다.

암호를 알고 있는 경우 파서에게 암호를 전달하여 파일 정보를 추출하는 샘플 코드는 다음과 같다.

public void analyzeEncryptedDocumetWithPassword(Path path, String password) {
    System.out.println("==========================================");
    System.out.println("path: " + path);

    AutoDetectParser parser = new AutoDetectParser();
    BodyContentHandler handler = new BodyContentHandler(-1);
    Metadata metadata = new Metadata();
    ParseContext context = new ParseContext();

    try(InputStream inputStream = TikaInputStream.get(path)) {
        if ( password != null ) {
            context.set( PasswordProvider.class, metaData -> password );
        }

        parser.parse( inputStream, handler, metadata, context );
        printMetaInfo( metadata );
    }
    catch ( EncryptedDocumentException e ) {
        System.out.println("encrypted, " + e.getMessage());
        String mimeType = metadata.get( Metadata.CONTENT_TYPE );
        System.out.println("mime type: " + mimeType);
        if ( password == null ) {
            analyzeDocument( path, "1575" );
        }
    }
    catch ( IOException e ) {
        System.out.println("io exception, " + e.getMessage());
    }
    catch ( SAXException e ) {
        System.out.println("sax exception, " + e.getMessage());
    }
    catch ( TikaException e ) {
        System.out.println("tika exception, " + e.getMessage());
    }
    System.out.println("==========================================");
}

private void printMetaInfo(Metadata metadata) {
    String[] metaDataNames = metadata.names();
    for (String name : metaDataNames) {
        System.out.println(name + " : " + metadata.get(name));
    }
}

위 코드는 ParseContext 클래스에 PasswordProvider 인스턴스를 전달하여 제공된 비밀번호를 파서에게 전달한다.

PasswordProvider는 Functional Interface로써 다음과 같이 정의되어 있다.

public interface PasswordProvider {
    String getPassword(Metadata var1);
}

Metadata 인스턴스를 파라미터로 받고 String 타입을 리턴하는 getPassword 메서드가 정의되어 있다.

getPassword() 메서드를 오버라이드 하여 우리가 알고 있는 비밀번호를 리턴하도록 하면 된다.

Functional Interface 라서 context.set 코드에 람다 형식으로 작성했지만 다음과 같이 작성해도 좋다.

만약 비밀번호가 틀리다면 EncryptedDocumentException 예외가 발생한다.
context.set( PasswordProvider.class, new PasswordProvider() {
    @Override
    public String getPassword( Metadata metadata ) {
        return password;
    }
} );

 

암호가 걸린 docx 문서를 테스트 했을 때 출력되는 메타데이터 정보 예시는 다음과 같다.

cp:revision : 1
extended-properties:DocSecurity : 1
extended-properties:AppVersion : 16.0000
meta:paragraph-count : 1
meta:word-count : 4
extended-properties:Application : Microsoft Office Word
X-TIKA:Parsed-By-Full-Set : org.apache.tika.parser.DefaultParser
extended-properties:Company : 
xmpTPg:NPages : 1
dcterms:created : 2024-01-03T05:11:00Z
meta:line-count : 1
dcterms:modified : 2024-01-03T05:32:00Z
meta:character-count : 8
extended-properties:Template : Normal.dotm
meta:character-count-with-spaces : 11
X-TIKA:Parsed-By : org.apache.tika.parser.DefaultParser
extended-properties:DocSecurityString : PasswordProtected
extended-properties:TotalTime : 4
meta:page-count : 1
Content-Type : application/vnd.openxmlformats-officedocument.wordprocessingml.document
dc:publisher :

위 메타데이터에서

extended-properties:DocSecurity : 1

extended-properties:DocSecurityString : PasswordProtected

를 통해서 문서에 암호가 걸렸다는 정보를 알 수 있다.

 

이상 끝.