package org.nishen.alma.toolkit.tasks; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import javax.ws.rs.client.Entity; import javax.ws.rs.client.WebTarget; import javax.ws.rs.core.MediaType; import javax.xml.bind.JAXBElement; import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVParser; import org.apache.commons.csv.CSVRecord; import org.nishen.alma.toolkit.entity.partners.Address; import org.nishen.alma.toolkit.entity.partners.Address.AddressTypes; import org.nishen.alma.toolkit.entity.partners.Address.Country; import org.nishen.alma.toolkit.entity.partners.Addresses; import org.nishen.alma.toolkit.entity.partners.ContactInfo; import org.nishen.alma.toolkit.entity.partners.Email; import org.nishen.alma.toolkit.entity.partners.Email.EmailTypes; import org.nishen.alma.toolkit.entity.partners.Emails; import org.nishen.alma.toolkit.entity.partners.IsoDetails; import org.nishen.alma.toolkit.entity.partners.Notes; import org.nishen.alma.toolkit.entity.partners.ObjectFactory; import org.nishen.alma.toolkit.entity.partners.Partner; import org.nishen.alma.toolkit.entity.partners.PartnerDetails; import org.nishen.alma.toolkit.entity.partners.PartnerDetails.LocateProfile; import org.nishen.alma.toolkit.entity.partners.PartnerDetails.SystemType; import org.nishen.alma.toolkit.entity.partners.Partners; import org.nishen.alma.toolkit.entity.partners.Phone; import org.nishen.alma.toolkit.entity.partners.Phone.PhoneTypes; import org.nishen.alma.toolkit.entity.partners.Phones; import org.nishen.alma.toolkit.entity.partners.ProfileDetails; import org.nishen.alma.toolkit.entity.partners.ProfileType; import org.nishen.alma.toolkit.entity.partners.RequestExpiryType; import org.nishen.alma.toolkit.entity.partners.Status; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.gargoylesoftware.htmlunit.TextPage; import com.gargoylesoftware.htmlunit.WebClient; import com.gargoylesoftware.htmlunit.html.HtmlPage; import com.gargoylesoftware.htmlunit.html.HtmlTable; import com.gargoylesoftware.htmlunit.html.HtmlTableRow; import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.name.Named; /** * A task to update Alma Resource Partners with LADD and Te Puna * partners. This task updates the partner list, adding or updating * codes, addresses and status (suspended/not suspended) * * * @author nishen.naidoo@mq.edu.au */ public class TaskUpdateResourcePartners implements Task { private static final Logger log = LoggerFactory.getLogger(TaskUpdateResourcePartners.class); private static final String TASKNAME = "updateResourcePartners"; private static final int PARTNERS_LIMIT = 100; private static final ObjectFactory of = new ObjectFactory(); private Properties config; private Provider<WebTarget> webTargetProviderAlma; private Provider<WebClient> webClientProvider; private String laddUrl; private String tepunaUrl; @Inject private TaskUpdateResourcePartners(@Named("app.cmdline") final String[] args, @Named("app.config") final Properties config, @Named("ws.url.alma") Provider<WebTarget> webTargetProviderAlma, Provider<WebClient> webClientProvider) { this.config = config; this.laddUrl = config.getProperty("ws.url.ladd"); this.tepunaUrl = config.getProperty("ws.url.tepuna"); this.webTargetProviderAlma = webTargetProviderAlma; this.webClientProvider = webClientProvider; log.debug("initialised taskupdateresourcepartners"); } @Override public void run() { log.info("executing task: {}", this.getClass().getSimpleName()); ConcurrentMap<String, Partner> almaPartners = getAlmaPartners(); ConcurrentMap<String, Partner> partners = getLaddPartners(); partners.putAll(getTepunaPartners()); try { WebTarget t = webTargetProviderAlma.get().path("partners"); ExecutorService executor = Executors.newFixedThreadPool(6); for (String s : partners.keySet()) { Partner p = partners.get(s); Partner ap = almaPartners.get(s); if (ap == null) { log.debug("starting thread for create partner: {}", p.getPartnerDetails().getCode()); executor.execute(new UpdatePartnerTask(t, p, false)); } else if (!isEqual(p, ap)) { log.debug("starting thread for update partner: {}", p.getPartnerDetails().getCode()); executor.execute(new UpdatePartnerTask(t, p, true)); } } executor.shutdown(); executor.awaitTermination(1L, TimeUnit.HOURS); } catch (InterruptedException ie) { log.error("executor awaiting termination was interrupted: {}", ie); log.debug("{}", ie); } catch (Exception e) { log.error("execution failure: {}", e); log.debug("{}", e); } } public Partner getAlmaPartner(String nuc) { WebTarget t = webTargetProviderAlma.get().path("partners").path(nuc); Partner result = t.request(MediaType.APPLICATION_XML).get(Partner.class); return result; } public ConcurrentMap<String, Partner> getAlmaPartners() { ConcurrentMap<String, Partner> partnerMap = new ConcurrentHashMap<String, Partner>(); long offset = 0; long total = -1; long count = 0; WebTarget target = webTargetProviderAlma.get().path("partners"); try { ExecutorService executor = Executors.newFixedThreadPool(6); log.debug("getAlmaPartners [count/total/offset]: {}/{}/{}", count, total, offset); Future<Partners> initial = executor.submit(new FetchResourcePartners(target, offset)); Partners partners = initial.get(); total = partners.getTotalRecordCount(); offset += PARTNERS_LIMIT; count += partners.getPartner().size(); for (Partner p : partners.getPartner()) partnerMap.put(p.getPartnerDetails().getCode(), p); List<Future<Partners>> partial = new ArrayList<Future<Partners>>(); while (count < total) { log.debug("getAlmaPartners [count/total/offset]: {}/{}/{}", count, total, offset); partial.add(executor.submit(new FetchResourcePartners(target, offset))); offset += PARTNERS_LIMIT; count += partners.getPartner().size(); } for (Future<Partners> future : partial) { partners = future.get(); for (Partner p : partners.getPartner()) partnerMap.put(p.getPartnerDetails().getCode(), p); } executor.shutdown(); } catch (ExecutionException ee) { log.error("execution failed: {}", ee.getMessage(), ee); } catch (InterruptedException ie) { log.error("execution interrupted: {}", ie.getMessage(), ie); } return partnerMap; } public ConcurrentMap<String, Partner> getLaddPartners() { String prefix = "NLA"; String institutionCode = config.getProperty("ladd.institution.code"); ConcurrentMap<String, Partner> result = new ConcurrentHashMap<String, Partner>(); WebClient webClient = webClientProvider.get(); HtmlPage page = null; try { page = webClient.getPage(laddUrl); } catch (IOException e) { log.error("unable to acquire page: {}", laddUrl); return result; } HtmlTable table = (HtmlTable) page.getElementById("suspension"); for (HtmlTableRow row : table.getRows()) { String nuc = null; try { nuc = row.getCell(0).asText(); if ("NUC symbol".equals(nuc) || institutionCode.equals(nuc)) { log.debug("skipping nuc: {}", nuc); continue; } String org = row.getCell(1).asText(); org = org.replace("&", "and"); boolean suspended = "Suspended".equals(row.getCell(3).asText()); Partner partner = new Partner(); partner.setLink("https://api-ap.hosted.exlibrisgroup.com/almaws/v1/partners/" + nuc); PartnerDetails partnerDetails = new PartnerDetails(); partner.setPartnerDetails(partnerDetails); ProfileDetails profileDetails = new ProfileDetails(); partnerDetails.setProfileDetails(profileDetails); profileDetails.setProfileType(ProfileType.ISO); RequestExpiryType requestExpiryType = new RequestExpiryType(); requestExpiryType.setValue("INTEREST_DATE"); requestExpiryType.setDesc("Expire by interest date"); IsoDetails isoDetails = new IsoDetails(); profileDetails.setIsoDetails(isoDetails); isoDetails.setAlternativeDocumentDelivery(false); isoDetails.setIllServer(config.getProperty("alma.ill.server")); isoDetails.setIllPort(Integer.parseInt(config.getProperty("alma.ill.port"))); isoDetails.setIsoSymbol(prefix + ":" + nuc); isoDetails.setSendRequesterInformation(false); isoDetails.setSharedBarcodes(true); isoDetails.setRequestExpiryType(requestExpiryType); SystemType systemType = new SystemType(); systemType.setValue("LADD"); systemType.setDesc("LADD"); LocateProfile locateProfile = new LocateProfile(); locateProfile.setValue("LADD"); locateProfile.setDesc("LADD Locate Profile"); partnerDetails.setStatus(suspended ? Status.INACTIVE : Status.ACTIVE); partnerDetails.setCode(nuc); partnerDetails.setName(org); partnerDetails.setSystemType(systemType); partnerDetails.setAvgSupplyTime(4); partnerDetails.setDeliveryDelay(4); partnerDetails.setCurrency("AUD"); partnerDetails.setBorrowingSupported(true); partnerDetails.setBorrowingWorkflow("LADD_Borrowing"); partnerDetails.setLendingSupported(true); partnerDetails.setLendingWorkflow("LADD_Lending"); partnerDetails.setLocateProfile(locateProfile); partnerDetails.setHoldingCode(nuc); ContactInfo contactInfo = new ContactInfo(); partner.setContactInfo(contactInfo); Addresses addresses = new Addresses(); contactInfo.setAddresses(addresses); Emails emails = new Emails(); contactInfo.setEmails(emails); Phones phones = new Phones(); contactInfo.setPhones(phones); Notes notes = new Notes(); partner.setNotes(notes); result.put(partner.getPartnerDetails().getCode(), partner); } catch (Exception e) { log.error("failed to process partner: {}", nuc); } } return result; } public ConcurrentMap<String, Partner> getTepunaPartners() { String prefix = "NLNZ"; String institutionCode = config.getProperty("ladd.institution.code"); ConcurrentMap<String, Partner> result = new ConcurrentHashMap<String, Partner>(); WebClient webClient = webClientProvider.get(); TextPage page = null; try { log.debug("tepuna url: {}", tepunaUrl); page = webClient.getPage(tepunaUrl); } catch (IOException e) { log.error("unable to acquire page: {}", tepunaUrl); return result; } log.debug("{}", page.getContent()); try (CSVParser parser = CSVParser.parse(page.getContent(), CSVFormat.DEFAULT.withHeader())) { for (CSVRecord record : parser) { String nuc = record.get(0); if ("NUC symbol".equals(nuc) || institutionCode.equals(nuc)) { log.debug("skipping nuc: {}", nuc); continue; } nuc = prefix + ":" + nuc; String org = record.get(2); Partner partner = new Partner(); partner.setLink("https://api-ap.hosted.exlibrisgroup.com/almaws/v1/partners/" + nuc); PartnerDetails partnerDetails = new PartnerDetails(); partner.setPartnerDetails(partnerDetails); ProfileDetails profileDetails = new ProfileDetails(); partnerDetails.setProfileDetails(profileDetails); profileDetails.setProfileType(ProfileType.ISO); RequestExpiryType requestExpiryType = new RequestExpiryType(); requestExpiryType.setValue("INTEREST_DATE"); requestExpiryType.setDesc("Expire by interest date"); IsoDetails isoDetails = new IsoDetails(); profileDetails.setIsoDetails(isoDetails); isoDetails.setAlternativeDocumentDelivery(false); isoDetails.setIllServer(config.getProperty("alma.ill.server")); isoDetails.setIllPort(Integer.parseInt(config.getProperty("alma.ill.port"))); isoDetails.setIsoSymbol(nuc); isoDetails.setSendRequesterInformation(false); isoDetails.setSharedBarcodes(true); isoDetails.setRequestExpiryType(requestExpiryType); SystemType systemType = new SystemType(); systemType.setValue("LADD"); systemType.setDesc("LADD"); LocateProfile locateProfile = new LocateProfile(); locateProfile.setValue("LADD"); locateProfile.setDesc("LADD Locate Profile"); partnerDetails.setStatus(Status.ACTIVE); partnerDetails.setCode(nuc); partnerDetails.setName(org); partnerDetails.setSystemType(systemType); partnerDetails.setAvgSupplyTime(4); partnerDetails.setDeliveryDelay(4); partnerDetails.setCurrency("AUD"); partnerDetails.setBorrowingSupported(true); partnerDetails.setBorrowingWorkflow("LADD_Borrowing"); partnerDetails.setLendingSupported(true); partnerDetails.setLendingWorkflow("LADD_Lending"); partnerDetails.setLocateProfile(locateProfile); partnerDetails.setHoldingCode(nuc); ContactInfo contactInfo = new ContactInfo(); partner.setContactInfo(contactInfo); Addresses addresses = new Addresses(); contactInfo.setAddresses(addresses); String s = record.get(5); if (s == null || "".equals(s.trim())) s = record.get(4); if (s != null && !"".equals(s.trim())) { Address address = getAddress(s); address.setPreferred(true); address.setAddressTypes(new AddressTypes()); address.getAddressTypes().getAddressType().add("ALL"); addresses.getAddress().add(address); log.debug("nuc/address [{}]: {}", nuc, address); } Emails emails = new Emails(); contactInfo.setEmails(emails); s = record.get(6); if (s != null && !"".equals(s.trim())) { Email email = new Email(); email.setEmailTypes(new EmailTypes()); email.setEmailAddress(s); email.setPreferred(true); email.setDescription("Primary Email Address"); email.getEmailTypes().getEmailType().add("ALL"); emails.getEmail().add(email); log.debug("nuc/email1 [{}]: {}", nuc, email); } s = record.get(13); if (s != null && !"".equals(s.trim())) { Email email = new Email(); email.setEmailTypes(new EmailTypes()); email.setEmailAddress(s); email.setPreferred(true); String m = record.get(12); if (m != null && !"".equals(m)) email.setDescription("Manager Email Address: " + m); else email.setDescription("Manager Email Address"); email.getEmailTypes().getEmailType().add("ALL"); emails.getEmail().add(email); log.debug("nuc/email2 [{}]: {}", nuc, email); } Phones phones = new Phones(); contactInfo.setPhones(phones); s = record.get(15); if (s == null || "".equals(s.trim())) s = record.get(7); if (s != null && !"".equals(s.trim())) { Phone phone = new Phone(); phone.setPhoneTypes(new PhoneTypes()); phone.setPhoneNumber(s); phone.setPreferred(true); phone.setPreferredSMS(false); phone.getPhoneTypes().getPhoneType().add("ALL"); phones.getPhone().add(phone); log.debug("nuc/phone [{}]: {}", nuc, phone); } Notes notes = new Notes(); partner.setNotes(notes); result.put(partner.getPartnerDetails().getCode(), partner); } } catch (IOException ioe) { log.error("unable to parse data: {}", tepunaUrl); } return result; } public Address getAddress(String s) { Address address = new Address(); if (s == null || s.trim().length() == 0) return address; List<String> tmpl = Arrays.asList(s.split(" *, *")); List<String> addr = new ArrayList<String>(); for (String tli : tmpl) if (tli != null && !"".equals(tli)) addr.add(0, tli); if (addr.size() == 0) return address; address.setLine1(addr.get(addr.size() - 1)); Country country = new Country(); switch (addr.get(0)) { case "Australia": country.setValue("AUS"); country.setDesc("Australia"); addr.remove(0); address.setCountry(country); break; case "New Zealand": country.setValue("NZL"); country.setDesc("New Zealand"); addr.remove(0); address.setCountry(country); break; default: } if (addr.size() == 0) return address; if (addr.get(0).matches("\\d{4}")) { address.setPostalCode(addr.get(0)); addr.remove(0); if (addr.size() == 0) return address; address.setCity(addr.get(0)); addr.remove(0); if (addr.size() == 0) return address; } else { address.setCity(addr.get(0)); addr.remove(0); if (addr.size() == 0) return address; } Collections.reverse(addr); address.setLine1(addr.get(0)); addr.remove(0); if (addr.size() == 0) return address; address.setLine2(addr.get(0)); addr.remove(0); if (addr.size() == 0) return address; address.setLine3(addr.get(0)); addr.remove(0); if (addr.size() == 0) return address; address.setLine4(addr.get(0)); addr.remove(0); if (addr.size() == 0) return address; address.setLine5(addr.get(0)); addr.remove(0); return address; } public boolean isEqual(Partner a, Partner b) { if (a == null && b == null) return true; if (a == null || b == null) return false; if (a.getContactInfo() != null) if (a.getContactInfo().getAddresses() != null) for (Address address : a.getContactInfo().getAddresses().getAddress()) address.setStartDate(null); if (b.getContactInfo() != null) if (b.getContactInfo().getAddresses() != null) for (Address address : b.getContactInfo().getAddresses().getAddress()) address.setStartDate(null); return a.equals(b); } @Override public Map<String, String> getUsageOptions() { Map<String, String> options = new HashMap<String, String>(); return options; } public static String getTaskName() { return TASKNAME; } private class UpdatePartnerTask implements Runnable { private WebTarget target; private Partner partner; private boolean replace; public UpdatePartnerTask(WebTarget target, Partner partner, boolean replace) { this.target = target; this.partner = partner; this.replace = replace; } @Override public void run() { String m = MediaType.APPLICATION_XML; String action = replace ? "Updating" : "Creating"; log.info("{} partner[{}]: {}", action, partner.getPartnerDetails().getCode(), partner.getPartnerDetails().getName()); Partner result = null; JAXBElement<Partner> p = of.createPartner(partner); String code = partner.getPartnerDetails().getCode(); try { if (replace) { result = target.path(code).request(m).put(Entity.entity(p, m), Partner.class); } else { result = target.request(m).post(Entity.entity(p, m), Partner.class); } } catch (Exception e) { log.error("error adding partner:\n{}", result, e); } log.debug("result:\n{}", result); } } private class FetchResourcePartners implements Callable<Partners> { private WebTarget target; private long offset; public FetchResourcePartners(WebTarget target, long offset) { this.target = target; this.offset = offset; } @Override public Partners call() throws Exception { WebTarget t = target.queryParam("limit", PARTNERS_LIMIT).queryParam("offset", offset); Partners partners = t.request(MediaType.APPLICATION_XML).get(Partners.class); log.debug("fetchResourcePartners [offset]: {}", offset); return partners; } } }