001/**
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
004 *
005 * This program and the accompanying materials are dual-licensed under
006 * either the terms of the Eclipse Public License v1.0 as published by
007 * the Eclipse Foundation
008 *
009 *   or (per the licensee's choosing)
010 *
011 * under the terms of the GNU Lesser General Public License version 2.1
012 * as published by the Free Software Foundation.
013 */
014package ch.qos.logback.core.recovery;
015
016import java.io.IOException;
017import java.io.OutputStream;
018import java.util.ArrayList;
019import java.util.List;
020
021import ch.qos.logback.core.Context;
022import ch.qos.logback.core.status.ErrorStatus;
023import ch.qos.logback.core.status.InfoStatus;
024import ch.qos.logback.core.status.Status;
025import ch.qos.logback.core.status.StatusManager;
026
027abstract public class ResilientOutputStreamBase extends OutputStream {
028
029    final static int STATUS_COUNT_LIMIT = 2 * 4;
030
031    private int noContextWarning = 0;
032    private int statusCount = 0;
033
034    private Context context;
035    private RecoveryCoordinator recoveryCoordinator;
036
037    protected OutputStream os;
038    protected boolean presumedClean = true;
039
040    List<RecoveryListener> recoveryListeners = new ArrayList<>(0);
041    
042    private boolean isPresumedInError() {
043        // existence of recoveryCoordinator indicates failed state
044        return (recoveryCoordinator != null && !presumedClean);
045    }
046
047    public void addRecoveryListener(RecoveryListener listener) {
048        recoveryListeners.add(listener);
049    }
050    
051    public void removeRecoveryListener(RecoveryListener listener) {
052        recoveryListeners.remove(listener);
053    }
054    
055    public void write(byte b[], int off, int len) {
056        if (isPresumedInError()) {
057            if (!recoveryCoordinator.isTooSoon()) {
058                attemptRecovery();
059            }
060            return; // return regardless of the success of the recovery attempt
061        }
062
063        try {
064            os.write(b, off, len);
065            postSuccessfulWrite();
066        } catch (IOException e) {
067            postIOFailure(e);
068        }
069    }
070
071    @Override
072    public void write(int b) {
073        if (isPresumedInError()) {
074            if (!recoveryCoordinator.isTooSoon()) {
075                attemptRecovery();
076            }
077            return; // return regardless of the success of the recovery attempt
078        }
079        try {
080            os.write(b);
081            postSuccessfulWrite();
082        } catch (IOException e) {
083            postIOFailure(e);
084        }
085    }
086
087    @Override
088    public void flush() {
089        if (os != null) {
090            try {
091                os.flush();
092                postSuccessfulWrite();
093            } catch (IOException e) {
094                postIOFailure(e);
095            }
096        }
097    }
098
099    abstract String getDescription();
100
101    abstract OutputStream openNewOutputStream() throws IOException;
102
103    private void postSuccessfulWrite() {
104        if (recoveryCoordinator != null) {
105            recoveryCoordinator = null;
106            statusCount = 0;
107            recoveryListeners.forEach( listener -> listener.recoveryOccured());
108            addStatus(new InfoStatus("Recovered from IO failure on " + getDescription(), this));
109        }
110    }
111
112    public void postIOFailure(IOException e) {
113        addStatusIfCountNotOverLimit(new ErrorStatus("IO failure while writing to " + getDescription(), this, e));
114        presumedClean = false;
115        if (recoveryCoordinator == null) {
116            recoveryCoordinator = new RecoveryCoordinator();
117            recoveryListeners.forEach( listener -> listener.newFailure(e));
118        }
119    }
120
121    @Override
122    public void close() throws IOException {
123        if (os != null) {
124            os.close();
125        }
126    }
127
128    void attemptRecovery() {
129        try {
130            close();
131        } catch (IOException e) {
132        }
133
134        addStatusIfCountNotOverLimit(
135                new InfoStatus("Attempting to recover from IO failure on " + getDescription(), this));
136
137        // subsequent writes must always be in append mode
138        try {
139            os = openNewOutputStream();
140            presumedClean = true;
141        } catch (IOException e) {
142            addStatusIfCountNotOverLimit(new ErrorStatus("Failed to open " + getDescription(), this, e));
143        }
144    }
145
146    void addStatusIfCountNotOverLimit(Status s) {
147        ++statusCount;
148        if (statusCount < STATUS_COUNT_LIMIT) {
149            addStatus(s);
150        }
151
152        if (statusCount == STATUS_COUNT_LIMIT) {
153            addStatus(s);
154            addStatus(new InfoStatus("Will supress future messages regarding " + getDescription(), this));
155        }
156    }
157
158    public void addStatus(Status status) {
159        if (context == null) {
160            if (noContextWarning++ == 0) {
161                System.out.println("LOGBACK: No context given for " + this);
162            }
163            return;
164        }
165        StatusManager sm = context.getStatusManager();
166        if (sm != null) {
167            sm.add(status);
168        }
169    }
170
171    public Context getContext() {
172        return context;
173    }
174
175    public void setContext(Context context) {
176        this.context = context;
177    }
178}