/* * Copyright (C) 2003-2010 eXo Platform SAS. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License * as published by the Free Software Foundation; either version 3 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see<http://www.gnu.org/licenses/>. */ package org.exoplatform.services.jcr.impl.core.query; import org.exoplatform.commons.utils.SecurityHelper; import org.exoplatform.services.jcr.config.RepositoryConfigurationException; import org.exoplatform.services.jcr.impl.core.query.lucene.OfflinePersistentIndex; import org.exoplatform.services.jcr.impl.util.io.DirectoryHelper; import org.exoplatform.services.log.ExoLogger; import org.exoplatform.services.log.Log; import org.exoplatform.services.rpc.RPCException; import org.exoplatform.services.rpc.RPCService; import org.exoplatform.services.rpc.RemoteCommand; import org.exoplatform.services.rpc.TopologyChangeEvent; import org.exoplatform.services.rpc.TopologyChangeListener; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.RandomAccessFile; import java.io.Serializable; import java.security.PrivilegedExceptionAction; import java.util.ArrayList; import java.util.List; import javax.jcr.RepositoryException; /** * Created by The eXo Platform SAS. * * Date: 16.02.2011 * * @author <a href="mailto:anatoliy.bazko@exoplatform.com.ua">Anatoliy Bazko</a> * @version $Id: IndexRetrievalImpl.java 34360 2010-11-11 11:11:11Z tolusha $ */ public class IndexRecoveryImpl implements IndexRecovery, TopologyChangeListener { /** * Logger instance for this class. */ private static final Log log = ExoLogger.getLogger("exo.jcr.component.core.IndexRecoveryImpl"); /** * Buffer size. */ public static final int BUFFER_SIZE = 1024 * 1024; /** * The service for executing commands on all nodes of cluster. */ protected final RPCService rpcService; /** * Remote command responsible for getting the list of relative paths of all files from index directory. */ private RemoteCommand getIndexList; /** * Remote command responsible for getting data of target file. */ private RemoteCommand getIndexFile; /** * Remote command to switch index between RO/RW state. */ private RemoteCommand changeIndexMode; /** * Remote command to check if index can be retrieved. */ private RemoteCommand checkIndexReady; /** * Remote command to check if node responsible for set index online leave the cluster. */ private RemoteCommand requestForResponsibleToSetIndexOnline; /** * Indicates that node keep responsible to set index online. */ protected Boolean isResponsibleToSetIndexOnline = false; /** * Indicates whether current node is in online or offline mode */ protected Boolean isOnline = true; protected final SearchManager searchManager; /** * Constructor IndexRetrieveImpl. * * @throws RepositoryConfigurationException */ public IndexRecoveryImpl(RPCService rpcService, final SearchManager searchManager) throws RepositoryConfigurationException { this.rpcService = rpcService; this.searchManager = searchManager; final String commandSuffix = searchManager.getWsId() + "-" + (searchManager.parentSearchManager == null); final File indexDirectory = searchManager.getIndexDirectory(); changeIndexMode = rpcService.registerCommand(new RemoteCommand() { public String getId() { return "org.exoplatform.services.jcr.impl.core.query.IndexRecoveryImpl-changeIndexMode-" + commandSuffix; } public Serializable execute(Serializable[] args) throws Throwable { boolean isOnline = (Boolean)args[0]; searchManager.setOnline(isOnline, false, false); IndexRecoveryImpl.this.isOnline = isOnline; return null; } }); getIndexList = rpcService.registerCommand(new RemoteCommand() { public String getId() { return "org.exoplatform.services.jcr.impl.core.query.IndexRecoveryImpl-getIndexList-" + commandSuffix; } public Serializable execute(Serializable[] args) throws Throwable { return SecurityHelper.doPrivilegedIOExceptionAction(new PrivilegedExceptionAction<ArrayList<String>>() { public ArrayList<String> run() throws IOException { int indexDirLen = indexDirectory.getAbsolutePath().length(); ArrayList<String> result = new ArrayList<String>(); for (File file : DirectoryHelper.listFiles(indexDirectory)) { if (!file.isDirectory()) { // if parent directory is not "offline" then add this file. Otherwise skip it. if (!file.getParent().endsWith(OfflinePersistentIndex.NAME)) { result.add(file.getAbsolutePath().substring(indexDirLen)); } } } return result; } }); } }); getIndexFile = rpcService.registerCommand(new RemoteCommand() { public String getId() { return "org.exoplatform.services.jcr.impl.core.query.IndexRecoveryImpl-getIndexFile-" + commandSuffix; } public Serializable execute(Serializable[] args) throws Throwable { String filePath = (String)args[0]; long offset = (Long)args[1]; RandomAccessFile file = new RandomAccessFile(new File(indexDirectory, filePath), "r"); try { file.seek(offset); byte[] buffer = new byte[BUFFER_SIZE]; int len = file.read(buffer); if (len == -1) { return null; } else { byte[] data = new byte[len]; System.arraycopy(buffer, 0, data, 0, len); return data; } } finally { try { file.close(); } catch (IOException e) { log.debug("Could not close the file", e); } } } }); requestForResponsibleToSetIndexOnline = rpcService.registerCommand(new RemoteCommand() { public String getId() { return "org.exoplatform.services.jcr.impl.core.query.IndexRecoveryImpl-requestForResponsibleToSetIndexOnline-" + commandSuffix; } public Serializable execute(Serializable[] args) throws Throwable { return isResponsibleToSetIndexOnline; } }); checkIndexReady = rpcService.registerCommand(new RemoteCommand() { public String getId() { return "org.exoplatform.services.jcr.impl.core.query.IndexRecoveryImpl-checkIndexIsReady-" + commandSuffix; } public Serializable execute(Serializable[] args) throws Throwable { // if index is currently online, then it can be retrieved return new Boolean(searchManager.isOnline()); } }); rpcService.registerTopologyChangeListener(this); } /** * {@inheritDoc} */ @SuppressWarnings("unchecked") public List<String> getIndexList() throws RepositoryException { try { return (List<String>)rpcService.executeCommandOnCoordinator(getIndexList, true); } catch (SecurityException e) { throw new RepositoryException(e); } catch (RPCException e) { throw new RepositoryException(e); } } /** * {@inheritDoc} */ public void setIndexOffline() throws RepositoryException { try { isResponsibleToSetIndexOnline = true; rpcService.executeCommandOnCoordinator(changeIndexMode, true, false); } catch (SecurityException e) { throw new RepositoryException(e); } catch (RPCException e) { throw new RepositoryException(e); } } /** * {@inheritDoc} */ public void setIndexOnline() throws RepositoryException { try { rpcService.executeCommandOnCoordinator(changeIndexMode, true, true); isResponsibleToSetIndexOnline = false; } catch (SecurityException e) { throw new RepositoryException(e); } catch (RPCException e) { throw new RepositoryException(e); } } /** * {@inheritDoc} */ public InputStream getIndexFile(String filePath) throws RepositoryException { try { return new RemoteInputStream(filePath); } catch (SecurityException e) { throw new RepositoryException(e); } catch (RPCException e) { throw new RepositoryException(e); } } /** * @see org.exoplatform.services.jcr.impl.core.query.IndexRecovery#checkIndexReady() */ public boolean checkIndexReady() throws RepositoryException { try { return (Boolean)rpcService.executeCommandOnCoordinator(checkIndexReady, true); } catch (SecurityException e) { throw new RepositoryException(e); } catch (RPCException e) { throw new RepositoryException(e); } } /** * Allows to read data from remote machine. */ class RemoteInputStream extends InputStream { private final String filePath; private long fileOffset = 0; private int bufferOffset = 0; private byte[] buffer; RemoteInputStream(String filePath) throws SecurityException, RPCException { this.filePath = filePath; readNext(); } /** * {@inheritDoc} */ @Override public int read() throws IOException { throw new UnsupportedOperationException("RemoteStream.read() method is not supported"); } /** * {@inheritDoc} */ @Override public int available() throws IOException { return buffer == null ? 0 : buffer.length - bufferOffset; } /** * {@inheritDoc} */ @Override public int read(byte b[]) throws IOException { if (buffer == null) { return -1; } else if (available() == 0) { try { readNext(); if (buffer == null) { return -1; } } catch (SecurityException e) { throw new IOException(e); } catch (RPCException e) { throw new IOException(e); } } int len = Math.min(b.length, available()); System.arraycopy(buffer, bufferOffset, b, 0, len); bufferOffset += len; return len; } /** * {@inheritDoc} */ @Override public int read(byte b[], int off, int len) throws IOException { throw new UnsupportedOperationException( "RemoteStream.read(byte b[], int off, int len) method is not supported"); } private void readNext() throws SecurityException, RPCException { this.buffer = (byte[])rpcService.executeCommandOnCoordinator(getIndexFile, true, filePath, fileOffset); if (buffer != null) { this.fileOffset += this.buffer.length; this.bufferOffset = 0; } } } /** * {@inheritDoc} */ public void onChange(TopologyChangeEvent event) { try { if (rpcService.isCoordinator() && !isOnline) { new Thread() { @Override public synchronized void run() { try { List<Object> results = rpcService.executeCommandOnAllNodes(requestForResponsibleToSetIndexOnline, true); for (Object result : results) { if (result instanceof Boolean) { if ((Boolean)result) { return; } } else { log.error("Result is not an instance of Boolean" + result); } } // node which was responsible for resuming leave the cluster, so resume component log .error("Node responsible for setting index back online seems to leave the cluster. Setting back online."); searchManager.setOnline(true, false, false); } catch (SecurityException e1) { log.error("You haven't privileges to execute remote command", e1); } catch (RPCException e1) { log.error("Exception during command execution", e1); } catch (IOException e2) { log.error("Exception during setting index back online", e2); } } }.start(); } } catch (RPCException e) { log.error("Can't check if node coordinator or not."); } } /** * {@inheritDoc} */ public void close() { rpcService.unregisterCommand(changeIndexMode); rpcService.unregisterCommand(getIndexList); rpcService.unregisterCommand(getIndexFile); rpcService.unregisterCommand(requestForResponsibleToSetIndexOnline); rpcService.unregisterCommand(checkIndexReady); rpcService.unregisterTopologyChangeListener(this); } }