view src/nabble/model/export/Export.java @ 47:72765b66e2c3

remove mailing list code
author Franklin Schmidt <fschmidt@gmail.com>
date Fri, 18 Jun 2021 17:44:24 -0600
parents 7ecd1a4ef557
children
line wrap: on
line source

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.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 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);
			// 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();
		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);
		}

		// 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();
		}
	}

	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();
	}
}