Project

General

Profile

1
package edu.ucsb.nceas.metacat.admin.upgrade;
2
/**
3
 *  '$RCSfile$'
4
 *    Purpose: A Class for upgrading the database to version 1.5
5
 *  Copyright: 2000 Regents of the University of California and the
6
 *             National Center for Ecological Analysis and Synthesis
7
 *    Authors: Saurabh Garg
8
 *
9
 *   '$Author: leinfelder $'
10
 *     '$Date: 2013-04-24 19:34:36 -0700 (Wed, 24 Apr 2013) $'
11
 * '$Revision: 7622 $'
12
 *
13
 * This program is free software; you can redistribute it and/or modify
14
 * it under the terms of the GNU General Public License as published by
15
 * the Free Software Foundation; either version 2 of the License, or
16
 * (at your option) any later version.
17
 *
18
 * This program is distributed in the hope that it will be useful,
19
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21
 * GNU General Public License for more details.
22
 *
23
 * You should have received a copy of the GNU General Public License
24
 * along with this program; if not, write to the Free Software
25
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
26
 */
27

    
28

    
29
import java.sql.Connection;
30
import java.sql.Driver;
31
import java.sql.DriverManager;
32
import java.sql.PreparedStatement;
33
import java.sql.ResultSet;
34
import java.sql.SQLException;
35
import java.util.ArrayList;
36
import java.util.List;
37

    
38
import org.apache.commons.logging.Log;
39
import org.apache.commons.logging.LogFactory;
40
import org.apache.wicket.protocol.http.mock.MockHttpServletRequest;
41
import org.dataone.service.types.v1.Event;
42
import org.dataone.service.types.v1.Identifier;
43
import org.dataone.service.types.v1.Session;
44
import org.dataone.service.types.v1.Subject;
45

    
46
import edu.ucsb.nceas.metacat.IdentifierManager;
47
import edu.ucsb.nceas.metacat.admin.AdminException;
48
import edu.ucsb.nceas.metacat.dataone.MNodeService;
49
import edu.ucsb.nceas.metacat.dataone.hazelcast.HazelcastService;
50
import edu.ucsb.nceas.metacat.properties.PropertyService;
51
import edu.ucsb.nceas.utilities.SortedProperties;
52

    
53
/**
54
 * Used for forcibly removing invalid replicas from a target node.
55
 * These replicas exist from legacy Metacat replication that may have introduced 
56
 * whitespace differences and entity escaping in the XML representations.
57
 * Removing these replicas is considered 'safe' because the source node[s] house the 
58
 * original content that should be re-replicated (by DataONE).
59
 * @see https://redmine.dataone.org/issues/3539
60
 * @author leinfelder
61
 *
62
 */
63
public class RemoveInvalidReplicas implements UpgradeUtilityInterface {
64

    
65
	protected static Log log = LogFactory.getLog(RemoveInvalidReplicas.class);
66
	
67
    private String driver = null;
68
    private String url = null;
69
    private String user = null;
70
    private String password = null;
71
    
72
    private boolean dryRun = false;
73

    
74
	private int serverLocation = 0;
75
	
76
    public int getServerLocation() {
77
		return serverLocation;
78
	}
79

    
80
	public void setServerLocation(int serverLocation) {
81
		this.serverLocation = serverLocation;
82
	}
83

    
84
    public boolean upgrade() throws AdminException {
85
        
86
    	boolean success = true;	
87
    	
88
    	// do not allow this. ever.
89
    	if (serverLocation == 1) {
90
    		throw new AdminException("This is a DESTRUCTIVE action. Cannot remove original objects from home server: " + serverLocation);
91
    	}
92
    	
93
        Connection sqlca = null;
94
        PreparedStatement pstmt = null;
95
        
96
        try {
97
        	
98
			log.debug("dryRun: " + dryRun);
99

    
100
        	// get the properties
101
    		driver = PropertyService.getProperty("database.driver");
102
    	    url = PropertyService.getProperty("database.connectionURI");
103
    	    user = PropertyService.getProperty("database.user");
104
    	    password = PropertyService.getProperty("database.password");
105
    	    
106
	        // Create a JDBC connection to the database    
107
	        Driver d = (Driver) Class.forName(driver).newInstance();
108
	        DriverManager.registerDriver(d);
109
	        sqlca = DriverManager.getConnection(url, user, password);
110
	        sqlca.setAutoCommit(true);       
111
	        
112
	        // find the replicas that failed to synch
113
			List<String> invalidReplicas = new ArrayList<String>();
114
			pstmt = sqlca.prepareStatement(
115
					"SELECT distinct guid " +
116
					"FROM xml_documents xml, identifier id, access_log log " +
117
					"WHERE id.docid = xml.docid " +
118
					"AND id.rev = xml.rev " +
119
					"AND log.docid = id.docid || '.' || id.rev " +
120
					"AND xml.server_location = ? " +
121
					"AND log.event = ? " +
122
					"UNION " +
123
					"SELECT distinct guid " +
124
					"FROM xml_revisions xml, identifier id, access_log log " +
125
					"WHERE id.docid = xml.docid " +
126
					"AND id.rev = xml.rev " +
127
					"AND log.docid = id.docid || '.' || id.rev " +
128
					"AND xml.server_location = ? " +
129
					"AND log.event = ? ");
130
			pstmt.setInt(1, serverLocation);
131
			pstmt.setString(2, Event.SYNCHRONIZATION_FAILED.xmlValue());
132
			pstmt.setInt(3, serverLocation);
133
			pstmt.setString(4, Event.SYNCHRONIZATION_FAILED.xmlValue());
134
			log.debug("Finding invalid (failed replicas with SQL: " + pstmt.toString());
135
			ResultSet rs = pstmt.executeQuery();
136
			while (rs.next()) {
137
				invalidReplicas.add(rs.getString(1));
138
			}
139

    
140
			log.debug("invalidReplicas count: " + invalidReplicas.size());
141

    
142
			// prepare statement for removing from identifier table
143
			pstmt = sqlca.prepareStatement("DELETE FROM identifier WHERE guid = ? ");
144

    
145
			// for using the MN API as the MN itself
146
			MockHttpServletRequest request = new MockHttpServletRequest(null, null, null);
147
			Session session = new Session();
148
	        Subject subject = MNodeService.getInstance(request).getCapabilities().getSubject(0);
149
	        session.setSubject(subject);
150
	        
151
			// remove them from the system in two steps
152
			for (String identifier: invalidReplicas) {
153
				
154
				log.debug("Removing invalid replica: " + identifier);
155
						
156
				// using the MN.delete() method first
157
				Identifier pid = new Identifier();
158
				pid.setValue(identifier);
159
				try {
160
					if (!dryRun) {
161
						MNodeService.getInstance(request).delete(session, pid);
162
						log.debug("Deleted invalid replica object: " + pid.getValue());
163

    
164
						// remove SM from database
165
						IdentifierManager.getInstance().deleteSystemMetadata(identifier);
166
						log.debug("Deleted invalid replica SystemMetadata for: " + pid.getValue());
167

    
168
						// remove the identifier from the database
169
						if (IdentifierManager.getInstance().mappingExists(identifier)) {
170
							String localId = IdentifierManager.getInstance().getLocalId(identifier);
171
							IdentifierManager.getInstance().removeMapping(identifier, localId);
172
							log.debug("Removed localId mapping: " + localId);
173
						} else {
174
							// remove from the identifier table manually
175
							pstmt.setString(1, identifier);									
176
							int count = pstmt.executeUpdate();
177
							log.debug("Removed identifier entry with SQL: " + pstmt.toString());
178

    
179
							// warn if we saw something unexpected
180
							if (count <= 0) {
181
								log.warn("Delete returned unexpected count for pid: " + identifier);
182
							}
183
						}
184
						
185
						// purge from Hz map
186
						HazelcastService.getInstance().getSystemMetadataMap().evict(pid);
187
						log.debug("Evicted identifier from HZ map: " + pid.getValue());
188

    
189
					}
190
				} catch (Exception e) {
191
					log.error("Could not delete invalid replica: " + identifier, e);
192
					continue;
193
				}
194
			}
195
			
196
        } catch (Exception e) {
197
        	// TODO Auto-generated catch block
198
			e.printStackTrace();
199
        	success = false;
200
		} finally {
201
			// clean up
202
			if (sqlca != null) {
203
				try {
204
					sqlca.close();
205
				} catch (SQLException e) {
206
					// TODO Auto-generated catch block
207
					e.printStackTrace();
208
				}
209
			}
210
		}
211
            	
212
		log.debug("Done removing failed/invalid replicas");
213

    
214
    	return success;
215
    }
216

    
217
    public static void main(String [] args){
218

    
219
        try {
220
        	// set up the properties based on the test/deployed configuration of the workspace
221
        	SortedProperties testProperties = 
222
				new SortedProperties("test/test.properties");
223
			testProperties.load();
224
			String metacatContextDir = testProperties.getProperty("metacat.contextDir");
225
			PropertyService.getInstance(metacatContextDir + "/WEB-INF");
226
			
227
			// now run it
228
            RemoveInvalidReplicas upgrader = new RemoveInvalidReplicas();
229
            if (args.length > 0) {
230
            	String serverLocation = args[0];
231
            	upgrader.setServerLocation(Integer.parseInt(serverLocation));
232
            }
233
            //upgrader.dryRun = true;
234
	        upgrader.upgrade();
235
            
236
        } catch (Exception ex) {
237
            System.out.println("Exception:" + ex.getMessage());
238
            ex.printStackTrace();
239
        }
240
    }
241
}
(2-2/14)