Skip to content

Commit

Permalink
from_version-5/fix_issue_769 - fix for settings import error along wi…
Browse files Browse the repository at this point in the history
…th auto-text substitution of projectIds in pasted project settings JSON
  • Loading branch information
wolfch-elsevier committed Jun 1, 2022
1 parent 2500943 commit cdfab72
Show file tree
Hide file tree
Showing 9 changed files with 719 additions and 17 deletions.
19 changes: 18 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,30 @@ target/
.classpath
.project
.settings/
**/.factorypath
out/

# https://www.jenv.be/
.java-version

logs/

velocity.log.*
*.log

dependency-reduced-pom.xml

.protegedata/
.protegedata/

# Package Files
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar

# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
replay_pid*
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.google.gwt.resources.client.DataResource;
import edu.stanford.bmir.protege.web.client.dispatch.DispatchServiceManager;
import edu.stanford.bmir.protege.web.shared.project.ProjectId;
import edu.stanford.bmir.protege.web.server.util.TypelessJSONSerialization;

import javax.annotation.Nonnull;
import javax.inject.Inject;
Expand Down Expand Up @@ -31,10 +32,12 @@ public ProjectSettingsService(DispatchServiceManager dispatch, ProjectId project
public void importSettings(@Nonnull String settingsToImportJson,
@Nonnull Runnable importSuccessfulHandler,
@Nonnull Runnable importErrorHandler) {
String settingsJsonWithUpdatedProjectId;
try {
settingsJsonWithUpdatedProjectId = updateInputJSONProjectId(settingsToImportJson);
RequestBuilder requestBuilder = new RequestBuilder(RequestBuilder.POST, "/data/projects/" + projectId.getId() + "/settings");

requestBuilder.setRequestData(settingsToImportJson);
requestBuilder.setRequestData(settingsJsonWithUpdatedProjectId);
requestBuilder.setHeader("Content-Type", "application/json");
requestBuilder.setCallback(new RequestCallback() {
@Override
Expand All @@ -57,5 +60,9 @@ public void onError(Request request, Throwable exception) {
importErrorHandler.run();
}
}

private String updateInputJSONProjectId(@Nonnull String settingsToImportJson) {
return TypelessJSONSerialization.resplaceAllStringValue(settingsToImportJson, "projectId", this.projectId.getId());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,15 @@
import org.semanticweb.owlapi.model.*;
import uk.ac.manchester.cs.owl.owlapi.OWLDataFactoryImpl;

import java.util.concurrent.atomic.AtomicInteger;

import javax.annotation.Nonnull;
import javax.inject.Inject;
import javax.inject.Provider;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Matthew Horridge
* Stanford Center for Biomedical Informatics Research
Expand All @@ -26,7 +31,11 @@ public class ObjectMapperProvider implements Provider<ObjectMapper> {

@Nonnull
private final OWLDataFactory dataFactory;

private static Logger logger = LoggerFactory.getLogger(ObjectMapperProvider.class);

private static AtomicInteger mapperInstanceCount = new AtomicInteger();

@Inject
public ObjectMapperProvider() {
this.dataFactory = new OWLDataFactoryImpl();
Expand All @@ -35,6 +44,9 @@ public ObjectMapperProvider() {
@Override
public ObjectMapper get() {
ObjectMapper mapper = new ObjectMapper();
int instanceCount = mapperInstanceCount.incrementAndGet();
String msg = String.format("Instantiated another ObjectMappger#%08x, total: %d", mapper.hashCode(), instanceCount);
logger.info(msg);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.setDefaultPrettyPrinter(new DefaultPrettyPrinter());
mapper.configure(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS, false);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,41 @@
package edu.stanford.bmir.protege.web.server.tag;

import com.google.common.collect.Streams;
import edu.stanford.bmir.protege.web.server.events.HasPostEvents;
import edu.stanford.bmir.protege.web.shared.event.ProjectEvent;
import edu.stanford.bmir.protege.web.shared.inject.ProjectSingleton;
import edu.stanford.bmir.protege.web.shared.project.ProjectId;
import edu.stanford.bmir.protege.web.shared.tag.*;
import org.semanticweb.owlapi.model.OWLEntity;
import static com.google.common.base.Preconditions.checkNotNull;
import static edu.stanford.bmir.protege.web.shared.tag.TagId.createTagId;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;
import static java.util.stream.Collectors.toSet;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.inject.Inject;
import java.util.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.stream.Stream;

import static com.google.common.base.Preconditions.checkNotNull;
import static edu.stanford.bmir.protege.web.shared.tag.TagId.createTagId;
import static java.util.stream.Collectors.*;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.inject.Inject;

import org.semanticweb.owlapi.model.OWLEntity;

import com.google.common.collect.Streams;

import edu.stanford.bmir.protege.web.server.events.HasPostEvents;
import edu.stanford.bmir.protege.web.shared.event.ProjectEvent;
import edu.stanford.bmir.protege.web.shared.inject.ProjectSingleton;
import edu.stanford.bmir.protege.web.shared.project.ProjectId;
import edu.stanford.bmir.protege.web.shared.tag.EntityTagsChangedEvent;
import edu.stanford.bmir.protege.web.shared.tag.ProjectTagsChangedEvent;
import edu.stanford.bmir.protege.web.shared.tag.Tag;
import edu.stanford.bmir.protege.web.shared.tag.TagData;
import edu.stanford.bmir.protege.web.shared.tag.TagId;

/**
* Matthew Horridge
Expand Down Expand Up @@ -150,6 +166,8 @@ public Collection<Tag> getProjectTags() {
*/
public void setProjectTags(@Nonnull Collection<TagData> newProjectTags) {
checkNotNull(newProjectTags);
if (newProjectTags.size() == 0)
return;
Collection<Tag> currentProjectTags = getProjectTags();
Set<OWLEntity> modifiedEntityTags = new HashSet<>();
try {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package edu.stanford.bmir.protege.web.server.util;

public class JavaUtil {
/**
* The purpose of this cast utility to be able to suppress unchecked
* cast in one and only one place so we don't have to pollute the main
* application code with @SuppressWarnings and thus be able to flag
* places where the type cast warning(s) are/were unexpected.
* @param <T>
* @param obj
* @return
*
* This great idea came from:
* @see http://www.whizu.org/articles/how-to-avoid-unchecked-cast-warnings-with-java-generics.whizu
*/
@SuppressWarnings("unchecked")
public static <T> T cast(Object obj) {
return (T)obj;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package edu.stanford.bmir.protege.web.server.util;

import static edu.stanford.bmir.protege.web.server.util.JavaUtil.cast;

import java.io.IOException;
import java.util.List;
import java.util.Map;

import javax.annotation.Nonnull;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.ObjectWriter;

/**
* These utility methods are for manipulating arbitrary JSON without needing to worry about what
* typed entities the JSON should be de/serialized to/from.
*
* Initial motivation is to be able to change the value of "projectId" in project settings and
* project forms import JSON.
*
* @author Chris Wolf <c.wolf@elsevier.com>
*/
public class TypelessJSONSerialization {

// TODO: should probably be injected, but effects the whole upstream DI call chain...
// TODO: ...and plus, ObjectMapper very expensive, so should be created only once.
private static final ObjectMapper objectMapper = new ObjectMapper();

public TypelessJSONSerialization() {
}

/**
* Accepts any arbitrary JSON and deserializes to nested <code>LinkedHashMap<String, Object></code>
* instances. Note that the <code>Object</code> type parameter could be of type <code>String</code>,
* <code>List</code>, or <code>Map</code> where the latter is for arbitrary nesting levels.
*
* <b>N.B.</b> For normal, strongly typed Webprotege serialization, use the <code>ObjectMapper</code>
* obtained from <code>ObjectMapperProvider</code> from <i>webprotege-server-core</i>.
*
* @param json
* @return
* @throws IOException
*/
public static Map<String, Object> deserializeJSON(@Nonnull String json) throws IOException {
ObjectReader objectReader = objectMapper.readerFor(new TypeReference<Map<String, Object>>() {
});
return objectReader.readValue(json);
}

/**
*
* @param object
* @return
* @throws IOException
*/
public static String serializeToJSON(Map<String, Object> object) throws IOException {
return serializeToJSON(object, false);
}

public static String serializeToJSON(Map<String, Object> object, boolean prettyPrint) throws IOException {
ObjectWriter objectWriter = prettyPrint ? objectMapper.writerWithDefaultPrettyPrinter() : objectMapper.writer();
return objectWriter.writeValueAsString(object);
}

public static String resplaceAllStringValue(@Nonnull String json, @Nonnull String keyName,
@Nonnull String replacementValue) throws IOException {

Map<String, Object> deserialized = deserializeJSON(json);

walkMapAndDReplace(deserialized, keyName, replacementValue);

String serialized = serializeToJSON(deserialized);

return serialized;
}

static void walkMapAndDReplace(Map<String, Object> data, String keyName, String replacementValue) {
for (var mapEntry : data.entrySet()) {
if (mapEntry.getValue() instanceof String && mapEntry.getKey().equals(keyName)) {
mapEntry.setValue(replacementValue);
} else if (mapEntry.getValue() instanceof List) {
List<Object> objlist = cast(mapEntry.getValue());
for (Object listEntry : objlist) {
if (listEntry instanceof Map)
walkMapAndDReplace(cast(listEntry), keyName, replacementValue);
}
} else if (mapEntry.getValue() instanceof Map) {
walkMapAndDReplace(cast(mapEntry.getValue()), keyName, replacementValue);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package edu.stanford.bmir.protege.web;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;

public class TestUtils {
/**
* Read contents of text file resource from <code>src/test/resources</code>.
* Entire content read into memory, so best for small files.
*
* @param resourceName
* @return text content and assuming UTF-8 encoding
* @throws IOException
*/
public static String readResourceTestFile(String resourceName) throws IOException {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
URI resource;
try {
resource = classLoader.getResource(resourceName).toURI();
} catch (URISyntaxException e) {
String msg = String.format("Can't read '%s'", resourceName);
throw new IOException(msg, e);
}
byte[] rawContent = Files.readAllBytes(Paths.get(resource));

return new String(rawContent, StandardCharsets.UTF_8);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package edu.stanford.bmir.protege.web.server.util;

import static edu.stanford.bmir.protege.web.server.util.JavaUtil.cast;
import static org.junit.Assert.assertEquals;

import java.util.List;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner.Silent;

import edu.stanford.bmir.protege.web.TestUtils;
import edu.stanford.bmir.protege.web.shared.project.ProjectId;

@RunWith(Silent.class)
public class TypelessJSONSerialization_TestCase {
private ProjectId projectId = ProjectId.get("c4e39f8f-d2b3-4212-8888-28fabd2aa5ac");

@Test
public void testJSONSerialization() throws Exception {
// test basic round-trip deserialize/serialize

String testSettings = TestUtils
.readResourceTestFile("edu/stanford/bmir/protege/web/server/util/project_settings_notags.json");
// out.println(testSettings);
Map<String, Object> data = TypelessJSONSerialization.deserializeJSON(testSettings);

String newSettings = TypelessJSONSerialization.serializeToJSON(data, true);

assertEquals(StringUtils.deleteWhitespace(testSettings), StringUtils.deleteWhitespace(newSettings));
// out.println(newSettings);
}

@Test
public void testJSONSerializationNameValueReplacement() throws Exception {
String testSettings = TestUtils
.readResourceTestFile("edu/stanford/bmir/protege/web/server/util/project_settings_notags.json");

String newSettings = TypelessJSONSerialization.resplaceAllStringValue(testSettings, "projectId",
projectId.getId());
Map<String, Object> data = TypelessJSONSerialization.deserializeJSON(newSettings);

String id = projectId.getId();
Map<String, Object> projectSettings = cast(data.get("projectSettings"));
Map<String, Object> sharingSettings = cast(data.get("sharingSettings"));
Map<String, Object> searchSettings = cast(data.get("searchSettings"));
List<Map<String, Object>> searchFilters = cast(searchSettings.get("searchFilters"));

assertEquals(id, projectSettings.get("projectId"));
assertEquals(id, sharingSettings.get("projectId"));
assertEquals(id, searchSettings.get("projectId"));
assertEquals(id, searchFilters.get(0).get("projectId"));
}
}

0 comments on commit cdfab72

Please sign in to comment.