View Javadoc

1   /*
2    * Copyright James Leigh (c) 2007.
3    *
4    * Licensed under the Aduna BSD-style license.
5    */
6   package org.openrdf.repository.memento;
7   
8   import java.util.ArrayList;
9   import java.util.List;
10  
11  import org.openrdf.model.Resource;
12  import org.openrdf.model.URI;
13  import org.openrdf.model.Value;
14  import org.openrdf.repository.DelegatingRepositoryConnection;
15  import org.openrdf.repository.RepositoryConnection;
16  import org.openrdf.repository.RepositoryException;
17  import org.openrdf.repository.event.NotifyingRepository;
18  import org.openrdf.repository.event.NotifyingRepositoryConnection;
19  import org.openrdf.repository.event.RepositoryConnectionListener;
20  import org.openrdf.repository.event.base.NotifyingRepositoryConnectionWrapper;
21  
22  /**
23   * Tracks changes to a RepositoryConnection and allows those changes to be
24   * reversed. There is a performance hit when this object is attached to a
25   * RepositoryConnection. This object depends on {@link NotifyingRepository} to
26   * be on the stack. If the repository does not support or have enabled read
27   * uncommitted, the {@link StatementRealizerRepository} must be a delegate of
28   * {@link NotifyingRepository}.
29   * 
30   * @author James Leigh
31   * 
32   */
33  public class RepositoryMemento implements RepositoryConnectionListener {
34  
35  	private NotifyingRepositoryConnection conn;
36  
37  	private List<Command> committed;
38  
39  	private List<Command> active;
40  
41  	private RepositoryException exc;
42  
43  	public RepositoryMemento(RepositoryConnection conn)
44  			throws RepositoryException {
45  		this(findNotifier(conn));
46  	}
47  
48  	public RepositoryMemento(NotifyingRepositoryConnection conn) {
49  		this.conn = conn;
50  		this.active = new ArrayList<Command>();
51  		this.conn.addRepositoryConnectionListener(this);
52  		if (conn instanceof NotifyingRepositoryConnectionWrapper) {
53  			NotifyingRepositoryConnectionWrapper w;
54  			w = (NotifyingRepositoryConnectionWrapper) conn;
55  			w.setReportDeltas(true);
56  		}
57  	}
58  
59  	public void close(RepositoryConnection conn) {
60  		stopListening();
61  	}
62  
63  	public void setAutoCommit(RepositoryConnection conn, boolean autoCommit) {
64  		synchronized (active) {
65  			if (committed == null) {
66  				committed = new ArrayList<Command>(active);
67  			} else {
68  				committed.addAll(active);
69  			}
70  			active.clear();
71  		}
72  	}
73  
74  	public void commit(RepositoryConnection conn) {
75  		setAutoCommit(conn, true);
76  	}
77  
78  	public void rollback(RepositoryConnection conn) {
79  		synchronized (active) {
80  			active.clear();
81  		}
82  	}
83  
84  	public void add(RepositoryConnection conn, Resource subj, URI pred,
85  			Value obj, Resource... contexts) {
86  		addCommand(new AddCommand(subj, pred, obj, contexts));
87  	}
88  
89  	public void remove(RepositoryConnection conn, Resource subj, URI pred,
90  			Value obj, Resource... contexts) {
91  		if (subj == null || pred == null || obj == null || contexts == null || contexts.length == 0)
92  			throw new UnsupportedOperationException("RepositoryMemento is only supported when reporting deltas is enabled");
93  		addCommand(new RemoveCommand(subj, pred, obj, contexts));
94  	}
95  
96  	public void clear(RepositoryConnection conn, Resource... contexts) {
97  		throw new UnsupportedOperationException("RepositoryMemento is only supported when reporting deltas is enabled");
98  	}
99  
100 	public void setNamespace(RepositoryConnection conn, String prefix,
101 			String name) {
102 		try {
103 			String namespace = conn.getNamespace(prefix);
104 			Command command = new OverrideNamespaceCommand(prefix, namespace,
105 					name);
106 			addCommand(command);
107 		} catch (RepositoryException e) {
108 			handleException(e);
109 		}
110 	}
111 
112 	public void removeNamespace(RepositoryConnection conn, String prefix) {
113 		try {
114 			String namespace = conn.getNamespace(prefix);
115 			Command command = new OverrideNamespaceCommand(prefix, namespace,
116 					null);
117 			addCommand(command);
118 		} catch (RepositoryException e) {
119 			handleException(e);
120 		}
121 	}
122 
123 	public void clearNamespaces(RepositoryConnection conn) {
124 		throw new UnsupportedOperationException("RepositoryMemento is only supported when reporting deltas is enabled");
125 	}
126 
127 	/**
128 	 * After this object has recorded changes to a connection, it can be
129 	 * reverted with this method. This RepositoryConnection does not have to be
130 	 * the same connection that this object was attached to.
131 	 * 
132 	 * @param conn
133 	 * @throws RepositoryException
134 	 */
135 	public void undo(RepositoryConnection conn) throws RepositoryException {
136 		if (exc != null)
137 			throw exc;
138 		stopListening();
139 		synchronized (active) {
140 			boolean autoCommit = conn.isAutoCommit();
141 			conn.setAutoCommit(false);
142 			undo(active, conn);
143 			if (committed != null)
144 				undo(committed, conn);
145 			conn.setAutoCommit(autoCommit);
146 		}
147 	}
148 
149 	/**
150 	 * After this object has recorded changes to a connection, it can be
151 	 * repeated with this method. This RepositoryConnection does not have to be
152 	 * the same connection that this object was attached to.
153 	 * 
154 	 * @param conn
155 	 * @throws RepositoryException
156 	 */
157 	public void redo(RepositoryConnection conn) throws RepositoryException {
158 		if (exc != null)
159 			throw exc;
160 		stopListening();
161 		synchronized (active) {
162 			boolean autoCommit = conn.isAutoCommit();
163 			conn.setAutoCommit(false);
164 			if (committed != null)
165 				redo(committed, conn);
166 			redo(active, conn);
167 			conn.setAutoCommit(autoCommit);
168 		}
169 	}
170 
171 	private void undo(List<Command> commands, RepositoryConnection conn)
172 			throws RepositoryException {
173 		for (int i = commands.size() - 1; i >= 0; i--) {
174 			Command command = commands.get(i);
175 			command.undo(conn);
176 		}
177 	}
178 
179 	private void redo(List<Command> commands, RepositoryConnection conn)
180 			throws RepositoryException {
181 		for (Command command : commands) {
182 			command.redo(conn);
183 		}
184 	}
185 
186 	private void addCommand(Command command) {
187 		synchronized (active) {
188 			active.add(command);
189 		}
190 	}
191 
192 	private void handleException(Exception e) {
193 		if (e instanceof RepositoryException) {
194 			exc = (RepositoryException) e;
195 		} else if (e.getCause() instanceof RepositoryException) {
196 			exc = (RepositoryException) e.getCause();
197 		} else {
198 			throw new AssertionError(e);
199 		}
200 		stopListening();
201 	}
202 
203 	private static NotifyingRepositoryConnection findNotifier(
204 			RepositoryConnection conn) throws RepositoryException {
205 		if (conn instanceof NotifyingRepositoryConnectionWrapper) {
206 			return (NotifyingRepositoryConnection) conn;
207 		}
208 		if (conn instanceof DelegatingRepositoryConnection) {
209 			DelegatingRepositoryConnection dconn = (DelegatingRepositoryConnection) conn;
210 			return findNotifier(dconn.getDelegate());
211 		}
212 		throw new IllegalArgumentException(
213 				"Repository stack does not contain notification support");
214 	}
215 
216 	private void stopListening() {
217 		conn.removeRepositoryConnectionListener(this);
218 		conn = null;
219 	}
220 }