com.getperka.flatpack.ext.BaseContext Maven / Gradle / Ivy
/*
* #%L
* FlatPack serialization code
* %%
* Copyright (C) 2012 Perka Inc.
* %%
* 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.
* #L%
*/
package com.getperka.flatpack.ext;
import static com.getperka.flatpack.util.FlatPackCollections.listForAny;
import static com.getperka.flatpack.util.FlatPackCollections.mapForIteration;
import java.io.Closeable;
import java.io.IOException;
import java.security.Principal;
import java.util.ArrayDeque;
import java.util.Collections;
import java.util.Deque;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Callable;
import javax.inject.Inject;
import com.getperka.flatpack.FlatPackEntity;
import com.getperka.flatpack.HasUuid;
/**
* Contains common data that affects the serialization process.
*
* This class also provides an error-assignment mechanism that tracks the current path of the
* serializer/deserializer through the object graph. When writing a {@link Codex} subtype, the
* following pattern should be used:
*
*
* void read/write(BaseContext context) {
* context.pushPath("useful information");
* try {
* // Do work, possibly utilizing other Codexes
* } catch (CheckedException e) {
* context.fail(e);
* } finally {
* context.popPath();
* }
* }
*
*/
public abstract class BaseContext implements Closeable {
private final Deque path = new ArrayDeque();
private final List> postWork = listForAny();
@Inject
private Principal principal;
private final Map warnings = mapForIteration();
BaseContext() {
path.addLast("");
}
/**
* Add a Callable to be executed when the (de)-serialization pass is completed. This is used
* primarily for fixing up "implied" properties across on-to-many relationships.
*/
public void addPostWork(Callable> r) {
postWork.add(r);
}
/**
* Add a warning message to be reported via the {@link FlatPackEntity} being processed.
*/
public void addWarning(HasUuid entity, String format, Object... args) {
warnings.put(entity.getUuid(), String.format(format, args));
}
@Override
public void close() throws IOException {}
/**
* Updates the exception's stack trace with the current path elements and performs a "sneaky"
* throw to continue to propagate the (checked) exception up the stack.
*/
public void fail(Throwable e) {
StackTraceElement newElement = new StackTraceElement("FlatPack", toString(), null, 0);
StackTraceElement[] stack = e.getStackTrace();
if (stack == null || stack.length == 0) {
e.setStackTrace(new StackTraceElement[] { newElement });
} else if (!"FlatPack".equals(stack[0].getClassName())) {
StackTraceElement[] newStack = new StackTraceElement[stack.length + 1];
newStack[0] = newElement;
System.arraycopy(stack, 0, newStack, 1, stack.length);
e.setStackTrace(newStack);
}
this. sneakyThrow(e);
}
public Principal getPrincipal() {
return principal;
}
public Map getWarnings() {
return Collections.unmodifiableMap(warnings);
}
/**
* Removes the topmost path.
*
* @return the removed path element
*/
public String popPath() {
return path.removeLast();
}
/**
* Adds a path to the error-reporting stack.
*
* @param element the path element to add
*/
public void pushPath(String element) {
path.addLast(element);
}
/**
* Executes all work items passed ino {@link #addPostWork(Callable)}.
*/
public void runPostWork() {
// Sort work by priority
Collections.sort(postWork, new PostWorkComparator());
for (Callable> callable : postWork) {
pushPath("" + callable.getClass().getName());
try {
callable.call();
} catch (Exception e) {
fail(e);
} finally {
popPath();
}
}
postWork.clear();
}
/**
* Returns the current error-reporting stack.
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
for (String p : path) {
sb.append(p);
}
return sb.toString();
}
/**
* A method to throw a possibly checked exception.
*/
@SuppressWarnings("unchecked")
private void sneakyThrow(Throwable t) throws T {
throw (T) t;
}
}