Java tutorial
/** * Copyright 2010 Comcast Cable Communications Management, LLC * * 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. */ package com.comcast.video.dawg.controller.house; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.LinkedList; import java.util.Map; import java.util.Set; import java.util.TreeMap; import org.apache.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import com.comcast.drivethru.exception.HttpException; import com.comcast.video.dawg.PopulatingClient; import com.comcast.video.dawg.common.MetaStb; import com.comcast.video.dawg.common.PersistableDevice; import com.comcast.video.dawg.common.ServerUtils; import com.comcast.video.dawg.common.exceptions.DawgIllegalArgumentException; import com.comcast.video.dawg.common.exceptions.DawgNotFoundException; import com.comcast.video.dawg.service.house.HouseService; import com.comcast.video.dawg.show.DawgShowClient; /** * The {@link HouseRestController} defines the public REST API for Dawg House, * the device indexing system. For more details on DAWG, see * TODO: Add new documentation location * * These methods are exposed at: * http://\<host\>:\<port\>/dawg-house/ * * Please note that <code>/devices/</code> is prefixed to all RequestMappings for the individual methods. * * So the effect contextPath for this interface is: * http://\<host\>:\<port\>/dawg-house/devices/ * * When an id parameter is required, it typically corresponds to the MAC Address * of a device, though it is an arbitrary string specified when the device is added to DAWG * * For consistency, the server will trim, lower case and remove all * non-alphanumeric characters from ids passed into methods. * * @author Val Apgar * */ @Controller @RequestMapping(value = "devices") public class HouseRestController { Logger logger = Logger.getLogger(HouseRestController.class); @Autowired HouseService service; @Autowired(required = false) PopulatingClient client; @Autowired ServerUtils serverUtils; /**{@link DawgHouseConfiguration} read from the property file.*/ @Autowired private DawgHouseConfiguration config; /** * List all the devices in the data store * * @return An array of map objects where each map represents a device in the data store */ @RequestMapping(value = "list*", method = RequestMethod.GET) @ResponseBody public Map<String, Object>[] listAll() { return service.getAll(); } /** * Get a specific devices information by its DAWG deviceID * * @param id the DAWG deviceId of the device. * @return a Map representing the device matching the provided DAWG deviceId */ @RequestMapping(value = "id/{id}", method = { RequestMethod.GET }) @ResponseBody public Map<String, Object> getStbsById(@PathVariable String id) { id = clean(id); Map<String, Object> device = service.getById(id); if (null != device) { return device; } else { throw new DawgNotFoundException("No existing device for id=" + id); } } public static final String[] ignoreProps = new String[] { "--class", PersistableDevice.RESERVER, PersistableDevice.EXPIRATION_KEY, MetaStb.CATSSERVERHOST, MetaStb.MACADDRESS, MetaStb.ID, MetaStb.MODIFIED_PROPS }; /** * Gets properties for the given stb that can be edited. Will also sort the properties. * @param id The id of the device to get editable properties for * @return */ @RequestMapping(value = "edit/{id}", method = { RequestMethod.GET }) @ResponseBody public Map<String, Object> editStb(@PathVariable String id) { MetaStb stb = new MetaStb(getStbsById(id)); Set<String> modifiedProps = stb.getModifiedProps(); Map<String, Object> data = stb.getData(); for (String iProp : ignoreProps) { data.remove(iProp); } Map<String, Object> sortedProps = new TreeMap<String, Object>(new MetaStbPropComparator()); for (String key : data.keySet()) { boolean modified = modifiedProps != null ? modifiedProps.contains(key) : false; sortedProps.put(key, new PropAndModified(data.get(key), modified)); } return sortedProps; } /** * Delete a specific device by its DAWG deviceId * * @param id the DAWG deviceId of the device. */ @RequestMapping(value = "id/{id}", method = { RequestMethod.DELETE }) @ResponseBody public void deleteSingle(@PathVariable String id) { id = clean(id); try { service.deleteStbById(id); } catch (Exception e) { throw new DawgIllegalArgumentException("Unable to delete device by id=" + id); } } /** * Add a new device with a specific DAWG deviceId * * @param id the DAWG deviceId of the device. * @param data a Map<String,Object> representation of the device. Typically this should contain the keys from {@link MetaStb} */ @RequestMapping(value = "id/{id}", method = { RequestMethod.PUT }) @ResponseBody public void add(@PathVariable String id, @RequestBody Map<String, Object> data) { id = clean(id); try { data.put(MetaStb.ID, id); service.upsertStb(new PersistableDevice(data)); } catch (Exception e) { throw new DawgIllegalArgumentException( "Could not insert the specified device with \n id=" + id + ",\n data=" + data); } } /** * Get a batch of devices by a set of DAWG deviceIds * * @param id An array of DAWG deviceIds to return the corresponding devices from * @return An array of Maps representing the devices matching the provided DAWG deviceIds. */ @RequestMapping(value = "id", method = { RequestMethod.POST }) @ResponseBody public Map<String, Object>[] getStbsByIds(@RequestParam String[] id) { for (int i = 0; i < id.length; i++) { id[i] = clean(id[i]); } Map<String, Object>[] devices = service.getStbsById(id); if (null == devices || 0 == devices.length) { throw new DawgNotFoundException("No devices were found with the specified id, id=" + Arrays.asList(id)); } else { return devices; } } /** * Update a set of devices by id with the corresponding data. * The server will attempt to retrieve existing devices by id and apply the provided * key value pairs into those devices. If no device exists for the device, nothing will happen. * * TODO: This method needs serious work. * * @param id An array of DAWG deviceIds to update * @param data A Map of key value pairs. * @return */ @RequestMapping(value = "update", method = { RequestMethod.POST }) @ResponseBody public int update(@RequestParam String[] id, @RequestBody Map<String, Object> data, @RequestParam(defaultValue = "false") boolean replace) { int counter = 0; for (int i = 0; i < id.length; i++) { id[i] = clean(id[i]); } Map<String, Object>[] matches = service.getStbsById(id); if (null != matches) { for (Map<String, Object> match : matches) { Map<String, Object> newProps = new HashMap<String, Object>(data); try { /** Do not override id and mac address */ newProps.put(MetaStb.ID, match.get(MetaStb.ID)); newProps.put(MetaStb.MACADDRESS, match.get(MetaStb.MACADDRESS)); PersistableDevice pd = new PersistableDevice(newProps); if (replace) { service.replace(pd); } else { service.upsertStb(pd); } counter++; } catch (Exception e) { e.printStackTrace(); logger.warn("Failed to update device. deviceId=" + match.get("id") + ", data=" + match); } } try { getDawgShowClient().updateCache(id); } catch (HttpException e) { logger.error("Failed to update DAWG show meta stb cache", e); } } return counter; } /** * Get the valid remote types from Dawg-show using DawgShowClient * @return A list of valid remote types */ @RequestMapping(value = "remotetypes", method = { RequestMethod.GET }, produces = "application/json; charset=utf-8") @ResponseBody public List<String> getRemoteTypes() { return getDawgShowClient().getRemoteTypes(); } /** * Update a set of devices by DAWG deviceId with the corresponding data. * The server will attempt to retrieve existing devices by deviceId and apply the provided * key value pairs into those devices. If no device exists for the device, nothing will happen. * * @param id An array of DAWG deviceIds to update * @param data A Map of key value pairs. * @return */ @SuppressWarnings({ "unchecked", "rawtypes" }) @RequestMapping(value = "update/tags/{op}", method = { RequestMethod.POST }) @ResponseBody public void updateTags(@RequestBody Map<String, Object> payload, @PathVariable("op") String op) { List<String> id = (List<String>) payload.get("id"); List<String> tag = (List<String>) payload.get("tag"); boolean add = op.equals("add"); if (null == id || id.size() < 1) { throw new DawgIllegalArgumentException("Could not update tags. No ids were provided."); } if (null == tag || tag.size() < 1) { throw new DawgIllegalArgumentException("Could not update tags. No tags were provided."); } tag = validateTags(tag); List<String> validIds = new ArrayList<String>(); // Cleanup the ids for (String i : id) { try { validIds.add(clean(i)); } catch (Exception e) { logger.warn("Updating tags, id unparsable. Id=" + i); } } if (validIds.size() < 1) { throw new DawgIllegalArgumentException("Could not update tags. None of the provided ids were valid."); } // find all the data matching the device ids Map<String, Object>[] matches = service.getStbsById(validIds.toArray(new String[validIds.size()])); if (null == matches || matches.length < 1) { throw new DawgIllegalArgumentException("Could not update tags. None of the provided existed."); } // update the tags List<PersistableDevice> devicesToUpdate = new ArrayList<PersistableDevice>(); for (Map<String, Object> match : matches) { if (null != match) { try { Set<String> updated = new HashSet<String>(); if (match.containsKey("tags")) { updated.addAll((Collection) match.get("tags")); } if (add) { updated.addAll(tag); } else { updated.removeAll(tag); } match.put("tags", updated); devicesToUpdate.add(new PersistableDevice(match)); } catch (Exception e) { logger.warn("Failed to update device. deviceId=" + match.get("id") + ", data=" + match, e); } } } service.upsertStb(devicesToUpdate.toArray(new PersistableDevice[devicesToUpdate.size()])); } // TODO: stubbed out for future development private List<String> validateTags(List<String> tags) { return tags; } /** * Get devices by searching with a query. * * TODO: The implementation details of this query need to be determined. * * @param q the query. Currently the query is an inline AND operation. For example, is q=this,that your query will be this AND that * @return An array of Maps corresponding to the devices that were found using the provided query. */ @RequestMapping(value = "query", method = { RequestMethod.GET, RequestMethod.POST }) @ResponseBody public Map<String, Object>[] getStbsByQuery(@RequestParam String q) { return service.getStbsByQuery(q.toLowerCase()); } /** * Get all devices which are matching with the provided tag. * * @param tag the tag to be searched * @param q the query need to check along with tag specified * @return an array of devices that were found using the provided tag and query. */ @RequestMapping(value = "tag/{tag}", method = { RequestMethod.GET, RequestMethod.POST }) @ResponseBody public Map<String, Object>[] getStbsByTag(@PathVariable String tag, @RequestParam(required = false) String q) { return service.getStbsByTag(tag, q); } /** * Batch deletion of devices by deviceId * * @param id An array of DAWG deviceIds to delete. <b>Each device with the matching id will be deleted</b> */ @RequestMapping(value = "delete", method = { RequestMethod.GET, RequestMethod.POST }) @ResponseBody public void deleteStb(@RequestParam String[] id) { for (int i = 0; i < id.length; i++) { id[i] = clean(id[i]); } service.deleteStbById(id); } /** * Scrape for boxes reserved but not allocated by the specified token * and add new entries into the data store for each device that was found. * * @param token The token to scrape device information from the client. * @param isToOverrideMetaData if true, the client meta data will override DAWG meta data ignoring the modified properties in DAWG. * @return int The number of devices that were entered into the data store. */ @RequestMapping(value = "populate/{token}", method = RequestMethod.POST) @ResponseBody public int populate(@PathVariable String token, @RequestParam(required = false, defaultValue = "false", value = "isToOverride") Boolean isToOverrideMetaData) { int counter = 0; try { List<MetaStb> stbs; if (null != client) stbs = client.populate(token); else stbs = new LinkedList<MetaStb>(); //List<MetaStb> stbs = client.populate(token); /** Gather all deviceIds of all devices to upsert */ String[] ids = new String[stbs.size()]; for (int i = 0; i < stbs.size(); i++) { ids[i] = stbs.get(i).getId(); } /** Create a mapping of deviceId to the existing device in dawg-house */ Map<String, Object>[] existingStbs = service.getStbsById(ids); Map<String, MetaStb> existingStbMap = new HashMap<String, MetaStb>(); for (Map<String, Object> data : existingStbs) { MetaStb stb = new MetaStb(data); existingStbMap.put(stb.getId(), stb); } /** Loop over all stbs from the client * If there is an existing stb we need to look at which properties are * purposely overridden in dawg-house. They must not change with any new values * from client. */ for (MetaStb stb : stbs) { MetaStb existing = existingStbMap.get(stb.getId()); MetaStb stbToUpsert; if (existing == null) { stbToUpsert = stb; } else { Map<String, Object> newData = stb.getData(); if (!isToOverrideMetaData) { Set<String> modifiedProps = existing.getModifiedProps(); for (String key : newData.keySet()) { if ((modifiedProps == null) || !modifiedProps.contains(key)) { existing.getData().put(key, newData.get(key)); } } } else { for (String key : newData.keySet()) { existing.getData().put(key, newData.get(key)); } existing.setModifiedProps(null); } stbToUpsert = existing; } try { service.upsertStb(stbToUpsert); counter++; } catch (Exception e) { e.printStackTrace(); logger.warn("Could not load into MetaStb=" + stb.toString() + ", Token=" + token); } } } catch (Exception e) { e.printStackTrace(); logger.error("Could not load anything from the PopulatingClient, Token=" + token); } return counter; } /** * Get the {@link DawgShowClient} for updating dawg-show properties from the * dawg-house. * * @return {@link DawgShowClient} */ protected DawgShowClient getDawgShowClient() { return new DawgShowClient(config.getDawgShowUrl()); } protected String clean(String string) { return serverUtils.clean(string); } }