http-services(-pro): Add back http-service and move custom http-service

This commit is contained in:
Owain van Brakel
2019-08-09 05:22:26 +02:00
parent f85b1254cd
commit 3a92e3b008
109 changed files with 8699 additions and 112 deletions

View File

@@ -0,0 +1,203 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.http.service;
import ch.qos.logback.classic.LoggerContext;
import com.google.common.base.Strings;
import java.io.IOException;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.ServletException;
import javax.sql.DataSource;
import lombok.extern.slf4j.Slf4j;
import net.runelite.http.api.RuneLiteAPI;
import net.runelite.http.service.util.InstantConverter;
import okhttp3.Cache;
import okhttp3.OkHttpClient;
import org.slf4j.ILoggerFactory;
import org.slf4j.impl.StaticLoggerBinder;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.sql2o.Sql2o;
import org.sql2o.converters.Converter;
import org.sql2o.quirks.NoQuirks;
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EnableScheduling
@Slf4j
public class SpringBootWebApplication extends SpringBootServletInitializer
{
@Bean
protected ServletContextListener listener()
{
return new ServletContextListener()
{
@Override
public void contextInitialized(ServletContextEvent sce)
{
log.info("RuneLitePlus API started");
}
@Override
public void contextDestroyed(ServletContextEvent sce)
{
// Destroy okhttp client
OkHttpClient client = RuneLiteAPI.CLIENT;
client.dispatcher().executorService().shutdown();
client.connectionPool().evictAll();
try
{
Cache cache = client.cache();
if (cache != null)
{
cache.close();
}
}
catch (IOException ex)
{
log.warn(null, ex);
}
log.info("RuneLitePlus API stopped");
}
};
}
@ConfigurationProperties(prefix = "datasource.runeliteplus")
@Bean("dataSourceRuneLite")
public DataSourceProperties dataSourceProperties()
{
return new DataSourceProperties();
}
@ConfigurationProperties(prefix = "datasource.runeliteplus-cache")
@Bean("dataSourceRuneLiteCache")
public DataSourceProperties dataSourcePropertiesCache()
{
return new DataSourceProperties();
}
@ConfigurationProperties(prefix = "datasource.runeliteplus-tracker")
@Bean("dataSourceRuneLiteTracker")
public DataSourceProperties dataSourcePropertiesTracker()
{
return new DataSourceProperties();
}
@Bean(value = "runelite", destroyMethod = "")
public DataSource runeliteDataSource(@Qualifier("dataSourceRuneLite") DataSourceProperties dataSourceProperties)
{
return getDataSource(dataSourceProperties);
}
@Bean(value = "runelite-cache2", destroyMethod = "")
public DataSource runeliteCache2DataSource(@Qualifier("dataSourceRuneLiteCache") DataSourceProperties dataSourceProperties)
{
return getDataSource(dataSourceProperties);
}
@Bean(value = "runelite-tracker", destroyMethod = "")
public DataSource runeliteTrackerDataSource(@Qualifier("dataSourceRuneLiteTracker") DataSourceProperties dataSourceProperties)
{
return getDataSource(dataSourceProperties);
}
@Bean("Runelite SQL2O")
public Sql2o sql2o(@Qualifier("runelite") DataSource dataSource)
{
return createSql2oFromDataSource(dataSource);
}
@Bean("Runelite Cache SQL2O")
public Sql2o cacheSql2o(@Qualifier("runelite-cache2") DataSource dataSource)
{
return createSql2oFromDataSource(dataSource);
}
@Bean("Runelite XP Tracker SQL2O")
public Sql2o trackerSql2o(@Qualifier("runelite-tracker") DataSource dataSource)
{
return createSql2oFromDataSource(dataSource);
}
private static DataSource getDataSource(DataSourceProperties dataSourceProperties)
{
if (!Strings.isNullOrEmpty(dataSourceProperties.getJndiName()))
{
// Use JNDI provided datasource, which is already configured with pooling
JndiDataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
return dataSourceLookup.getDataSource(dataSourceProperties.getJndiName());
}
else
{
return dataSourceProperties.initializeDataSourceBuilder().build();
}
}
private static Sql2o createSql2oFromDataSource(final DataSource dataSource)
{
final Map<Class, Converter> converters = new HashMap<>();
converters.put(Instant.class, new InstantConverter());
return new Sql2o(dataSource, new NoQuirks(converters));
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application)
{
return application.sources(SpringBootWebApplication.class);
}
@Override
public void onStartup(ServletContext servletContext) throws ServletException
{
super.onStartup(servletContext);
ILoggerFactory loggerFactory = StaticLoggerBinder.getSingleton().getLoggerFactory();
if (loggerFactory instanceof LoggerContext)
{
LoggerContext loggerContext = (LoggerContext) loggerFactory;
loggerContext.setPackagingDataEnabled(false);
log.debug("Disabling logback packaging data");
}
}
public static void main(String[] args)
{
SpringApplication.run(SpringBootWebApplication.class, args);
}
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright (c) 2018, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.http.service;
import java.util.List;
import net.runelite.http.api.RuneLiteAPI;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.GsonHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@EnableWebMvc
public class SpringWebMvcConfigurer implements WebMvcConfigurer
{
/**
* Configure .js as application/json to trick Cloudflare into caching json responses
*/
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer)
{
configurer.mediaType("js", MediaType.APPLICATION_JSON);
}
/**
* Use GSON instead of Jackson for JSON serialization
* @param converters
*/
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters)
{
// Could not figure out a better way to force GSON
converters.removeIf(MappingJackson2HttpMessageConverter.class::isInstance);
GsonHttpMessageConverter gsonHttpMessageConverter = new GsonHttpMessageConverter();
gsonHttpMessageConverter.setGson(RuneLiteAPI.GSON);
converters.add(gsonHttpMessageConverter);
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright (c) 2018, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.http.service.animation;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
class AnimationCache
{
private int npcid;
private int anim1;
private int anim2;
private int anim3;
private int anim4;
private int anim5;
private int anim6;
private int anim7;
private int anim8;
private int anim9;
private int anim10;
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright (c) 2018, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.http.service.animation;
import java.util.List;
import java.util.stream.Collectors;
import net.runelite.http.api.animation.AnimationKey;
import net.runelite.http.api.animation.AnimationRequest;
import net.runelite.http.service.util.exception.NotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import static org.springframework.web.bind.annotation.RequestMethod.POST;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/animation")
public class AnimationController
{
@Autowired
private AnimationEndpoint animationService;
@RequestMapping(method = POST)
public void submit(@RequestBody AnimationRequest animationRequest)
{
animationService.submit(animationRequest);
}
@GetMapping
public List<AnimationKey> get()
{
return animationService.get().stream()
.map(AnimationController::entryToKey)
.collect(Collectors.toList());
}
@GetMapping("/{npcid}")
public AnimationKey getRegion(@PathVariable int npcid)
{
AnimationEntry animationEntry = animationService.getNPC(npcid);
if (animationEntry == null)
{
throw new NotFoundException();
}
return entryToKey(animationEntry);
}
private static AnimationKey entryToKey(AnimationEntry xe)
{
AnimationKey animationKey = new AnimationKey();
animationKey.setNPCId(xe.getNPCId());
animationKey.setAnimations(new int[]
{
xe.getAnimations()[0],
xe.getAnimations()[1],
xe.getAnimations()[2],
xe.getAnimations()[3],
xe.getAnimations()[4],
xe.getAnimations()[5],
xe.getAnimations()[6],
xe.getAnimations()[7],
xe.getAnimations()[8],
xe.getAnimations()[9],
});
return animationKey;
}
}

View File

@@ -0,0 +1,206 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.http.service.animation;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import java.util.List;
import net.runelite.http.api.animation.AnimationKey;
import net.runelite.http.api.animation.AnimationRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.sql2o.Connection;
import org.sql2o.Query;
import org.sql2o.Sql2o;
@Service
public class AnimationEndpoint
{
private static final String CREATE_SQL = "CREATE TABLE IF NOT EXISTS `animation` (\n"
+ " `id` int(11) NOT NULL AUTO_INCREMENT,\n"
+ " `npcid` int(11) NOT NULL,\n"
+ " `time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n"
+ " `rev` int(11) NOT NULL,\n"
+ " `anim1` int(11) NOT NULL,\n"
+ " `anim2` int(11),\n"
+ " `anim3` int(11),\n"
+ " `anim4` int(11),\n"
+ " `anim5` int(11),\n"
+ " `anim6` int(11),\n"
+ " `anim7` int(11),\n"
+ " `anim8` int(11),\n"
+ " `anim9` int(11),\n"
+ " `anim10` int(11),\n"
+ " PRIMARY KEY (`id`),\n"
+ " KEY `npcid` (`npcid`,`time`)\n"
+ ") ENGINE=InnoDB";
private final Sql2o sql2o;
private final Cache<Integer, AnimationCache> keyCache = CacheBuilder.newBuilder()
.maximumSize(1024)
.build();
@Autowired
public AnimationEndpoint(
@Qualifier("Runelite SQL2O") Sql2o sql2o
)
{
this.sql2o = sql2o;
try (Connection con = sql2o.beginTransaction())
{
con.createQuery(CREATE_SQL)
.executeUpdate();
}
}
private AnimationEntry findLatestAnimations(Connection con, int npcid)
{
return con.createQuery("select npcid, time, anim1, anim2, anim3, anim4, anim5, anim6, anim7, anim8, anim9, anim10 from animation "
+ "where npcid = :npcid "
+ "order by time desc "
+ "limit 1")
.addParameter("npcid", npcid)
.executeAndFetchFirst(AnimationEntry.class);
}
public void submit(AnimationRequest animationRequest)
{
boolean cached = true;
for (AnimationKey key : animationRequest.getKeys())
{
int npcid = key.getNPCId();
int[] animations = key.getAnimations();
AnimationCache animationCache = keyCache.getIfPresent(npcid);
if (animationCache == null
|| animationCache.getAnim1() != animations[0]
|| animationCache.getAnim2() != animations[1]
|| animationCache.getAnim3() != animations[2]
|| animationCache.getAnim4() != animations[3]
|| animationCache.getAnim5() != animations[4]
|| animationCache.getAnim6() != animations[5]
|| animationCache.getAnim7() != animations[6]
|| animationCache.getAnim8() != animations[7]
|| animationCache.getAnim9() != animations[8]
|| animationCache.getAnim10() != animations[9])
{
cached = false;
keyCache.put(npcid, new AnimationCache(npcid, animations[0], animations[1], animations[2], animations[3], animations[4], animations[5], animations[6], animations[7], animations[8], animations[9]));
}
}
if (cached)
{
return;
}
try (Connection con = sql2o.beginTransaction())
{
Query query = null;
for (AnimationKey key : animationRequest.getKeys())
{
int npcid = key.getNPCId();
int[] animations = key.getAnimations();
AnimationEntry animationEntry = findLatestAnimations(con, npcid);
if (animations.length != 10)
{
throw new IllegalArgumentException("Key length must be 10");
}
// already have these?
if (animationEntry != null
&& animationEntry.getAnimations()[0] == animations[0]
&& animationEntry.getAnimations()[1] == animations[1]
&& animationEntry.getAnimations()[2] == animations[2]
&& animationEntry.getAnimations()[3] == animations[3]
&& animationEntry.getAnimations()[4] == animations[4]
&& animationEntry.getAnimations()[5] == animations[5]
&& animationEntry.getAnimations()[6] == animations[6]
&& animationEntry.getAnimations()[7] == animations[7]
&& animationEntry.getAnimations()[8] == animations[8]
&& animationEntry.getAnimations()[9] == animations[9])
{
continue;
}
if (query == null)
{
query = con.createQuery("insert into animation (npcid, rev, anim1, anim2, anim3, anim4, anim5, anim6, anim7, anim8, anim9, anim10) "
+ "values (:npcid, :rev, :anim1, :anim2, :anim3, :anim4, anim5, anim6, anim7, anim8, anim9, anim10)");
}
query.addParameter("npcid", npcid)
.addParameter("rev", animationRequest.getRevision())
.addParameter("anim1", animations[0])
.addParameter("anim2", animations[1])
.addParameter("anim3", animations[2])
.addParameter("anim4", animations[3])
.addParameter("anim5", animations[4])
.addParameter("anim6", animations[5])
.addParameter("anim7", animations[6])
.addParameter("anim8", animations[7])
.addParameter("anim9", animations[8])
.addParameter("anim10", animations[9])
.addToBatch();
}
if (query != null)
{
query.executeBatch();
con.commit(false);
}
}
}
public List<AnimationEntry> get()
{
try (Connection con = sql2o.open())
{
return con.createQuery(
"select t1.npcid, t2.time, t2.rev, t2.anim1, t2.anim2, t2.anim3, t2.anim4, t2.anim5, t2.anim6, t2.anim7, t2.anim8, t2.anim9, t2.anim10 from " +
"(select npcid,max(id) as id from animation group by npcid) t1 " +
"join animation t2 on t1.id = t2.id")
.executeAndFetch(AnimationEntry.class);
}
}
public AnimationEntry getNPC(int npcid)
{
try (Connection con = sql2o.open())
{
return con.createQuery("select npcid, time, rev, anim1, anim2, anim3, anim4, anim5, anim6, anim7, anim8, anim9, anim10 from animation "
+ "where npcid = :npcid order by time desc limit 1")
.addParameter("npcid", npcid)
.executeAndFetchFirst(AnimationEntry.class);
}
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.http.service.animation;
import java.time.Instant;
public class AnimationEntry
{
private int npcid;
private Instant time;
private int rev;
private int[] animations;
public int getNPCId()
{
return npcid;
}
public void setNPCId(int npcid)
{
this.npcid = npcid;
}
public Instant getTime()
{
return time;
}
public void setTime(Instant time)
{
this.time = time;
}
public int getRev()
{
return rev;
}
public void setRev(int rev)
{
this.rev = rev;
}
public int[] getAnimations()
{
return animations;
}
public void setAnimations(int[] animations)
{
this.animations = animations;
}
}

View File

@@ -0,0 +1,103 @@
/*
* Copyright (c) 2018, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.http.service.chat;
import java.util.regex.Pattern;
import com.google.common.base.Strings;
import net.runelite.http.api.chat.House;
import net.runelite.http.service.util.exception.NotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/chat")
public class ChatController
{
private static final Pattern STRING_VALIDATION = Pattern.compile("[^a-zA-Z0-9' -]");
private static final int STRING_MAX_LENGTH = 50;
@Autowired
private ChatService chatService;
@PostMapping("/layout")
public void submitLayout(@RequestParam String name, @RequestParam String layout)
{
if (Strings.isNullOrEmpty(layout))
{
return;
}
chatService.setLayout(name, layout);
}
@GetMapping("/layout")
public String getLayout(@RequestParam String name)
{
String layout = chatService.getLayout(name);
if (layout == null)
{
throw new NotFoundException();
}
return layout;
}
@PostMapping("/hosts")
public void submitHost(@RequestParam int world, @RequestParam String location, @RequestParam String owner, @RequestParam boolean guildedAltar, @RequestParam boolean occultAltar, @RequestParam boolean spiritTree, @RequestParam boolean fairyRing, @RequestParam boolean wildernessObelisk, @RequestParam boolean repairStand, @RequestParam boolean combatDummy, @RequestParam(required = false, defaultValue = "false") boolean remove)
{
if (!location.equals("Rimmington") && !location.equals("Yanille"))
{
return;
}
House house = new House();
house.setOwner(owner);
house.setGuildedAltarPresent(guildedAltar);
house.setOccultAltarPresent(occultAltar);
house.setSpiritTreePresent(spiritTree);
house.setFairyRingPresent(fairyRing);
house.setWildernessObeliskPresent(wildernessObelisk);
house.setRepairStandPresent(repairStand);
house.setCombatDummyPresent(combatDummy);
if (remove)
{
chatService.removeHost(world, location, house);
}
else
{
chatService.addHost(world, location, house);
}
}
@GetMapping("/hosts")
public House[] getHosts(@RequestParam int world, @RequestParam String location)
{
return chatService.getHosts(world, location);
}
}

View File

@@ -0,0 +1,120 @@
/*
* Copyright (c) 2018, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.http.service.chat;
import java.time.Duration;
import java.util.List;
import net.runelite.http.api.chat.ChatClient;
import net.runelite.http.api.RuneLiteAPI;
import net.runelite.http.api.chat.House;
import net.runelite.http.service.util.redis.RedisPool;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;
@Service
public class ChatService
{
private static final Duration EXPIRE = Duration.ofMinutes(2);
private final RedisPool jedisPool;
private final ChatClient chatClient = new ChatClient();
@Autowired
public ChatService(RedisPool jedisPool)
{
this.jedisPool = jedisPool;
}
public String getLayout(String name)
{
String value;
try (Jedis jedis = jedisPool.getResource())
{
value = jedis.get("layout." + name);
}
return value;
}
public void setLayout(String name, String layout)
{
if (!chatClient.testLayout(layout))
{
throw new IllegalArgumentException(layout);
}
try (Jedis jedis = jedisPool.getResource())
{
jedis.setex("layout." + name, (int) EXPIRE.getSeconds(), layout);
}
}
public void addHost(int world, String location, House house)
{
String houseJSON = house.toString();
String key = "hosts.w" + Integer.toString(world) + "." + location;
try (Jedis jedis = jedisPool.getResource())
{
jedis.rpush(key, houseJSON);
}
}
public House[] getHosts(int world, String location)
{
List<String> json;
String key = "hosts.w" + Integer.toString(world) + "." + location;
try (Jedis jedis = jedisPool.getResource())
{
json = jedis.lrange(key, 0, 25);
}
if (json.isEmpty())
{
return null;
}
House[] hosts = new House[json.size()];
for (int i = 0; i < json.size(); i++)
{
hosts[i] = RuneLiteAPI.GSON.fromJson(json.get(i), House.class);
}
return hosts;
}
public void removeHost(int world, String location, House house)
{
String json = house.toString();
String key = "hosts.w" + Integer.toString(world) + "." + location;
try (Jedis jedis = jedisPool.getResource())
{
jedis.lrem(key, 0, json);
}
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright (c) 2018, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.http.service.xtea;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
class XteaCache
{
private int region;
private int key1;
private int key2;
private int key3;
private int key4;
}

View File

@@ -0,0 +1,86 @@
/*
* Copyright (c) 2018, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.http.service.xtea;
import java.util.List;
import java.util.stream.Collectors;
import net.runelite.http.api.xtea.XteaKey;
import net.runelite.http.api.xtea.XteaRequest;
import net.runelite.http.service.util.exception.NotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import static org.springframework.web.bind.annotation.RequestMethod.POST;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/xtea")
public class XteaController
{
@Autowired
private XteaEndpoint xteaService;
@RequestMapping(method = POST)
public void submit(@RequestBody XteaRequest xteaRequest)
{
xteaService.submit(xteaRequest);
}
@GetMapping
public List<XteaKey> get()
{
return xteaService.get().stream()
.map(XteaController::entryToKey)
.collect(Collectors.toList());
}
@GetMapping("/{region}")
public XteaKey getRegion(@PathVariable int region)
{
XteaEntry xteaRegion = xteaService.getRegion(region);
if (xteaRegion == null)
{
throw new NotFoundException();
}
return entryToKey(xteaRegion);
}
private static XteaKey entryToKey(XteaEntry xe)
{
XteaKey xteaKey = new XteaKey();
xteaKey.setRegion(xe.getRegion());
xteaKey.setKeys(new int[]
{
xe.getKey1(),
xe.getKey2(),
xe.getKey3(),
xe.getKey4()
});
return xteaKey;
}
}

View File

@@ -0,0 +1,183 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.http.service.xtea;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import java.util.List;
import net.runelite.http.api.xtea.XteaKey;
import net.runelite.http.api.xtea.XteaRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.sql2o.Connection;
import org.sql2o.Query;
import org.sql2o.Sql2o;
@Service
public class XteaEndpoint
{
private static final String CREATE_SQL = "CREATE TABLE IF NOT EXISTS `xtea` (\n"
+ " `id` int(11) NOT NULL AUTO_INCREMENT,\n"
+ " `region` int(11) NOT NULL,\n"
+ " `time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n"
+ " `rev` int(11) NOT NULL,\n"
+ " `key1` int(11) NOT NULL,\n"
+ " `key2` int(11) NOT NULL,\n"
+ " `key3` int(11) NOT NULL,\n"
+ " `key4` int(11) NOT NULL,\n"
+ " PRIMARY KEY (`id`),\n"
+ " KEY `region` (`region`,`time`)\n"
+ ") ENGINE=InnoDB";
private final Sql2o sql2o;
private final Cache<Integer, XteaCache> keyCache = CacheBuilder.newBuilder()
.maximumSize(1024)
.build();
@Autowired
public XteaEndpoint(
@Qualifier("Runelite SQL2O") Sql2o sql2o
)
{
this.sql2o = sql2o;
try (Connection con = sql2o.beginTransaction())
{
con.createQuery(CREATE_SQL)
.executeUpdate();
}
}
private XteaEntry findLatestXtea(Connection con, int region)
{
return con.createQuery("select region, time, key1, key2, key3, key4 from xtea "
+ "where region = :region "
+ "order by time desc "
+ "limit 1")
.addParameter("region", region)
.executeAndFetchFirst(XteaEntry.class);
}
public void submit(XteaRequest xteaRequest)
{
boolean cached = true;
for (XteaKey key : xteaRequest.getKeys())
{
int region = key.getRegion();
int[] keys = key.getKeys();
XteaCache xteaCache = keyCache.getIfPresent(region);
if (xteaCache == null
|| xteaCache.getKey1() != keys[0]
|| xteaCache.getKey2() != keys[1]
|| xteaCache.getKey3() != keys[2]
|| xteaCache.getKey4() != keys[3])
{
cached = false;
keyCache.put(region, new XteaCache(region, keys[0], keys[1], keys[2], keys[3]));
}
}
if (cached)
{
return;
}
try (Connection con = sql2o.beginTransaction())
{
Query query = null;
for (XteaKey key : xteaRequest.getKeys())
{
int region = key.getRegion();
int[] keys = key.getKeys();
XteaEntry xteaEntry = findLatestXtea(con, region);
if (keys.length != 4)
{
throw new IllegalArgumentException("Key length must be 4");
}
// already have these?
// TODO : check if useful / works should check with findLatestXtea
if (xteaEntry != null
&& xteaEntry.getKey1() == keys[0]
&& xteaEntry.getKey2() == keys[1]
&& xteaEntry.getKey3() == keys[2]
&& xteaEntry.getKey4() == keys[3])
{
continue;
}
if (query == null)
{
query = con.createQuery("insert into xtea (region, rev, key1, key2, key3, key4) "
+ "values (:region, :rev, :key1, :key2, :key3, :key4)");
}
query.addParameter("region", region)
.addParameter("rev", xteaRequest.getRevision())
.addParameter("key1", keys[0])
.addParameter("key2", keys[1])
.addParameter("key3", keys[2])
.addParameter("key4", keys[3])
.addToBatch();
}
if (query != null)
{
query.executeBatch();
con.commit(false);
}
}
}
public List<XteaEntry> get()
{
try (Connection con = sql2o.open())
{
return con.createQuery(
"select t1.region, t2.time, t2.rev, t2.key1, t2.key2, t2.key3, t2.key4 from " +
"(select region,max(id) as id from xtea group by region) t1 " +
"join xtea t2 on t1.id = t2.id")
.executeAndFetch(XteaEntry.class);
}
}
public XteaEntry getRegion(int region)
{
try (Connection con = sql2o.open())
{
return con.createQuery("select region, time, rev, key1, key2, key3, key4 from xtea "
+ "where region = :region order by time desc limit 1")
.addParameter("region", region)
.executeAndFetchFirst(XteaEntry.class);
}
}
}

View File

@@ -0,0 +1,109 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.http.service.xtea;
import java.time.Instant;
public class XteaEntry
{
private int region;
private Instant time;
private int rev;
private int key1;
private int key2;
private int key3;
private int key4;
public int getRegion()
{
return region;
}
public void setRegion(int region)
{
this.region = region;
}
public Instant getTime()
{
return time;
}
public void setTime(Instant time)
{
this.time = time;
}
public int getRev()
{
return rev;
}
public void setRev(int rev)
{
this.rev = rev;
}
public int getKey1()
{
return key1;
}
public void setKey1(int key1)
{
this.key1 = key1;
}
public int getKey2()
{
return key2;
}
public void setKey2(int key2)
{
this.key2 = key2;
}
public int getKey3()
{
return key3;
}
public void setKey3(int key3)
{
this.key3 = key3;
}
public int getKey4()
{
return key4;
}
public void setKey4(int key4)
{
this.key4 = key4;
}
}