blob: 37f4d8a285bc2e934b44b2a6229842eb257bd6dc [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2014, 2019 The Eclipse Foundation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Yatta Solutions - initial API and implementation, bug 432803: public API
*******************************************************************************/
package org.eclipse.epp.internal.mpc.core.service;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.CharArrayReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CodingErrorAction;
import java.nio.charset.StandardCharsets;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.epp.internal.mpc.core.MarketplaceClientCore;
import org.eclipse.epp.internal.mpc.core.service.xml.StripInvalidXMLCharsReader;
import org.eclipse.epp.internal.mpc.core.service.xml.Unmarshaller;
import org.eclipse.epp.mpc.core.service.IMarketplaceUnmarshaller;
import org.eclipse.epp.mpc.core.service.UnmarshalException;
import org.eclipse.osgi.util.NLS;
import org.osgi.service.component.annotations.Component;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
/**
* @author Carsten Reckord
*/
@Component(name = "org.eclipse.epp.mpc.core.unmarshaller")
public class MarketplaceUnmarshaller implements IMarketplaceUnmarshaller {
@Override
public <T> T unmarshal(InputStream in, Class<T> type, IProgressMonitor monitor) throws IOException,
UnmarshalException {
if (in == null) {
throw new IOException(Messages.MarketplaceUnmarshaller_errorNullStream);
}
final Unmarshaller unmarshaller = new Unmarshaller();
final XMLReader xmlReader = Unmarshaller.createXMLReader(unmarshaller);
BufferedInputStream bufferedInput = in instanceof BufferedInputStream ? (BufferedInputStream) in
: new BufferedInputStream(in);
ByteBuffer peekBuffer = peekResponseContent(bufferedInput);
// FIXME how can the charset be determined?
Reader reader = new InputStreamReader(bufferedInput, StandardCharsets.UTF_8);
reader = new StripInvalidXMLCharsReader(reader);
try {
xmlReader.parse(new InputSource(reader));
} catch (final SAXException e) {
IStatus error = createContentError(peekBuffer,
NLS.bind(Messages.MarketplaceUnmarshaller_invalidResponseContent, e.getMessage()), e);
throw new UnmarshalException(error);
}
Object model = unmarshaller.getModel();
if (model == null) {
// if we reach here this should never happen
IStatus error = createContentError(peekBuffer,
Messages.MarketplaceUnmarshaller_unexpectedResponseContentNullResult, null);
throw new UnmarshalException(error);
} else {
try {
return type.cast(model);
} catch (Exception e) {
String message = NLS.bind(Messages.DefaultMarketplaceService_unexpectedResponseContent,
model.getClass().getSimpleName());
IStatus error = createContentError(peekBuffer, message, e);
throw new UnmarshalException(error);
}
}
}
private ByteBuffer peekResponseContent(BufferedInputStream bufferedInput) throws IOException {
bufferedInput.mark(2049);
ReadableByteChannel inputChannel = Channels.newChannel(bufferedInput);
ByteBuffer peekBuffer = ByteBuffer.allocate(2048);
while (peekBuffer.hasRemaining()) {
int read = inputChannel.read(peekBuffer);
if (read == -1) {
break;
}
}
bufferedInput.reset();
peekBuffer.flip();
return peekBuffer;
}
private IStatus createContentError(ByteBuffer peekBuffer, String message, Throwable t) {
IStatus status = createErrorStatus(message, t);
if (peekBuffer != null && peekBuffer.hasRemaining()) {
IStatus contentInfo = createContentInfo(peekBuffer);
if (contentInfo != null) {
MultiStatus multiStatus = new MultiStatus(status.getPlugin(), status.getCode(), status.getMessage(),
status.getException());
multiStatus.add(contentInfo);
status = multiStatus;
}
}
return status;
}
private IStatus createContentInfo(ByteBuffer peekBuffer) {
try {
StringBuilder message = new StringBuilder("Received response begins with:\n\n"); //$NON-NLS-1$
CharsetDecoder decoder = StandardCharsets.US_ASCII.newDecoder();
decoder.onMalformedInput(CodingErrorAction.REPLACE);
decoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
decoder.replaceWith("?"); //$NON-NLS-1$
CharBuffer charBuffer = decoder.decode(peekBuffer);
BufferedReader reader = new BufferedReader(new CharArrayReader(charBuffer.array(), 0, charBuffer.limit()));
for (int i = 0; i < 3; i++) {
//include the first three lines
String line = reader.readLine();
if (line == null) {
break;
}
char[] safeChars = line.toCharArray();
for (int j = 0; j < safeChars.length; j++) {
char c = safeChars[j];
if (c < 32 || c >= 127) {
//replace potentially non-printable character
safeChars[j] = '?';
}
}
message.append(i + 1).append(": ").append(new String(safeChars)).append("\n"); //$NON-NLS-1$ //$NON-NLS-2$
}
return new Status(IStatus.INFO, MarketplaceClientCore.BUNDLE_ID, 0, message.toString(), null);
} catch (Exception e) {
// ignore - this is only additional diagnostic info, so don't bother anybody with errors assembling it
return null;
}
}
protected IStatus createErrorStatus(String message, Throwable t) {
return new Status(IStatus.ERROR, MarketplaceClientCore.BUNDLE_ID, 0, message, t);
}
}