Java tutorial
/* * See the NOTICE file distributed with this work for additional * information regarding copyright ownership. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.phenotips.integration.lims247.internal; import org.phenotips.Constants; import org.phenotips.data.Patient; import org.xwiki.bridge.event.DocumentCreatedEvent; import org.xwiki.bridge.event.DocumentDeletedEvent; import org.xwiki.bridge.event.DocumentUpdatedEvent; import org.xwiki.component.annotation.Component; import org.xwiki.context.Execution; import org.xwiki.model.reference.DocumentReference; import org.xwiki.observation.EventListener; import org.xwiki.observation.event.Event; import java.util.Arrays; import java.util.Collections; import java.util.List; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager; import org.apache.commons.httpclient.methods.PostMethod; import org.apache.commons.httpclient.methods.StringRequestEntity; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import com.xpn.xwiki.XWiki; import com.xpn.xwiki.XWikiContext; import com.xpn.xwiki.XWikiException; import com.xpn.xwiki.doc.XWikiDocument; import com.xpn.xwiki.objects.BaseObject; /** * Pushes updated (or deleted) patient records to remote PhenoTips instances. * * @version $Id: 1807045cf91444955768818c095f2ab98e532c17 $ */ @Component @Named("pushDataToPublicClone") @Singleton public class RemoteSynchronizationEventListener implements EventListener { /** Logging helper object. */ @Inject private Logger logger; /** Provides access to the current request context. */ @Inject private Execution execution; /** HTTP client used for communicating with the remote server. */ private final HttpClient client = new HttpClient(new MultiThreadedHttpConnectionManager()); @Override public String getName() { return "pushDataToPublicClone"; } @Override public List<Event> getEvents() { return Arrays.<Event>asList(new DocumentCreatedEvent(), new DocumentUpdatedEvent(), new DocumentDeletedEvent()); } @Override public void onEvent(Event event, Object source, Object data) { if (event instanceof DocumentDeletedEvent) { handleDelete((XWikiDocument) source); } else { handleUpdate((XWikiDocument) source); } } private void handleUpdate(XWikiDocument doc) { try { if (!isPatient(doc)) { return; } this.logger.debug("Pushing updated document [{}]", doc.getDocumentReference()); XWikiContext context = getXContext(); String payload = doc.toXML(true, false, true, false, context); List<BaseObject> servers = getRegisteredServers(context); if (servers != null && !servers.isEmpty()) { for (BaseObject serverConfiguration : servers) { submitData(payload, serverConfiguration); } } } catch (XWikiException ex) { this.logger.warn("Failed to serialize changed document: {}", ex.getMessage()); } } private void handleDelete(XWikiDocument doc) { if (!isPatient(doc.getOriginalDocument())) { return; } this.logger.debug("Pushing deleted document [{}]", doc.getDocumentReference()); XWikiContext context = getXContext(); List<BaseObject> servers = getRegisteredServers(context); if (servers != null && !servers.isEmpty()) { for (BaseObject serverConfiguration : servers) { deleteData(doc.getDocumentReference().toString(), serverConfiguration); } } } /** * Check if the modified document is a patient record. * * @param doc the modified document * @return {@code true} if the document contains a PatientClass object and a non-empty external identifier, * {@code false} otherwise */ private boolean isPatient(XWikiDocument doc) { BaseObject o = doc.getXObject(Patient.CLASS_REFERENCE); return (o != null && !StringUtils.equals("PatientTemplate", doc.getDocumentReference().getName())); } /** * Get all the trusted remote instances where data should be sent that are configured in the current instance. * * @param context the current request object * @return a list of {@link BaseObject XObjects} with LIMS server configurations, may be {@code null} */ private List<BaseObject> getRegisteredServers(XWikiContext context) { try { XWiki xwiki = context.getWiki(); XWikiDocument prefsDoc = xwiki .getDocument(new DocumentReference(xwiki.getDatabase(), "XWiki", "XWikiPreferences"), context); return prefsDoc .getXObjects(new DocumentReference(xwiki.getDatabase(), Constants.CODE_SPACE, "RemoteClone")); } catch (XWikiException ex) { return Collections.emptyList(); } } /** * Send the changed document to a remote PhenoTips instance. * * @param doc the serialized document to send * @param serverConfiguration the XObject holding the remote server configuration */ private void submitData(String doc, BaseObject serverConfiguration) { // FIXME This should be asynchronous; reimplement! PostMethod method = null; try { String submitURL = getSubmitURL(serverConfiguration); if (StringUtils.isNotBlank(submitURL)) { this.logger.debug("Pushing updated document to [{}]", submitURL); method = new PostMethod(submitURL); method.setRequestEntity(new StringRequestEntity(doc, "application/xml", XWiki.DEFAULT_ENCODING)); this.client.executeMethod(method); } } catch (Exception ex) { this.logger.warn("Failed to notify remote server of patient update: {}", ex.getMessage(), ex); } finally { if (method != null) { method.releaseConnection(); } } } /** * Notify a remote PhenoTips instance of a deleted document. * * @param doc the name of the deleted document * @param serverConfiguration the XObject holding the remote server configuration */ private void deleteData(String doc, BaseObject serverConfiguration) { // FIXME This should be asynchronous; reimplement! PostMethod method = null; try { String deleteURL = getDeleteURL(serverConfiguration); if (StringUtils.isNotBlank(deleteURL)) { this.logger.debug("Pushing deleted document to [{}]", deleteURL); method = new PostMethod(deleteURL); method.addParameter("document", doc); this.client.executeMethod(method); } } catch (Exception ex) { this.logger.warn("Failed to notify remote server of patient removal: {}", ex.getMessage(), ex); } finally { if (method != null) { method.releaseConnection(); } } } /** * Return the URL of the specified remote PhenoTips instance, where the updated document should be sent. * * @param serverConfiguration the XObject holding the remote server configuration * @return the configured URL, in the format {@code http://remote.host.name/bin/receive/data/}, or {@code null} if * the configuration isn't valid */ private String getSubmitURL(BaseObject serverConfiguration) { String result = getBaseURL(serverConfiguration); if (StringUtils.isNotBlank(result)) { return result + "?action=save&token=" + getToken(serverConfiguration); } return null; } /** * Return the URL of the specified remote PhenoTips instance, where the notifications about deleted documents should * be sent. * * @param serverConfiguration the XObject holding the remote server configuration * @return the configured URL, in the format {@code http://remote.host.name/bin/deleted/data/}, or {@code null} if * the configuration isn't valid */ private String getDeleteURL(BaseObject serverConfiguration) { String result = getBaseURL(serverConfiguration); if (StringUtils.isNotBlank(result)) { return result + "?action=delete&token=" + getToken(serverConfiguration); } return null; } /** * Return the base URL of the specified remote PhenoTips instance. * * @param serverConfiguration the XObject holding the remote server configuration * @return the configured URL, in the format {@code http://remote.host.name/bin/}, or {@code null} if the * configuration isn't valid */ private String getBaseURL(BaseObject serverConfiguration) { if (serverConfiguration != null) { String result = serverConfiguration.getStringValue("url"); if (StringUtils.isBlank(result)) { return null; } if (!result.startsWith("http")) { result = "http://" + result; } return StringUtils.stripEnd(result, "/") + "/bin/data/sync"; } return null; } private String getToken(BaseObject serverConfiguration) { if (serverConfiguration != null) { return serverConfiguration.getStringValue("token"); } return null; } /** * Helper method for obtaining a valid xcontext from the execution context. * * @return the current request context */ private XWikiContext getXContext() { return (XWikiContext) this.execution.getContext().getProperty(XWikiContext.EXECUTIONCONTEXT_KEY); } }