/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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.apache.ignite.internal; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.Writer; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; import java.nio.channels.OverlappingFileLockException; import java.nio.charset.StandardCharsets; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.locks.Lock; import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.IgniteLogger; import org.apache.ignite.internal.util.GridStripedLock; import org.apache.ignite.internal.util.typedef.internal.U; /** * File-based persistence provider for {@link MarshallerContextImpl}. * * Saves mappings in format <b>{typeId}.classname{platformId}</b>, e.g. 123.classname0. * * It writes new mapping when it is accepted by all grid members and reads mapping * when a classname is requested but is not presented in local cache of {@link MarshallerContextImpl}. */ final class MarshallerMappingFileStore { /** */ private static final GridStripedLock fileLock = new GridStripedLock(32); /** */ private final IgniteLogger log; /** */ private final File workDir; /** * @param log Logger. */ MarshallerMappingFileStore(String igniteWorkDir, IgniteLogger log) throws IgniteCheckedException { workDir = U.resolveWorkDirectory(igniteWorkDir, "marshaller", false); this.log = log; } /** * @param platformId Platform id. * @param typeId Type id. * @param typeName Type name. */ void writeMapping(byte platformId, int typeId, String typeName) { String fileName = getFileName(platformId, typeId); Lock lock = fileLock(fileName); lock.lock(); try { File file = new File(workDir, fileName); try (FileOutputStream out = new FileOutputStream(file)) { FileLock fileLock = fileLock(out.getChannel(), false); assert fileLock != null : fileName; try (Writer writer = new OutputStreamWriter(out, StandardCharsets.UTF_8)) { writer.write(typeName); writer.flush(); } } catch (IOException e) { U.error(log, "Failed to write class name to file [platformId=" + platformId + "id=" + typeId + ", clsName=" + typeName + ", file=" + file.getAbsolutePath() + ']', e); } catch(OverlappingFileLockException ignored) { if (log.isDebugEnabled()) log.debug("File already locked (will ignore): " + file.getAbsolutePath()); } catch (IgniteInterruptedCheckedException e) { U.error(log, "Interrupted while waiting for acquiring file lock: " + file, e); } } finally { lock.unlock(); } } /** * @param platformId Platform id. * @param typeId Type id. */ String readMapping(byte platformId, int typeId) throws IgniteCheckedException { String fileName = getFileName(platformId, typeId); Lock lock = fileLock(fileName); lock.lock(); try { File file = new File(workDir, fileName); try (FileInputStream in = new FileInputStream(file)) { FileLock fileLock = fileLock(in.getChannel(), true); assert fileLock != null : fileName; try (BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) { return reader.readLine(); } } catch (IOException ignored) { return null; } } finally { lock.unlock(); } } /** * @param platformId Platform id. * @param typeId Type id. */ private String getFileName(byte platformId, int typeId) { return typeId + ".classname" + platformId; } /** * @param fileName File name. * @return Lock instance. */ private static Lock fileLock(String fileName) { return fileLock.getLock(fileName.hashCode()); } /** * @param ch File channel. * @param shared Shared. */ private static FileLock fileLock( FileChannel ch, boolean shared ) throws IOException, IgniteInterruptedCheckedException { ThreadLocalRandom rnd = ThreadLocalRandom.current(); while (true) { FileLock fileLock = ch.tryLock(0L, Long.MAX_VALUE, shared); if (fileLock == null) U.sleep(rnd.nextLong(50)); else return fileLock; } } }