All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.wildfly.transaction.client.provider.jboss.FileSystemXAResourceRegistry Maven / Gradle / Ivy

/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2018 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.wildfly.transaction.client.provider.jboss;

import java.io.File;
import java.io.FilePermission;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import jakarta.transaction.SystemException;
import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;

import org.wildfly.common.annotation.NotNull;
import org.wildfly.transaction.client.LocalTransaction;
import org.wildfly.transaction.client.SimpleXid;
import org.wildfly.transaction.client.XAResourceRegistry;
import org.wildfly.transaction.client.XAResourceRegistryProvider;
import org.wildfly.transaction.client._private.Log;
import org.wildfly.transaction.client.spi.LocalTransactionProvider;

import static java.security.AccessController.doPrivileged;

/**
 * A registry persisted in a series of log files, containing all outflowed resources info for unfinished transactions.
 * This registry is created whenever a subordinate resource is outflowed to a remote location,
 * and deleted only when all outflowed resources participating in that transaction are successfully prepared,
 * committed or rolled back (the two latter in the case of a one-phase commit).
 *
 * Used for {@link #getInDoubtXAResources()}  recovery of in doubt resources}.
 *
 * @author Flavia Rainone
 */
final class FileSystemXAResourceRegistry {

    /**
     * Name of recovery dir. Location of this dir can be defined at constructor.
     */
    private static final String RECOVERY_DIR = "ejb-xa-recovery";

    /**
     * Empty utility array.
     */
    private static final XAResource[] EMPTY_IN_DOUBT_RESOURCES = new XAResource[0];

    /**
     * Key for keeping the xa resource registry associated with a local transaction
     */
    private static final Object XA_RESOURCE_REGISTRY_KEY = new Object();

    /**
     * The local transaction provider associated with this file system XAResource registry
     */
    private final LocalTransactionProvider provider;

    /**
     * The xa recovery path, i.e., the path containing {@link #RECOVERY_DIR}.
     */
    private final Path xaRecoveryPath;

    /**
     * Permission for writing and reading the xa recovery dir.
     */
    private final FilePermission xaRecoveryDirPermission;

    /**
     * A set containing the list of all registry files that are currently open. Used as a support to
     * identify in doubt registries. See {@link #recoverInDoubtRegistries}.
     */
    private final Set openFilePaths = Collections.synchronizedSet(new HashSet<>());

    /**
     * A set of in doubt resources, i.e., outflowed resources whose prepare/rollback/commit operation was not
     * completed normally, or resources that have been recovered from in doubt registries. See
     * {@link XAResourceRegistryFile#resourceInDoubt} and {@link XAResourceRegistryFile#loadInDoubtResources}.
     */
    private final Set inDoubtResources = Collections.synchronizedSet(new HashSet<>()); // it is a set because we could have an in doubt resource reincide in failure to complete

    /**
     * Creates a FileSystemXAResourceRegistry.
     *
     * @param relativePath the path recovery dir is relative to
     */
    FileSystemXAResourceRegistry (LocalTransactionProvider provider, Path relativePath) {
        this.provider = provider;
        if (relativePath == null) {
            this.xaRecoveryPath = FileSystems.getDefault().getPath(RECOVERY_DIR);
        } else {
            this.xaRecoveryPath = relativePath.resolve(RECOVERY_DIR);
        }
        xaRecoveryDirPermission = new FilePermission(xaRecoveryPath.toString() + File.separatorChar + '*', "read,write");
    }

    /**
     * Returns the XAResourceRegistry file for {@code transaction}.
     *
     * @param transaction the transaction
     * @return the XAResourceRegistry for {@code transaction}. If there is no such registry file, a new one is created.
     * @throws SystemException if an unexpected failure occurs when creating the registry file
     */
    XAResourceRegistry getXAResourceRegistryFile(LocalTransaction transaction) throws SystemException {
        XAResourceRegistry registry = (XAResourceRegistry) transaction.getResource(XA_RESOURCE_REGISTRY_KEY);
        if (registry != null)
            return registry;
        registry = new XAResourceRegistryFile(transaction.getXid());
        transaction.putResource(XA_RESOURCE_REGISTRY_KEY, registry);
        return registry;
    }

    /**
     * Returns a list containing all in doubt xa resources. A XAResource is considered in doubt if:
     * 
    *
  • it failed to prepare on a two-phase commit by throwing an exception
  • *
  • it failed to commit or rollback in a one-phase commit by throwing an exception
  • *
* An in doubt resource is no longer considered in doubt if it succeeded to rollback without an exception. * * Notice that in doubt xa resources are kept after the server shuts down, guaranteeing that they can eventually be * recovered, even if in a different server JVM instance than the one that outflowed the resource. This mechanism * assures proper recovery and abortion of the original in-doubt outflowed resource, that belongs to an external * remote server. * * @return a list of the in doubt xa resources */ XAResource[] getInDoubtXAResources() { try { recoverInDoubtRegistries(); } catch (IOException e) { throw Log.log.unexpectedExceptionOnXAResourceRecovery(e); } return inDoubtResources.isEmpty() ? EMPTY_IN_DOUBT_RESOURCES : inDoubtResources.toArray(new XAResource[0]); } /** * Recovers closed registries files from file system. All those registries are considered in doubt. * * @throws IOException if there is an I/O error when reading the recovered registry files */ private void recoverInDoubtRegistries() throws IOException { final File recoveryDir = xaRecoveryPath.toFile(); if (!recoveryDir.exists()) { return; } final String[] xaRecoveryFileNames = recoveryDir.list(); if (xaRecoveryFileNames == null) { Log.log.listXAResourceRecoveryFilesNull(recoveryDir); return; } for (String xaRecoveryFileName : xaRecoveryFileNames) { // check if file is not open already if (!openFilePaths.contains(xaRecoveryFileName)) new XAResourceRegistryFile(xaRecoveryFileName, provider); } } /** * Represents a single file in the file system that records all outflowed resources per a specific local transaction. */ private final class XAResourceRegistryFile extends XAResourceRegistry { /** * Path to the registry file. */ @NotNull private final Path filePath; /** * The file channel, if non-null, it indicates that this registry represents a current, on-going transaction, * if null, then this registry represents an registry file recovered from file system. */ private final FileChannel fileChannel; /** * Keeps track of the XA outflowed resources stored in this registry, see {@link #addResource} and * {@link #removeResource}. */ private final Set resources = Collections.synchronizedSet(new HashSet<>()); /** * Creates a XA recovery registry for a transaction. This method assumes that there is no file already * existing for this transaction, and, furthermore, it is not thread safe (the creation of this object is * already thread protected at the caller). * * @param xid the transaction xid * @throws SystemException if the there was a problem when creating the recovery file in file system */ XAResourceRegistryFile(Xid xid) throws SystemException { final String xidString = SimpleXid.of(xid).toHexString('_'); this.filePath = xaRecoveryPath.resolve(xidString); try { final SecurityManager sm = System.getSecurityManager(); if (sm == null) fileChannel = openRecoveryFile(); else fileChannel = doPrivileged((PrivilegedExceptionAction) () -> { sm.checkPermission(xaRecoveryDirPermission); return openRecoveryFile(); }); openFilePaths.add(xidString); fileChannel.lock(); Log.log.xaResourceRecoveryFileCreated(filePath); } catch (PrivilegedActionException e) { throw Log.log.createXAResourceRecoveryFileFailed(filePath, (IOException)e.getCause()); } catch (IOException e) { throw Log.log.createXAResourceRecoveryFileFailed(filePath, e); } } /** * Reload a registry that is in doubt, i.e., the registry is not associated yet with a current * transaction in this server, but with a transaction of a previous jvm instance that is now * being recovered. * This will happen only if the jvm crashes before a transaction with XA outflowed resources is * fully prepared. In this case, any lines in the registry can correspond to in doubt outflowed * resources. The goal is to reload those resources so they can be recovered. * * @param inDoubtFileName the file name of the in doubt registry * @throws IOException if there is an I/O error when reloading the registry file */ private XAResourceRegistryFile(String inDoubtFileName, LocalTransactionProvider provider) throws IOException { this.filePath = xaRecoveryPath.resolve(inDoubtFileName); this.fileChannel = null; // no need to open file channel here openFilePaths.add(inDoubtFileName); loadInDoubtResources(provider.getNodeName()); Log.log.xaResourceRecoveryRegistryReloaded(filePath); } /** * {@inheritDoc} */ @Override protected void addResource(XAResource resource, Xid xid, URI uri) throws SystemException { assert fileChannel != null; if (!this.resources.add(resource)) return; // trying to add a duplication try { assert fileChannel.isOpen(); String record = new StringBuilder() .append(uri.toString()).append(System.lineSeparator()) .append(SimpleXid.of(xid).toHexString()).append(System.lineSeparator()) .toString(); fileChannel.write(ByteBuffer.wrap((record).getBytes(StandardCharsets.UTF_8))); fileChannel.force(true); } catch (IOException e) { throw Log.log.appendXAResourceRecoveryFileFailed(uri, filePath, e); } Log.log.xaResourceAddedToRecoveryRegistry(uri, filePath); } /** * {@inheritDoc} * The registry file is closed and deleted if there are no more resources left. * * @throws XAException if there is a problem deleting the registry file */ @Override protected void removeResource(XAResource resource) throws XAException { if (resources.remove(resource)) { if (resources.isEmpty()) { // delete file try { if (fileChannel != null) { fileChannel.close(); } Files.delete(filePath); openFilePaths.remove(filePath.getFileName().toString()); } catch (IOException e) { throw Log.log.deleteXAResourceRecoveryFileFailed(XAException.XAER_RMERR, filePath, resource, e); } Log.log.xaResourceRecoveryFileDeleted(filePath); } // remove resource from in doubt list, in case the resource was in doubt inDoubtResources.remove(resource); } } /** * {@inheritDoc} */ @Override protected void resourceInDoubt(XAResource resource) { inDoubtResources.add(resource); } /** * Opens the {@link #filePath recovery file}. * * @return the fileChannel * @throws IOException if an IO error occurs during file creation and opening */ private FileChannel openRecoveryFile() throws IOException { xaRecoveryPath.toFile().mkdir(); // create dir if non existent return FileChannel.open(filePath, StandardOpenOption.APPEND, StandardOpenOption.CREATE_NEW); } /** * Loads in doubt resources from recovered registry file. * * @throws IOException if an I/O error occurs when reloading the resources from the file */ private void loadInDoubtResources(String nodeName) throws IOException { assert fileChannel == null; final List lines; try { lines = Files.readAllLines(filePath); } catch (IOException e) { throw Log.log.readXAResourceRecoveryFileFailed(filePath, e); } Iterator linesIterator = lines.iterator(); while(linesIterator.hasNext()) { String line = linesIterator.next(); // adding a line separator at the end of each uri entry results in an extra empty line // the end of the file means empty line and then no more record (no has next) if (line.isEmpty() && !linesIterator.hasNext()) { break; } // record consists from two lines, first line is URI final URI uri; try { uri = new URI(line); } catch (URISyntaxException e) { throw Log.log.readURIFromXAResourceRecoveryFileFailed(line, filePath, e); } // the second line is Xid Xid xid = null; if(linesIterator.hasNext()) { line = linesIterator.next(); // line separator could lead to empty line if (line.isEmpty() && linesIterator.hasNext()) line = linesIterator.next(); try { xid = SimpleXid.of(line); } catch (Exception e) { throw Log.log.readXidFromXAResourceRecoveryFileFailed(line, filePath, e); } } final XAResource xaresource = reloadInDoubtResource(uri, nodeName, xid); resources.add(xaresource); inDoubtResources.add(xaresource); Log.log.xaResourceRecoveredFromRecoveryRegistry(uri, filePath); } } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy