package com.bayer.edam.migration; import java.io.File; import java.io.IOException; import java.net.URI; import java.nio.charset.Charset; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import org.apache.commons.io.FileUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.PropertySource; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; import org.springframework.http.client.BufferingClientHttpRequestFactory; import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.web.client.HttpStatusCodeException; import org.springframework.web.client.RestTemplate; import com.bayer.edam.migration.adam.Campaign; import com.bayer.edam.migration.adam.Classification; import com.bayer.edam.migration.adam.ClassificationList; import com.bayer.edam.migration.adam.EntityId; import com.bayer.edam.migration.adam.Token; import com.bayer.edam.migration.adam.UploadClassification; import com.bayer.edam.migration.adam.UploadRecord; import com.bayer.edam.migration.adam.UploadRecord.WriteClassification; import com.bayer.edam.migration.mam.Entry; import com.bayer.edam.migration.mam.categories.CategoryTree; import com.bayer.edam.migration.mam.categories.CategoryTree.Node; import com.fasterxml.jackson.databind.ObjectMapper; @SpringBootApplication @PropertySource("file:secret.properties") public class MamMigrationSelective implements CommandLineRunner { private static final Logger log = LoggerFactory.getLogger(MamMigrationSelective.class); static final String adamHost = "https://edam-q.bayer.com:2015"; static final String mamHost = "https://service.media-assistant.animalhealth.bayer.com"; private List entrySelection = new ArrayList(Arrays.asList( 72048, 60984 )); static final String categoryJson = "resources/2017-10-19_categories.json"; static final String categoryMapping = "resources/Media_Assistant_Categories_131017_EDITED_without_archive.csv"; static final File entryLog = new File("C:\\Temp\\Migration AH\\files\\entry.log"); static final File problemLog = new File("C:\\Temp\\Migration AH\\files\\problem.log"); static final String fieldSeparator = "|~|"; static final String lineSeparator = "\r\n"; @Value("${mam.auth}") private String mamAuth; @Value("${adam.auth}") private String adamAuth; RestTemplate restTemplate; HttpHeaders adamHeaders, mamHeaders; HttpEntity adamHeadersEntity; // private SimpleDateFormat isoDate = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'"); private SimpleDateFormat stringDate = new SimpleDateFormat("yyyy-MM-dd"); private ObjectMapper jsonMapper = new ObjectMapper(); private List campaignYears; private List campaigns = new LinkedList(); private CategoryTree mamCategoryTree; @Override public void run(String... strings) throws Exception { // XXX readEntryIDsFromFile(); log.info("Write log headers for " + entryLog.getAbsolutePath()); FileUtils.writeStringToFile(entryLog, "Location" + fieldSeparator + "Entry" + lineSeparator, Charset.forName("utf-8")); log.info("Write log headers for " + problemLog.getAbsolutePath()); FileUtils.writeStringToFile(problemLog, "entryId" + fieldSeparator + "Entry" + lineSeparator, Charset.forName("utf-8")); log.info("Read categories"); mamCategoryTree = new CategoryTree(categoryJson, categoryMapping); restTemplate = new RestTemplate(new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory())); List interceptors = new ArrayList(); interceptors.add(new LoggingRequestInterceptor()); restTemplate.setInterceptors(interceptors); adamHeaders = createAdamHeaders(); adamHeadersEntity = new HttpEntity("", adamHeaders); mamHeaders = createMamHeaders(); log.info("Read campaigns"); campaignYears = restTemplate.exchange(adamHost + "/classification/" + Definitions.campaignRoot + "/children", HttpMethod.GET, adamHeadersEntity, ClassificationList.class).getBody().items; for (Classification yearClassification : campaignYears) { List campaignClassifications = restTemplate.exchange(adamHost + "/classification/" + yearClassification.id + "/children", HttpMethod.GET, adamHeadersEntity, ClassificationList.class).getBody().items; for (Classification campaignClassification : campaignClassifications) { //log.debug(yearClassification.name + ": " + campaignClassification.name + ", " + campaignClassification.labels[0].value + ", " + campaignClassification.id); Campaign campaign = new Campaign(campaignClassification.name, campaignClassification.labels[0].value, yearClassification.name); campaign.guid = campaignClassification.id; campaigns.add(campaign); } } HttpEntity entity = new HttpEntity("", mamHeaders); for (int id : entrySelection) { // XXX // if needed start from a certain ID // if (id > 60984 || id < 60984) // continue; log.info("GET entry " + id); Entry entry = restTemplate.exchange(mamHost + "/rest/migration/entry/{id}", HttpMethod.GET, entity, Entry.class, id).getBody(); log.debug("Name: " + entry.name.get("en")); // check exclusion criteria if ((entry.stateId == 5 || entry.stateId == 4) && entry.fileName == null && entry.categories.size() == 0) { log.info("RESULT Skipping entry " + entry.id + ": Status " + entry.stateId + ", no file attached, no category set"); continue; } else if (entry.stateId == 7) { log.info("RESULT Skipping entry " + entry.id + ": Status " + entry.stateId + " (LOCKED_BY_ADMIN)"); continue; } else if (entry.stateId == 8) { log.info("RESULT Skipping entry " + entry.id + ": Status " + entry.stateId + " (ASSIGNED_TO_INDEXATION)"); continue; } else if (entry.stateId == 10) { log.info("RESULT Skipping entry " + entry.id + ": Status " + entry.stateId + " (ARCHIVED)"); continue; } postToAdam(entry); } invalidateAdamToken(); } void postToAdam(Entry entry) throws Exception { UploadRecord record = new UploadRecord(); String entryJson = jsonMapper.writeValueAsString(entry); Set keywords = new HashSet(); // globally applicable classifications record.addClassification("MediaAssistant-Migration"); record.addClassification("Country.Global"); record.addClassification("Agencies.Bayer AH"); record.addClassification("Usage.Restricted"); // media type record.addClassification(Definitions.mediaTypes.get(entry.mediaType.id)); // process categories if (entry.categories.size() == 0) { log.info("RESULT Skipping entry " + entry.id + ": No category assigned, cannot determine business unit and brand"); return; } if (entry.categories.size() > 1) log.warn("Entry " + entry.id + " has " + entry.categories.size() + " categories assigned"); for (int categoryId : entry.categories) { log.debug("Category: " + categoryId); Node node = mamCategoryTree.getNode(categoryId); log.debug("Node: " + node); List path = new LinkedList(); Node tmpNode = node; while (tmpNode.id != CategoryTree.ROOT_NODE_ID) { if (tmpNode.id == 0) { log.info("RESULT Skipping entry " + entry.id + ": Node outside of category tree (" + node.toString() + ")"); return; } path.add(0, tmpNode); tmpNode = tmpNode.parent; } for (Node n : path) { if (n.action.equals("not migrated")) { log.info("RESULT Skipping entry " + entry.id + " because of tag on (parent) category " + n.name + " (" + n.id + ")"); return; } if (n.action.equals("classify")) { String[] parameters = n.parameter.split(","); for (String p : parameters) { // TODO what if "Released" is assigned after "Archived"? (Multiple category problem) if (p.startsWith("Status.")) { if (!p.equals("Status.Released")) record.removeClassification("Status.Released"); else if (record.hasClassificationStartingWith("Status.Draft") || record.hasClassificationStartingWith("Status.Archived")) { log.warn("Classification Status.Released not added, different status already set"); continue; } } if (p.startsWith("BusinessUnit.")) { record.removeClassification("BusinessUnit.CAP"); record.removeClassification("BusinessUnit.FAP"); record.removeClassification("BusinessUnit.General"); } if (p.startsWith("Brand.")) { if (p.equals("Brand.General") && record.hasClassificationStartingWith("Brand.")) { log.warn("Classification Brand.General not added, different brand already set"); continue; } else { record.removeClassification("Brand.General"); } } record.addClassification(p); } } else if (n.action.equals("brand")) { String brandClassificationId = "Brand." + n.name.trim(); record.addClassification(brandClassificationId); } else if (n.action.equals("campaign")) { String campaignName = n.name; String campaignLabel = n.name; String campaignYear; if (n.parameter != null && !n.parameter.trim().equals("")) { campaignLabel = campaignLabel + " " + n.parameter; campaignYear = n.parameter.substring(1, 5); } else { campaignYear = n.name.substring(0, 4); } try { Integer.parseInt(campaignYear); // check whether the year classification already exists Classification yearClassification = null; for (Classification c : campaignYears) { if (c.name.equals(campaignYear)) { yearClassification = c; } } if (yearClassification == null) { log.debug("Create campaign year " + campaignYear); UploadClassification newYear = new UploadClassification(campaignYear, campaignYear); HttpEntity httpEntity = new HttpEntity(newYear, adamHeaders); try { EntityId classificationId = restTemplate.exchange(adamHost + "/classifications", HttpMethod.POST, httpEntity, EntityId.class).getBody(); newYear.guid = classificationId.id; // retrieve the newly created classification back yearClassification = restTemplate.exchange(adamHost + "/classification/" + newYear.guid, HttpMethod.GET, adamHeadersEntity, Classification.class).getBody(); campaignYears.add(yearClassification); } catch (HttpStatusCodeException hsce) { log.error("Could not create campaign year '" + campaignYear + "' for entry " + entry.id + ": " + hsce.getStatusCode().toString()); log.error(hsce.getResponseBodyAsString()); } } // check whether campaign already exists String campaignGuid = null; for (Campaign c : campaigns) { if (c.name.equals(campaignName)) { log.debug("Add existing campaign " + campaignName); campaignGuid = c.guid; record.addClassification(new WriteClassification(campaignGuid, 1)); } } if (campaignGuid == null) { log.info("Create campaign " + campaignYear + "/" + campaignName); Campaign campaign = new Campaign(campaignName, campaignLabel, campaignYear); HttpEntity httpEntity = new HttpEntity(campaign, adamHeaders); try { EntityId campaignId = restTemplate.exchange(adamHost + "/classifications", HttpMethod.POST, httpEntity, EntityId.class).getBody(); campaign.guid = campaignId.id; record.addClassification(new WriteClassification(campaign.guid, 1)); campaigns.add(campaign); } catch (HttpStatusCodeException hsce) { log.error("Could not create campaign '" + campaignName + "' for entry " + entry.id + ": " + hsce.getStatusCode().toString()); log.error(hsce.getResponseBodyAsString()); } } } catch (NumberFormatException nfe) { log.error("Could not parse year from string " + campaignYear); } } else if (n.action.equals("keyword")) { log.debug("Adding keyword " + n.name.trim()); keywords.add(n.name.trim()); } } } // Fields if (entry.name.get("en") != null) { record.addField("byr_Title", entry.name.get("en")); } else { log.warn("Entry " + entry.id + ": No title"); record.addField("byr_Title", "No title"); } // Project lead if (entry.user != null && entry.user.get("id") != null) { if (Definitions.users.get(entry.user.get("id")) != null) { record.addField("byr_ah_projectLead", Collections.singletonList(Definitions.users.get(entry.user.get("id")))); record.addField("byr_ah_brandCommunicationRepresentative", Collections.singletonList(Definitions.users.get(entry.user.get("id")))); } else { log.warn("Entry " + entry.id + ": MAM user " + entry.user.get("id") + " not available, assigning default user"); record.addField("byr_ah_projectLead", Collections.singletonList(Definitions.defaultProjectLead)); record.addField("byr_ah_brandCommunicationRepresentative", Collections.singletonList(Definitions.defaultProjectLead)); } } else { log.warn("Entry " + entry.id + ": No user, assigning default user"); record.addField("byr_ah_projectLead", Collections.singletonList(Definitions.defaultProjectLead)); record.addField("byr_ah_brandCommunicationRepresentative", Collections.singletonList(Definitions.defaultProjectLead)); } // Description / Additional Information String description = entry.description.get("en"); if (description == null) description = ""; else description += "\n\n"; if (entry.comment.get("en") != null) description += "Comment: " + entry.comment.get("en") + "\n\n"; String migrationDescription = "Migrated from Media Assistant ID " + entry.id; if (entry.creationDate != null) migrationDescription += "\nDate of Issue: " + stringDate.format(entry.creationDate); description += migrationDescription; record.addField("byr_ah_description", description); // Keywords if (entry.keyword.get("en") != null && entry.keyword.get("en").length() > 0) { keywords.addAll(Arrays.asList(entry.keyword.get("en").split("\\s*,\\s*"))); } if (keywords.size() > 0) { record.addField("byr_ah_keywords_tl", new ArrayList(keywords)); } // License Information record.addField("byr_ah_licenseHolderList", Collections.singletonList(Definitions.defaultLicenseHolder)); record.addField("byr_ah_licenseAdditionalPermittedUse", Collections.singletonList(Definitions.defaultAdditionalPermittedUse)); if (entry.copyright != null && entry.copyright.organization != null) { String licenseDescription = ""; if (entry.copyright.use != null) licenseDescription += entry.copyright.use; record.addField("byr_ah_licenseComments", licenseDescription); // if license holder is not "Bayer Animal Health GmbH" put on review list if (!entry.copyright.organization.get("id").equals("3740")) log.warn("Entry " + entry.id + ": License Holder is not set to 'Bayer Animal Health GmbH' (organization 3740)"); } else { // if license information is missing put on review list log.warn("Entry " + entry.id + ": Copyright information missing"); } if (entry.gpc != null) record.addField("byr_ah_approvalNumber",entry.gpc.gpcNumber); // Create record HttpEntity httpEntity = new HttpEntity(record, adamHeaders); try { URI location = restTemplate.postForLocation(adamHost + "/records", httpEntity, UploadRecord.class); log.info("RESULT Entry " + entry.id + " migrated to record " + location); FileUtils.writeStringToFile(entryLog, location.toString() + fieldSeparator + entryJson + lineSeparator, Charset.forName("utf-8"), true); } catch (HttpStatusCodeException hsce) { log.error("RESULT Skipping entry " + entry.id + ": Error while creating record " + hsce.getStatusCode().toString()); log.error(hsce.getResponseBodyAsString()); FileUtils.writeStringToFile(problemLog, entry.id + fieldSeparator + entryJson + lineSeparator, Charset.forName("utf-8"), true); } } private void readEntryIDsFromFile() throws IOException { File entryIdListFile = new File("resources/entrylist_2017-10-25.txt"); log.debug("Reading entry IDs from " + entryIdListFile.getAbsolutePath()); List entryIdList = FileUtils.readLines(entryIdListFile, Charset.forName("utf-8")); entrySelection = new ArrayList(); log.debug("Parsing " + entryIdList.size() + " IDs"); for (String s : entryIdList) { entrySelection.add(Integer.parseInt(s)); } } private HttpHeaders createMamHeaders() { HttpHeaders headers = new HttpHeaders(); headers.add("Accept", "application/json"); headers.add("Authorization", "Basic " + mamAuth); headers.add("Content-Type", "application/json"); headers.add("From", "migration@bayer.com"); return headers; } private HttpHeaders createAdamHeaders() { HttpHeaders headers = new HttpHeaders(); headers.add("Registration", "ADAMBAYER"); headers.add("API-VERSION", "1"); headers.add("Accept", "application/hal+json"); headers.add("Content-Type", "application/json"); headers.add("Authorization", "Basic " + adamAuth); headers.add("set-immediateSearchIndexUpdate", "true"); // see https://edam.bayer.com:2015/docs/resources/record#example-records-direct-push HttpEntity entity = new HttpEntity("", headers); Token token = restTemplate.exchange(adamHost + "/auth", HttpMethod.GET, entity, Token.class).getBody(); // replace auth header headers.remove("Authorization"); headers.add("Authorization", "Token " + token.getToken()); // replace accept-charset header (in order to declutter log) headers.add("Accept-Charset", "us-ascii, utf-16, utf-8"); return headers; } private void invalidateAdamToken() { ResponseEntity response = restTemplate.exchange(adamHost + "/auth", HttpMethod.DELETE, adamHeadersEntity, String.class); log.info("Logged out from adam API: " + response.toString()); } /* * // Quoted "Z" to indicate UTC, no timezone offset private static SimpleDateFormat outputDateFormat = new * SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'"); private static SimpleDateFormat inputDateFormat = new SimpleDateFormat("yyyy-MM-dd"); * * public static String toIso8601(String isoDate) { try { return outputDateFormat.format(inputDateFormat.parse(isoDate)); } catch * (ParseException e) { log.error(e.toString()); e.printStackTrace(); } return null; } */ public static void main(String args[]) { SpringApplication.run(MamMigrationSelective.class, args); } }