blog

Cấu hình NginX cho websocket

Nếu bạn có một webserver có public websocket ra dưới một reverse proxy NginX, nếu không cấu hình đúng bạn sẽ có thể gặp các lỗi như 403 Forbidden, không thể subscribe được một topic qua server name public ra ngoài.

Sử dụng cấu hình tham khảo dưới đây cho NginX khi bạn có một websocket chạy qua nó.

    server {
        listen       8000;
        location / {
            proxy_pass http://127.0.0.1:8000;
        }
        # The second stage for websocket
        location /ws {
            proxy_pass http://127.0.0.1:8000;
            # this magic is needed for WebSocket
            proxy_http_version  1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
        }
    }

Hướng dẫn tạo database MariaDB/MySQL

Bài hướng dẫn này bao gồm các thông tin:
– Tạo một database tên: kuong
– Tạo một database user có tên kuonguser với mật khẩu là kuonguser
– Gán quyền cho user kuonguser với database kuong

  • Mở terminal để đăng nhập vào quản trị database
mysql -u root -p $rootpassword
  • Tạo database tên kuong
mysql> create database kuong default character set utf8 default collate utf8_bin;
  • Tạo tài khoản truy cập vào database tên kuonguser có mật khẩu là kuonguser, gán quyền truy cập cho tài khoản đó
mysql> GRANT ALL PRIVILEGES ON kuong.* to kuonguser@'%' IDENTIFIED BY 'kuonguser';
mysql> GRANT ALL PRIVILEGES ON kuong.* to kuonguser@'localhost' IDENTIFIED BY 'kuonguser';

Quên mật khẩu root của MariaDB trên Mac OS X

Để reset mật khẩu user root của MariaDB trên Mac OS X, các bạn thực hiện theo các bước sau:

Đảm bảo MariaDB đã được stop

# brew services stop mariadb

Sau đó thực hiện câu lệnh sau:

# mysqld --skip-grant-tables --user=mysql &

Tiếp theo kết nối vào mariadb server:

# mysql

Do việc quản lý account đã được disabled khi thực hiện câu lệnh ở trên với tham số –skip-grant-tables nên chúng ta phải thực thi câu lệnh dưới để để tải lại quyền, để có thể thay đổi mật khẩu:

# FLUSH PRIVILEGES;

Thực thi query sau đó thực hiện cập nhật mật khẩu của user root:

# ALTER USER 'root'@'localhost' IDENTIFIED BY 'new_passowrd';

Kết quả như sau:

MariaDB [(none)]> ALTER USER 'root'@'localhost' IDENTIFIED BY 'Itsol@maria';
Query OK, 0 rows affected (0.006 sec)

Khởi động lại Mariadb:

# brew services restart mariadb

Bây giờ bạn đã có thể đăng nhập lại với user root và mật khẩu mới

# mysql -u root -p
Cuongs-MacBook-Pro-4:~ macbookpro$ mysql -u root -p
Enter password: 
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 9
Server version: 10.3.11-MariaDB Homebrew

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

Hướng dẫn chuyển vùng và cài Google Assistant Tiếng Việt trên iOS

Đầu tháng 05/2019, Google Assistent đã chính thức hỗ trợ Tiếng Việt. Tuy nhiên hiện tại để sử dụng được Tiếng Việt, người dùng phải tải bản cập nhật mới nhất từ App Store Mỹ. Với những tài khoản đang để App Store tại Việt Nam cần chuyển đổi Quốc gia/Vùng sang Mỹ để thực hiện tải về.

Để chuyển vùng, bạn làm theo các bước sau:

  1. Mở App Store, bấm vào avatar tài khoản của bạn

2. Làm theo các bước sau:

Làm theo hướng dẫn như trên

Chọn Hoa Kỳ (United State)

Bấm Đồng ý (Agree)

Ở màn hình này phần thông tin thanh toán chọn None nhé ^^

Điền địa chỉ ở Mỹ, ví dụ như trên, bạn có thể điền cùng thông tin này.

Sau khi hoàn thành các bước này, bạn mở lại App Store, tìm kiếm với từ khóa “Google Assistant” sẽ có kết quả như sau:

Sau khi cài đặt thì bạn đã có thể sử dụng được Tiếng Việt với cô trợ lý vui tính của Google.

10 BÀI HỌC KINH DOANH ĐẮT GIÁ TỪ CUỘC SỐNG

1/ Bài học số 1

Hai con bồ câu trống và mái tha hạt thóc về đầy tổ, cả hai rất ư hạnh phúc. Gặp mùa khô hanh, hạt thóc ngót lại. Con trống thấy tổ vơi đi liền trách con mái ăn vụng. Con mái cãi lại liền bị con trống mổ chết. Mấy hôm sau mưa xuống, hạt thóc thấm nước và nở to ra. Bồ câu trống ngẩn tò te.

+ Bài học kinh doanh rút ra:

“Thịt” nhân viên một cách hồ đồ không làm bạn trông thông minh hơn.

2/ Bài học số 2

Một ông vua nọ do chán chuyện triều đình nên mua một con khỉ đem về. Con khỉ làm trò rất hay nên được vua sủng ái, đi đâu cũng mang theo, cho mặc quần áo, giao cả kiếm cho giữ. Một hôm, vua ra vườn thượng uyển ngủ. Có con ong bay đến đậu lên đầu vua. Khỉ muốn đuổi ong, lấy kiếm nhắm vào ong mà chém. Đức vua băng hà.

+ Bài học kinh doanh rút ra:

Trao quyền cho những kẻ không có năng lực thì luôn phải cảnh giác.

3/ Bài học số 03

Quạ thấy chó ngậm khúc xương quá ngon, bèn đánh liều lao xuống mổ vào đầu chó. Bị bất ngờ, chó bỏ chạy để lại khúc xương. Quạ ngoạm lấy khúc xương nhưng nặng quá không tha nổi. Chó, sau khi hoàn hồn, thấy kẻ tấn công chỉ là con quạ nên quay lại táp một cú, quạ chết tươi.

+ Bài học kinh doanh rút ra:

Đừng chiếm thị trường nếu bạn biết là không giữ được nó.

4/ Bài học số 04

Ba con thú dữ là sói, gấu và cáo thay nhau ức hiếp đàn dê. Dê đầu đàn bèn nói với cả bầy: “Ta nên mời một trong ba gã sói, gấu hay cáo làm thủ lĩnh của chúng ta”. Cả đàn dê bất bình, nhưng ba “hung thần” nghe tin này rất mừng. Thế là chúng quay sang tranh giành nhau quyền lãnh đạo, cuối cùng cáo dùng bẫy hại chết được sói và gấu. Nhưng rồi một mình nó không còn ức hiếp đàn dê được nữa.

+ Bài học kinh doanh rút ra:

Hãy thận trọng khi nghe tin bạn sắp được làm sếp!

5/ Bài học số 05

Chàng yêu nàng từ thuở nàng mười lăm mười sáu tuổi. Cả hai lén lút đi lại, quan hệ, quậy gia đình, trốn nhà đi, dọa chết nếu không được chấp nhận. Nếu quan hệ ấy kéo dài một năm, được gọi là phạm pháp, dụ dỗ trẻ vị thành niên, có nguy cơ ra tòa thụ án. Nếu mối tình ấy kéo dài ba năm, được gọi là yêu trộm, tình yêu oan trái. Nếu mối tình kéo dài sáu bảy năm, sẽ được gọi là tình yêu đích thực, vượt núi trèo đèo qua bao khó khăn để yêu nhau.

+ Bài học kinh doanh rút ra:

Bạn làm gì chả quan trọng, quan trọng là bạn làm được trong … bao lâu!

6/ Bài học số 06

Một nàng cave, nếu ngủ với thợ thuyền hoặc lao động ngoại tỉnh, thì bị gọi là đối tượng xã hội. Nếu ngủ với đại gia lừng lẫy, thì được gọi là chân dài. Nếu ngủ với một ngôi sao sân cỏ hoặc màn bạc, sẽ được đàng hoàng lên báo kể chuyện “nghề nghiệp” và trưng ảnh hở da thịt giữa công chúng, không ai có ý định bắt nàng.

+ Bài học kinh doanh rút ra:

Bạn làm gì chả quan trọng, quan trọng là bạn làm điều đó với ai!

7/ Bài học số 07

Phòng tắm công cộng bỗng dưng bị chập điện gây hỏa hoạn lớn, vô số chị em chạy túa ra đường mà không kịp mặc gì. Những nàng thông minh là người không lấy tay che thân thể, mà lấy tay che… mặt.

+ Bài học kinh doanh rút ra:

Hãy quan tâm tới mấu chốt của mọi vấn đề.

8/ Bài học số 08

Một nàng gái ế chạy tới đồn cảnh sát tố cáo: “Tôi đã cẩn thận để tiền trong áo lót, thế mà thằng cha đẹp trai đứng cạnh tôi ở trên xe bus đông đúc đã móc lấy mất tiền của tôi!”.
Cảnh sát ngạc nhiên: “Tại sao nó có thể móc tiền được ở một vị trí “nhạy cảm” như thế, mà cô không phát hiện ra?”
Cô nàng gái ế thút thít: “Ai ngờ được là nó chỉ muốn moi tiền?”

+ Bài học kinh doanh rút ra:

Một nhà kinh doanh tài ba là người moi được tiền của khách hàng trong lúc đang khiến khách hàng sung sướng ngất ngây.

9/ Bài học số 09

Nhân viên vệ sinh của công ty rất buồn phiền vì các quý ông thường lơ đãng khi vào nhà vệ sinh. Để giải quyết những vũng nước vàng khè dưới nền toilette, công ty dán lên tường, phía trên bệ xí nam một tờ giấy: “Không tiểu tới bô chứng tỏ bạn bị ngắn, tiểu ra ngoài bô chứng tỏ bạn bị… ủ rũ!”. Ngay từ ngày hôm sau, toilette nam sạch bóng và không còn quý ông nào lơ đãng nữa.

+ Bài học kinh doanh rút ra:

Hãy chứng minh cho khách hàng thấy vấn đề một cách cụ thể, ấn tượng.

10/ Bài học số 10

Bố mẹ nàng mở cuộc thi tuyển con rể. Chàng A nói, tài khoản có một triệu đô. Chàng B khoe, có biệt thự hai triệu đô. Bố mẹ nàng có vẻ ưng lắm. Chàng C nói, cháu chả có gì cả, thưa các bác. Cháu chỉ có mỗi một đứa con, hiện đang nằm trong bụng của con gái các bác!

+ Bài học kinh doanh rút ra:

Muốn cạnh tranh với đối thủ, cần có tay trong!

Những điểm mới của Java 12

Java 12 chính thức được tung ra vào ngày 19/03/2019 với rất nhiều tính năng mới. So với phiên bản gần nhất là 11 thì phiên bản JDK 12 có khoảng 90 điểm và 109 so với bản 10. Các thay đổi này tạm nhóm trong 4 nhóm chính như sau: Ngôn ngữ Java, thư viện, JVM (máy ảo java) và các tính năng khác.

Thay đổi về ngôn ngữ

Điểm đáng chú ý nhất trên phiên bản JDK 12 này là biểu thức switch (switch expression – JEP 325). Bằng cách mở rộng câu lệnh switch này mà lập trình viên có thể sử dụng như một câu lệnh hoặc một biểu thức.

Ví dụ về câu lệnh switch truyền thống:

int numLetters;
switch (day) {
case MONDAY:
case FRIDAY:
case SUNDAY:
numLetters = 6;
break;
case TUESDAY:
numLetters = 7;
break;
case THURSDAY:
case SATURDAY:
numLetters = 8;
break;
case WEDNESDAY:
numLetters = 9;
break;
default:
throw new IllegalStateException("Huh? " + day);
}

Bây giờ bạn có thể viết thành dạng sau:

int numLetters = switch (day) {
case MONDAY, FRIDAY, SUNDAY -> 6;
case TUESDAY -> 7;
case THURSDAY, SATURDAY -> 8;
case WEDNESDAY -> 9;
default -> throw new IllegalStateException("Huh? " + day);
};

Quan sát vào cú pháp mới này ta có thể nhận thấy có 2 thay đổi ở đây. Thứ nhất là thay vì phải chia từng trường hợp một thì ta có thể gom thành 1 nhóm, phân tách nhau bởi dấu phẩy (“,”). Điểm thứ 2 là toán tử Lambda (->) được sử dụng để đơn giản hóa cách chúng ta thể hiện giá trị được trả về từ biểu thức switch.

Thư viện (Libraries)

Có một thay đổi nhỏ rất hữu ích đó là Stream API có 1 Collector mới, được cung cấp bởi lớp Collectors. Collector mới này thu được bằng cách sử dụng phương thức teeing. Teeing collector nhận vào 3 đối số, 2 collectors, và một BiFunction. Để hiểu rõ hơn về điểm này các bạn có thể xem sơ đồ sau:

Tất cả các giá trị từ input stream sẽ được truyền vào mỗi collector. Kết quả của mỗi collector sẽ được truyền đi dưới dạng đối số cho BiFunction để xử lý và đưa ra kết quả cuối cùng.

Để hiểu rõ hơn về khái niệm này, chúng ta có thể xem ví dụ đơn giản sau, về cách tính trung bình.

/* Assume Collectors is statically imported */
double average = Stream.of(1, 4, 2, 7, 4, 6, 5)
.collect(teeing(
summingDouble(i -> i), 
counting(),
(sum, n) -> sum / n));

Collector đầu tiên sẽ tính tổng của các số, collector thứ 2 sẽ thực hiện đếm số lượng số. BiFunction sẽ thực hiện lấy tổng chia số lượng ra số trung bình.

java.io

InputStream skipNBytes(long n): hàm này sẽ bỏ qua và từ chối chính xác n bytes dữ liệu. Nếu n bằng 0 hoặc nhỏ hơn thì sẽ không có byte nào được bỏ qua.

java.lang

Trong package java.lang có thêm một package con là: java.lang.constant

JVM Constants API cung cấp các loại tham chiếu tượng trưng để mô tả cho từng dạng hằng số (class, loadable constant, MethodHandleMethodHandle constant, and MethodType constant). Các class sau sẽ có thêm hàm describeConstable() 

  • Class
  • Double
  • Enum
  • Float
  • Integer
  • Long
  • String
  • MethodHandle
  • MethodType
  • VarHandle

Các class sau sẽ chứa 1 hàm resolveConstantDesc():

  • Double
  • Enum.EnumDesc
  • Float
  • Integer
  • Long
  • String

java.lang.Character

Các class con đã được cập nhật để thêm các khối Unicode mới.

  • Chess Symbols
  • Mayan numerals
  • Sogdian
  • Old Sogdian

java.lang.String

indent(): thêm các ký tự khoảng trắng vào đầu chuỗi. Nếu giá trị truyền vào là một số âm thì số lượng các ký tự khoảng trắng tương ứng ở đầu chuỗi sẽ bị lược bỏ.
transform(): phương thức này cho phép chúng ta áp dụng một biểu thức lambda vào một chuỗi. Ví dụ:

String str = "1000";       
Integer integer = str.transform(Integer::parseInt);
System.out.println(integer);

java.lang.invoke

VarHandle: thêm hàm toString, trả về một mô tả ngắn gọn
java.net.SecureCacheResponse và java.net.ssl.HttpsConnection có phương thức mới: getSSLSession()

java.nio.files

Class Files có thêm phương thức mới: mismatch(), phương thức này sẽ tìm và trả về vị trí đầu tiên của byte không match trong nội dung của 2 file, trả về -1L nếu match.

Danh sách các website và blog về Java uy tín

Top 10 website về Java

1. Javaworld

2. Java Code Geeks

3. InfoQ | Java

4. DZone

5. The Server Side

6. Jaxenter | Java

7. The Spring Blog

8. DeveloperWorks: Java technology

9. Baeldung

10. Voxxed

Top Java Developers Blogs (21 blogs)

1. Mkyong

2. Vogella | Java

3. Java Specialists Newsletter

4. Javarevisited

5. Java Papers | Java

6. Stephen Colebourne’s blog

7. ProgramCreek

8. Bozho’s tech blog

9. Miles to go 3.0 …

10. jOOQ Blog | Java

11. Petri Kainulainen

12. NoBlogDefFound

13. Vlad Mihalcea’s Blog

14. CodeFX

15. Thought on Java

16. A Java Geek

17. Jenkov Tutorials

18. Adam Bien’s Weblog

19. Java2S | Java

20. How to do in Java

21. Inspired by Actual Events

How to replace text in Word file using Apache POI

Last couple of days, I faced an issue with replace text in Microsoft Word file, using Apache POI library, version 3.15.

The existing solution to replace the text I did follow as below:

package org.kodejava.example.poi;

import org.apache.poi.hwpf.HWPFDocument;
import org.apache.poi.hwpf.usermodel.CharacterRun;
import org.apache.poi.hwpf.usermodel.Paragraph;
import org.apache.poi.hwpf.usermodel.Range;
import org.apache.poi.hwpf.usermodel.Section;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;

public class WordReplaceText {
    public static final String SOURCE_FILE = "lipsum.doc";
    public static final String OUTPUT_FILE = "new-lipsum.doc";

    public static void main(String[] args) throws Exception {
        WordReplaceText instance = new WordReplaceText();
        HWPFDocument doc = instance.openDocument(SOURCE_FILE);
        if (doc != null) {
            doc = instance.replaceText(doc, "o", "0");
            instance.saveDocument(doc, OUTPUT_FILE);
        }
    }

    private HWPFDocument replaceText(HWPFDocument doc, String findText, String replaceText) {
        Range r = doc.getRange();
        for (int i = 0; i < r.numSections(); ++i) {
            Section s = r.getSection(i);
            for (int j = 0; j < s.numParagraphs(); j++) {
                Paragraph p = s.getParagraph(j);
                for (int k = 0; k < p.numCharacterRuns(); k++) {
                    CharacterRun run = p.getCharacterRun(k);
                    String text = run.text();
                    if (text.contains(findText)) {
                        run.replaceText(findText, replaceText);
                    }
                }
            }
        }
        return doc;
    }

    private HWPFDocument openDocument(String file) throws Exception {
        URL res = getClass().getClassLoader().getResource(file);
        HWPFDocument document = null;
        if (res != null) {
            document = new HWPFDocument(new POIFSFileSystem(
                    new File(res.getPath())));
        }
        return document;
    }

    private void saveDocument(HWPFDocument doc, String file) {
        try (FileOutputStream out = new FileOutputStream(file)) {
            doc.write(out);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

The problem is: when I have multiple “key” on the documents which is appear on paragraphs or in the table, the above function cannot replace all of them. Some keys are still there, cannot be replaced. I found an other solution on github that submitted by deividasstr. This solution solved my issues. You can explore at: https://github.com/deividasstr/docx-word-replacer. There are some main java files:

  • OnWordFoundCallback.java
  • TextReplacer.java
  • WordFinder.java
  • WordReplacer.java
package com.xandryex.utils;

import org.apache.poi.xwpf.usermodel.XWPFRun;

import java.util.List;

interface OnWordFoundCallback {

    void onWordFoundInRun(XWPFRun run);
    void onWordFoundInPreviousCurrentNextRun(List<XWPFRun> runs, int currentRun);
}
package com.xandryex.utils;

import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFRun;

import java.util.List;
import java.util.regex.Pattern;

public class TextReplacer extends WordFinder {

    private static int DEFAULT_TEXT_POS = 0;

    private String replacement;
    private String bookmark;

    public void replaceInText(XWPFDocument document, String bookmark, String replacement) {
        this.replacement = replacement;
        this.bookmark = bookmark;
        findWordsInText(document, bookmark);
    }

    public void replaceInTable(XWPFDocument document, String bookmark, String replacement) {
        this.replacement = replacement;
        this.bookmark = bookmark;
        findWordsInTable(document, bookmark);
    }

    @Override
    public void onWordFoundInRun(XWPFRun run) {
        replaceWordInRun(run);
    }

    @Override
    public void onWordFoundInPreviousCurrentNextRun(List<XWPFRun> runs, int currentRun) {
        replaceWordInPreviousCurrentNextRuns(runs, currentRun);
    }

    private void replaceWordInPreviousCurrentNextRuns(List<XWPFRun> runs, int currentRun) {
        boolean replacedInPreviousRun = replaceRunTextStart(runs.get(currentRun - 1));
        if (replacedInPreviousRun) {
            deleteTextFromRun(runs.get(currentRun));
        } else {
            replaceRunTextStart(runs.get(currentRun));
        }
        cleanRunTextStart(runs.get(currentRun + 1));
    }

    private void deleteTextFromRun(XWPFRun run) {
        run.setText("", DEFAULT_TEXT_POS);
    }

    //replaceAll() first parameter is used as regex pattern so normally special chars have to be escaped.
    //Pattern.quote() transforms given string into literal where special chars are ignored, thus can be used without escaping
    private void replaceWordInRun(XWPFRun run) {
        String replacedText = run.getText(DEFAULT_TEXT_POS).replaceAll(Pattern.quote(bookmark), replacement);
        run.setText(replacedText, DEFAULT_TEXT_POS);
    }

    private boolean replaceRunTextStart(XWPFRun run) {
        String text = run.getText(DEFAULT_TEXT_POS);
        String remainingBookmark = getRemainingBookmarkStart(text, bookmark);
        if (!remainingBookmark.isEmpty()) {
            text = text.replace(remainingBookmark, replacement);
            run.setText(text, DEFAULT_TEXT_POS);
            return true;
        }
        return false;
    }

    private void cleanRunTextStart(XWPFRun run) {
        String text = run.getText(DEFAULT_TEXT_POS);
        String remainingBookmark = getRemainingBookmarkEnd(text, bookmark);
        text = text.replace(remainingBookmark, "");
        run.setText(text, DEFAULT_TEXT_POS);
    }

    private String getRemainingBookmarkEnd(String text, String bookmark) {
        if (!text.startsWith(bookmark)) {
            return getRemainingBookmarkEnd(text, bookmark.substring(1, bookmark.length()));
        } else {
            return bookmark;
        }
    }

    private String getRemainingBookmarkStart(String text, String bookmark) {
        if (!text.endsWith(bookmark)) {
            return getRemainingBookmarkStart(text, bookmark.substring(0, bookmark.length() - 1));
        } else {
            return bookmark;
        }
    }
}
package com.xandryex.utils;

import org.apache.poi.xwpf.usermodel.*;

import java.util.*;

abstract class WordFinder implements OnWordFoundCallback {

    private static int DEFAULT_POS = 0;
    private static int DEFAULT_LAST_USED_RUN = -1;

    private String bookmark;

    /**
     * Checks if XWPFDocument tables contain a given bookmark. Checks runs of all paragraphs if searchable text is in one or
     * scattered in runs around it. It does not check separate paragraphs if text is scattered amongst them.
     *
     * @param doc  XWPFDocument
     * @param word to be searched
     */
    void findWordsInTable(XWPFDocument doc, String word) {
        this.bookmark = word;
        for (XWPFTable t : doc.getTables()) {
            checkTable(t);
        }
    }

    /**
     * Checks if XWPFDocument text contains a given bookmark. Checks runs of all paragraphs if searchable text is in one or
     * scattered in runs around it. It does not check separate paragraphs if text is scattered amongst them.
     *
     * @param doc  XWPFDocument
     * @param word to be searched
     */
    void findWordsInText(XWPFDocument doc, String word) {
        this.bookmark = word;
        for (XWPFParagraph p : doc.getParagraphs()) {
            if (paragraphNotNullAndHasRuns(p)) {
                checkInParagraph(p);
            }
        }
    }

    private void checkTable(XWPFTable t) {
        if (t.getRows() == null) return;
        for (XWPFTableRow r : t.getRows()) {
            checkRow(r);
        }
    }

    private void checkRow(XWPFTableRow r) {
        if (r.getTableCells() == null) return;
        for (XWPFTableCell cell : r.getTableCells()) {
            checkCell(cell);
        }
    }

    private void checkCell(XWPFTableCell cell) {
        if (cell.getParagraphs() == null) return;
        for (XWPFParagraph p : cell.getParagraphs()) {
            if (paragraphNotNullAndHasRuns(p)) {
                checkInParagraph(p);
            }
        }
    }

    private void checkInParagraph(XWPFParagraph p) {
        List<XWPFRun> runs = p.getRuns();
        int lastUsedRun = DEFAULT_LAST_USED_RUN;
        for (int runIndex = 0; runIndex < runs.size(); runIndex++) {
            XWPFRun run = p.getRuns().get(runIndex);
            if (isRunNotNullAndNotEmpty(run)) {
                String text = run.getText(DEFAULT_POS);
                //System.out.println(runIndex + " " + text);  //Uncomment for printing the runs
                if (text.contains(bookmark)) {
                    onWordFoundInRun(run);
                    lastUsedRun = runIndex;
                } else if (nextRunHasText(runs, runIndex)
                        && !nextRunsText(runs, runIndex).contains(bookmark)
                        && isWordInPreviousCurrentNextRuns(runs, lastUsedRun, runIndex)) {
                    onWordFoundInPreviousCurrentNextRun(runs, runIndex);
                }
            }
        }
    }

    private boolean isWordInPreviousCurrentNextRuns(List<XWPFRun> runs, int lastUsedRun, int runIndex) {
        return isNotFirstRun(runIndex)
                && previousRunHasText(runs, runIndex)
                && previousRunWasNotUsed(lastUsedRun, runIndex)
                && lastThisNextRunText(runs, runIndex).contains(bookmark);
    }

    private boolean previousRunWasNotUsed(int lastUsedRun, int runIndex) {
        return lastUsedRun != runIndex - 1;
    }

    private boolean paragraphNotNullAndHasRuns(XWPFParagraph p) {
        return p != null && !p.getRuns().isEmpty();
    }

    private String lastThisNextRunText(List<XWPFRun> runs, int runIndex) {
        String text = runs.get(runIndex).getText(DEFAULT_POS);
        return lastAndCurrentRunsText(runs, runIndex, text) + nextRunsText(runs, runIndex);
    }

    private boolean nextRunHasText(List<XWPFRun> runs, int runIndex) {
        return runs.size() > runIndex + 1
                && runs.get(runIndex + 1).getText(DEFAULT_POS) != null
                && !runs.get(runIndex + 1).getText(DEFAULT_POS).isEmpty();
    }

    private String nextRunsText(List<XWPFRun> runs, int i) {
        return runs.get(i + 1).getText(DEFAULT_POS);
    }

    private String lastAndCurrentRunsText(List<XWPFRun> runs, int runIndex, String text) {
        return runs.get(runIndex - 1).getText(DEFAULT_POS) + text;
    }

    private boolean previousRunHasText(List<XWPFRun> runs, int runIndex) {
        return runs.get(runIndex - 1).getText(DEFAULT_POS) != null
                && !runs.get(runIndex - 1).getText(DEFAULT_POS).isEmpty();
    }

    private boolean isNotFirstRun(int runIndex) {
        return runIndex > 0;
    }

    private boolean isRunNotNullAndNotEmpty(XWPFRun run) {
        return run != null && run.getText(DEFAULT_POS) != null;
    }
}
package com.xandryex;

import org.apache.poi.xwpf.usermodel.XWPFDocument;
import com.xandryex.utils.TextReplacer;

import java.io.*;

public class WordReplacer {

    private XWPFDocument document;
    private TextReplacer replacer;

    /**
     * Creates WordReplacer with file to modify.
     *
     * @param docxFile file of type docx.
     * @throws IOException thrown if file is not found or is not required type.
     */
    public WordReplacer(File docxFile) throws IOException {
        InputStream inputStream = new FileInputStream(docxFile);
        init(new XWPFDocument(inputStream));
    }

    /**
     * Creates WordReplacer with XWPFDocument to modify.
     * @param xwpfDoc to modify.
     */
    public WordReplacer(XWPFDocument xwpfDoc) {
        init(xwpfDoc);
    }

    private void init(XWPFDocument xwpfDoc) {
        if (xwpfDoc == null) throw new NullPointerException();
        document = xwpfDoc;
        replacer = new TextReplacer();
    }

    /**
     * Replaces all occurrences of a bookmark only in the text of the file with a replacement string.
     * @param bookmark word to replace.
     * @param replacement word of replacement.
     */
    public void replaceWordsInText(String bookmark, String replacement) {
        replacer.replaceInText(document, bookmark, replacement);
    }

    /**
     * Replaces all occurrences of a bookmark only in tables of the file with a replacement string.
     * @param bookmark word to replace.
     * @param replacement word of replacement.
     */
    public void replaceWordsInTables(String bookmark, String replacement) {
        replacer.replaceInTable(document, bookmark, replacement);
    }

    /**
     * Most of the time we want our template files untouched. Creates file from path, saves the modified document to it and returns it.
     * @param path filepath (dirs + filename).
     * @return modified file.
     * @throws Exception thrown if some issues while saving occur - mostly due to unavailable file or permissions.
     */
    public File saveAndGetModdedFile(String path) throws Exception {
        File file = new File(path);
        return saveToFile(file);
    }

    /**
     * Most of the time we want our template files untouched. Saves the modified document to the given file and returns it.
     * @param file to save to.
     * @return modified file.
     * @throws Exception thrown if some issues while saving occur - mostly due to unavailable file or permissions.
     */
    public File saveAndGetModdedFile(File file) throws Exception {
        return saveToFile(file);
    }

    public XWPFDocument getModdedXWPFDoc() {
        return document;
    }

    private File saveToFile(File file) throws Exception {
        FileOutputStream out = null;
        try {
            out = new FileOutputStream(file, false);
            document.write(out);
            document.close();
            return file;
        } catch (Exception e) {
            throw e;
        } finally {
            if (out != null) {
                out.flush();
                out.close();
            }
        }
    }
}

Using:

public static final String REPLACED_WORD = "pogo";
public static final String REPLACED_WORD_2 = "stick";

public static final String TEXT_WITHOUT_BOOKMARK = "Something here";

private static File docxFile;
private static TextReplacer replacer;
private static WordCounter wordCounter;
private XWPFDocument document;

......
docxFile = new File("./src/test/resources/docxfile.docx");
replacer = new TextReplacer();
wordCounter = new WordCounter();

......
replacer.replaceInText(document, WordCounterTest.TEST_DOC_TEST_CASE, REPLACED_WORD);
replacer.replaceInTable(document, WordCounterTest.TEST_DOC_TEST_CASE, REPLACED_WORD);

Many thanks to deividasstr.