Skip to main content

Example 1: ReferenceContentMigrator Implementation

public class StoryReferenceMigrator implements ReferencedContentMigrator {

public static final String LEGACY_PREFIX = "image.";

private static final String IMG_TAG_NAME = "img";
private static final String SRC_ATTR_NAME = "src";
private static final String ALT_ATTR_NAME = "alt";
private static final String CLASS_ATTR_NAME = "class";
private static final String BUTTON_TAG_NAME = "button";
private static final String BUTTON_CSS_CLASS = "enhancement";
private static final String DATA_ID_ATTR_NAME = "data-id";
private static final String DATA_REFERENCE_ATTR_NAME = "data-reference";

@Override
public Collection<Class<? extends Migrator>> dependencies() {
Collection<Class<? extends Migrator>> dependencies = new HashSet<>();
dependencies.add(StoryMigrator.class);

return dependencies;
}

@Override
public String getContextName() {
return AcmeMigrationContext.CONTEXT_NAME;
}

@Override
public Class<AcmeMigrationContext> getContextClass() {
return AcmeMigrationContext.class;
}

@Override
public String getLegacyIdPrefix(MigrationContext context) {
return StoryMigrator.LEGACY_PREFIX;
}

@Override
public String getReferencedLegacyIdPrefix(MigrationContext context) {
return LEGACY_PREFIX;
}

@Override
public Class<? extends Recordable> getReferencedContentClass() {
return Image.class;
}

@Override
public Collection<String> getReferencedLegacyIds(Recordable obj) {

if (obj instanceof Story && ((Story) obj).getBody() != null) {
List<String> imageUrls = new ArrayList<>();

for (Object item : ((Story) obj).getBody()) {
if (item instanceof String) {
Document doc = Jsoup.parse((String) item);
Element body = doc.body();

for (Element imgElement : body.getElementsByTag(IMG_TAG_NAME)) {
if (imgElement.hasAttr(SRC_ATTR_NAME)) {
imageUrls.add(imgElement.attr(SRC_ATTR_NAME));
}
}
}
}

return imageUrls;
}

return null;
}

@Override
public void processObject(MigrationContext context, Recordable obj, String imageId, Recordable refObj) {

Story story = (Story) obj;
Image image = (Image) refObj;

if (story.getBody() != null) {
StringBuilder builder = new StringBuilder();

for (Object item : story.getBody()) {

if (item instanceof String) {
Document doc = Jsoup.parse((String) item);
doc.outputSettings().prettyPrint(false);
Element body = doc.body();

// Replace Images with image references.
for (Element imgElement : body.getElementsByTag(IMG_TAG_NAME)) {
String imgSrc = imgElement.attr(SRC_ATTR_NAME);

if (imgElement.hasAttr(SRC_ATTR_NAME) && imageId.equals(imgSrc)) {

if (imgElement.parent() == null) {
// This can happen if same image is referenced twice - it will have been replaced already
// and will error on subsequent iterations.
continue;
}

try {
// Escape whitespace in URL.
String cleanImgSrc = imgSrc.trim().replaceAll(" ", "%20").replaceAll("\r", "").replaceAll("\n", "");
Image checkImage = null;

// Save binary or just a reference (DEV) to image.
StorageItem storageItem;

if (((AcmeMigrationContext) context).isDownloadImages()) {
checkImage = Query.from(Image.class)
.where("migration.legacyId = ?", "image." + cleanImgSrc)
.first();

// Don't download and save again if already pulled binary or if only saved URL reference.
if (checkImage == null || (checkImage.getFile() instanceof UrlStorageItem) || (checkImage.getFile() == null)) {
storageItem = StorageItem.Static.create();

String fileName = cleanImgSrc.substring(cleanImgSrc.lastIndexOf("/") + 1);
String hash = StringUtils.hex(StringUtils.md5(fileName));

// Always use a deterministic filename
storageItem.setPath(hash.substring(0, 2) + "/" + hash + "/" + fileName);

String extension = imgSrc.substring(cleanImgSrc.lastIndexOf(".") + 1);
storageItem.setContentType("image/" + extension);

if (!storageItem.isInStorage()) {
URL url = new URL(cleanImgSrc);
URLConnection c = url.openConnection();
c.setConnectTimeout(3000);
c.setReadTimeout(4000);

storageItem.setData(new ByteArrayInputStream(IoUtils.toByteArray(url.openStream())));
storageItem.save();
}

// Add Image field values.
image.setFile(storageItem);
}

} else {
storageItem = StorageItem.Static.createUrl(cleanImgSrc);

// Add Image field values.
image.setFile(storageItem);
}

if (imgElement.hasAttr(ALT_ATTR_NAME)) {
image.setAltText(imgElement.attr(ALT_ATTR_NAME));
}

// Check for credit
Element sibling = imgElement.nextElementSibling();
if (sibling != null) {
Element creditElement = sibling.select("em").first();

if (creditElement != null) {
image.setCredit(creditElement.text());
}

sibling.remove();
}

String fileName = imgSrc.substring(imgSrc.lastIndexOf("/") + 1);
String title = fileName.split("\\.")[0];
image.setTitle(title);
image.as(Site.ObjectModification.class).setGlobal(true);

image.saveImmediately();

// Replace <img /> tag with Reference as custom RTE tag.
Element rte = doc.createElement(ImageRichTextElement.TAG_NAME)
.attr(ImageRichTextElement.TAG_ATTR_NAME_ID, image.getId().toString())
.text(title);

imgElement.before("<br/>").after("<br/><br/>");

imgElement.replaceWith(rte);

if (story.as(Promotable.Data.class).getPromoImage() == null) {
story.as(Promotable.Data.class).setPromoImage(image);
}

} catch (Exception e) {
e.printStackTrace();
}
}
}

builder.append(body.html());
}
}

if (builder.length() > 0) {
ReferentialText refText = new ReferentialText();
refText.addHtml(builder.toString());

story.setBody(refText);
}
}
}

private Element createRteReferenceElement(Document doc, UUID objectId, Map<String, Object> simpleValues) {
return doc.createElement(BUTTON_TAG_NAME)
.attr(CLASS_ATTR_NAME, BUTTON_CSS_CLASS)
.attr(DATA_ID_ATTR_NAME, objectId.toString())
.attr(DATA_REFERENCE_ATTR_NAME, ObjectUtils.toJson(simpleValues));
}
}