diff src/nabble/model/export/Export.java @ 0:7ecd1a4ef557

add content
author Franklin Schmidt <fschmidt@gmail.com>
date Thu, 21 Mar 2019 19:15:52 -0600
parents
children 72765b66e2c3
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/export/Export.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,279 @@
+package nabble.model.export;
+
+import fschmidt.db.DbDatabase;
+import fschmidt.util.java.IoUtils;
+import fschmidt.util.mail.Mail;
+import fschmidt.util.mail.MailAddress;
+import fschmidt.util.mail.MailHome;
+import fschmidt.util.mail.PlainTextContent;
+import nabble.model.MailingList;
+import nabble.model.ModelException;
+import nabble.model.ModelHome;
+import nabble.model.Executors;
+import nabble.model.Node;
+import nabble.model.NodeIterator;
+import nabble.model.Person;
+import nabble.model.User;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.rmi.Naming;
+import java.rmi.NotBoundException;
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+public class Export implements Runnable {
+
+	private static final Logger logger = LoggerFactory.getLogger(Export.class);
+
+	protected static class ExportMessages {
+		public String getSuccessMessage(Node node) { return "Export finished successfully."; }
+		public String getErrorMessage(Node node, Exception e) { return "Export error: \n" + getStackTrace(e); }
+		public String getPermalinkNotFoundMessage(String permalink) { return "Export couldn't start because the link you provided is not a valid Nabble application: \n" + permalink; }
+		public String getEmailSubject(Node node) { return "Export of node " + node.getId() + " | " + node.getSubject(); }
+	}
+
+	protected static final class ShutdownException extends RuntimeException {}
+
+	private static final Pattern URL_BASE = Pattern.compile( "^\\w+://[^/]+/");
+	private static final Pattern SERVER_FORMAT = Pattern.compile( "(localhost|\\d+\\.\\d+\\.\\d+\\.\\d+):\\d+");
+	private final String email;
+	private final Node rootNode;
+	private final Import imp;
+	private final ExportMessages messages;
+	private final String permalink;
+	private boolean handledMailingList = false;
+	private Set<Long> movedUserIds = new TreeSet<Long>();
+
+	protected Export(Node rootNode,String permalink,String email, ImportServer is, ExportMessages messages)
+		throws IOException
+	{
+		logger.info("Exporting node ID = " + rootNode.getId());
+		setExporting(rootNode.getSite().getId(), true);
+		this.rootNode = rootNode;
+		imp = is.newImport(permalink,rootNode.getId());
+		this.email = email;
+		this.messages = messages;
+		this.permalink = permalink;
+	}
+
+	public Export(Node rootNode,String permalink,String email)
+		throws IOException
+	{
+		this(rootNode,permalink,email,getServer(permalink),new ExportMessages());
+	}
+
+	// Global Information
+	public static Set<Long> exportSiteIds = new HashSet<Long>();
+
+	private static synchronized void setExporting(long siteId, boolean start) {
+		if (start) {
+			exportSiteIds.add(siteId);
+		} else
+			exportSiteIds.remove(siteId);
+	}
+
+	private static String getServerAddress(String permalink) throws IOException {
+		Matcher m = URL_BASE.matcher(permalink);
+		if( !m.find() )
+			throw new IllegalArgumentException();
+		String base = m.group();
+		return IoUtils.readPage(base+"util/Rmi.jtp");
+	}
+
+	private static Remote lookup(String rmiServer,String name)
+		throws RemoteException
+	{
+		try {
+			return Naming.lookup("//"+rmiServer+"/"+name);
+		} catch(MalformedURLException e) {
+			throw new RuntimeException(e);
+		} catch(NotBoundException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	private static ImportServer getServer(String permalink) throws IOException {
+		return (ImportServer) lookup(getServerAddress(permalink),"import");
+	}
+
+	public static boolean isValidExportServer(String permalink) {
+		try {
+			String server = getServerAddress(permalink);
+			return SERVER_FORMAT.matcher(server).matches();
+		} catch (IOException e) {
+			return false;
+		} catch (IllegalArgumentException e) {
+			return false;
+		}
+	}
+
+	public void run() {
+		logger.info("Started export thread for ID = " + rootNode.getId() + " / " + email);
+		PlainTextContent content = null;
+		ModelHome.beginImport();
+		long siteId = this.rootNode.getSite().getId();
+		boolean closed = false;
+		try {
+			Node rootNode = this.rootNode.getGoodCopy();
+			long parentId = imp.getNodeId(permalink);
+			export(rootNode.getGoodCopy(),parentId,null);
+			if( handledMailingList ) {
+				handledMailingList = false;
+				export(rootNode.getGoodCopy(),parentId,null);
+				if( handledMailingList )
+					throw new RuntimeException();
+			}
+			// The last step is to delete the old nodes, so there is no reason
+			// to keep this connection open. Let's close it.
+			imp.close();
+			closed = true;
+
+			// Delete the old nodes now
+			rootNode.deleteRecursively();
+			content = new PlainTextContent(messages.getSuccessMessage(rootNode));
+		} catch(ShutdownException e) {
+			throw e;
+		} catch(RuntimeException e) {
+			logger.error(rootNode.toString(),e);
+			content = new PlainTextContent(messages.getErrorMessage(rootNode, e));
+		} catch(RemoteException e) {
+			logger.error(rootNode.toString(),e);
+			content = new PlainTextContent(messages.getErrorMessage(rootNode, e));
+		} catch(Import.BadLink e) {
+			content = new PlainTextContent(messages.getPermalinkNotFoundMessage(permalink));
+		} finally {
+			ModelHome.endImport();
+			setExporting(siteId, false);
+			if (!closed) {
+				try {
+					imp.close();
+				} catch(Exception e) {
+					logger.error("imp.close",e);
+					if (content == null)
+						content = new PlainTextContent(messages.getErrorMessage(rootNode, e));
+				}
+			}
+		}
+		Mail mail = MailHome.newMail();
+		MailAddress to = new MailAddress(email);
+		mail.setFrom(new MailAddress(ModelHome.noReply, "Nabble"));
+		mail.setTo(to);
+		mail.setContent(content);
+		mail.setSubject(messages.getEmailSubject(rootNode));
+		ModelHome.send(mail);
+	}
+
+	private void export(Node node,long parentId,int[] pin)
+		throws RemoteException
+	{
+		if( Executors.isShuttingDown() )
+			throw new ShutdownException();
+		long id = node.getExportedNodeId();
+		try {
+			final MailingList ml = node.getMailingList();
+			Integer pinOrder = pin == null || !node.isPinned()? null : ++pin[0];
+			if( id==0L ) {
+				NodeData data = node.getData();
+				data.parentId = parentId;
+				data.pin = pinOrder;
+				String redirectUrl = imp.importNode(data);
+				try {
+					id = imp.getNodeId(redirectUrl);
+				} catch(Import.BadLink e) {
+					logger.error(""+node+" url = "+redirectUrl,e);
+					throw new RuntimeException("redirect url not found: "+redirectUrl);
+				}
+				setExportedNodeId(node, id);
+				if (ml != null) {
+					imp.setExportOwner(id,email);
+					imp.subscribe(id);
+					ml.setExportOwner(email);
+					ml.update();
+					ml.unsubscribe();
+					handledMailingList = true;
+				}
+			} else {
+				if (ml!=null) {
+					imp.setExportOwner(id,null);
+					ml.setExportOwner(null);
+					ml.update();
+				}
+			}
+
+			if (ml != null)
+				imp.setMailingList(id);
+
+			// Move author
+			Person author = node.getOwner();
+			if (author instanceof User) {
+				User user = (User) author;
+				if (user.isRegistered() && !movedUserIds.contains(user.getId())) {
+					String smallAvatarUrl = null;
+					String bigAvatarUrl = null;
+					if (user.hasAvatar()) {
+						String base = node.getSite().getBaseUrl() + "/file/a" + user.getId() + '/';
+						smallAvatarUrl = base + ModelHome.AVATAR_SMALL;
+						bigAvatarUrl = base + ModelHome.AVATAR_BIG;
+					}
+					imp.addUser(user.getName(), user.getEmail(), user.getPasswordDigest(), user.getRegistered(), smallAvatarUrl, bigAvatarUrl);
+					movedUserIds.add(user.getId());
+				}
+			}
+
+			pin = new int[1];
+
+			// Here we use an iterator because something may go wrong with the
+			// migration (e.g., the other server may go down or the network may fail)
+			// So we should make sure all DB connections are properly closed.
+			NodeIterator<? extends Node> iterator = node.getChildren();
+			try {
+				while (iterator.hasNext()) {
+					export(iterator.next(), id, pin );
+				}
+			} finally {
+				iterator.close();
+			}
+			if (ml != null)
+				imp.setMailingList(null);
+		} catch(ModelException e) {
+			logger.error(""+node,e);
+			throw new RuntimeException(e.toString());
+		}
+	}
+
+	protected void setExportedNodeId(Node node, long id) {
+		final DbDatabase db = rootNode.getSite().getDb();
+		db.beginTransaction();
+		try {
+			node.getGoodCopy().setExportedNodeId(id);
+			db.commitTransaction();
+		} finally {
+			db.endTransaction();
+		}
+	}
+
+	// Utilities --------------------------------------------------------------
+
+	protected static String getStackTrace(Throwable e) {
+		StackTraceElement[] arr = e.getStackTrace();
+		StringBuilder builder = new StringBuilder(e.toString());
+		builder.append('\n');
+		for (StackTraceElement elem : arr) {
+			builder.append('\t').append(elem).append('\n');
+		}
+		if (e.getCause() != null) {
+			builder.append("Caused by:\n");
+			builder.append(getStackTrace(e.getCause()));
+		}
+		return builder.toString();
+	}
+}